Basics

The python file extension is *.py

To execute (assuming Python is in your PATH variable) run "python myFile.py" from the console.

Python is interpreted, not compiled.

(todo: look up detailed list of differences between 2 and 3)

Syntax

Statements

Python commands usually terminate on an end-line.
Ending statements with a semi-colon (;) does not cause an error, but is against convention.
Blank lines (white-space characters, comments) are ignored.

You can stretch a command across multiple lines by escaping the end-line character

if x==1 and y==0 \
    and z==6 \
    and t==8 :
    print("xyz")

You can place multiple statements on a single line by ending each statement with a ;

x=1; y=4; u=7;

Blocks

Python uses indents to define coding blocks, instead of braces.

x = 0;
if x == 1 :
    print("a");
    print("b");
print("c"); #only this is run

The indentation amount is not fixed, but must be consistent for all lines in a block. Indentation can be made up of tabs and/or spaces.

There are competing conventions for one-tab-per-indent and for x-spaces-per-indent.

Comments

Single line comments

    #comments
    
    x = 7 #comments

There are no multiline comments in python, but here is the commonly accepted work-around. Wrap the section in triple quotes to turn it into a multiline string literal. The line is still interpreted, but does nothing.

'''
comments
comments
comments
'''

Command Line

Arguments

To access command line arguments:

    import sys
    
    print(sys.argv) #an array of command line arguments
    print(sys.argv[0]) #the name of this script

Exit Script


    import sys
    
    sys.exit()
Import

How to organize your program into multiple files:


 #file A.py
import B
print("this is A")

 #file B.py in same directory
print("this is B")

 #output from running python A.py
 #this is B
 #this is A


 #file A.py
import B
B.myFunc() #must specify the module name to call the function
print("this is A")

 #file B.py in same directory
def myFunc():
    print("this is B")

 #output from running python A.py
 #this is B
 #this is A

Alias


import really________long_______name as shortName
import module.submodule.subsubmodule as otherShortName

Import Object

Don't want to say "datetime.datetime.now()"? You can import just the datetime object from the datetime module.

from datetime import datetime
currentDateTime = datetime.now()

I/O

Print

Writing output to the console. Print has an implicit end-line character.
String literals can be written with single or double quotes.

 #python 2
print "text"

 #python 3
print("text")

Print will concatenate strings together, with a default space (" ") delimitor.

x = 1
print("a", x) #outputs "a 1"

Formatted strings: everything inside a { } will be interpreted as a python expression in the current scope.

myCount = 5
myVar = "A"
print(f"count: {myCount}, var: {myVar}") #"count: 5, var: A"

Input

The user will see a prompt at the command line. When the user hits Enter, whatever they just typed will be returned to you.

Input - converts user text to the (seemingly) appropriate type

x = input("Enter a number:") #user input is assigned to x
print(x)

Raw Input - returns all input as a string

y = raw_input("Prompt:") #treats all input as string
y = int(y) #make sure of type #throws error if value cannot be converted to int
print(y)

Cleaning input

x = ""
while not x.isdigit():
    x = input("Prompt: ")
x = int(x)

Print


x = 5
y = "hey"
print(x) #5
print(y) #"hey"
print(str(x)+y) #"5hey"
print(f'{x} {y}') #"5 hey"
Variables

Variables are not declared. They are created when they are first assigned.

Naming Conventions

Begin with a letter or underscore
Continue with letters, numbers, or underscores
Names are case-sensitive
Only start with an underscore for special variables

Assignment


x = 1
y = "a"
z = 1 * 5 + 7 / 4

x = y = z = 1 #all equal 1 now

x,y,z = 1,"a",4+5 #x=1, y="a", z=9

You can swap variables in a single line.

x,y = 1,2 #x=1 and y=2
x,y = y,x #now x=2 and y=1

Naming Collisions

Python will let you name variables the same as modules, as objects, as functions. If you do so, the variable overwrites the previous "value".

For example, if you name a variable "sum" and then try to use the built-in sum function, you'll get an error.

Types

Python is loosely typed: it will automatically determine the variable type based on the value, and the type can change.

Everything in Python is an object, each with an identity, type, and value.
Some objects are mutable (value can be changed), some are immutable (value cannot be changed).

The types generally called "primitives" are all immutable objects in Python: integer, string, boolean, etc.

Collections and user-defined types are all mutable objects: list, set, dictionary, etc.

Definitions

Integer: a whole number

String: an ordered sequence of characters

Boolean: true or false

List: an ordered sequence of elements (mutable)
can contain different data types

Tuple: an ordered sequence of elements (immutable)
can contain different data types

Set: an unordered collection of unique elements
can contain different data types
Sets can only contain immutable objects - otherwise you get an "unhashable type" error
Sets have length, but not indexes.

Dict: an unordered collection of key/value pairs
can contain different data types

Get Type


print(type(12)) #<class 'int'>

print(type(12.56)) #<class 'float'>
print(type(5E22)) #<class 'float'>

print(type(True)) #<class 'bool'>
print(type(False)) #<class 'bool'>

print(type(complex(2,5))) #<class 'complex'>
print(type(2+5J)) #<class 'complex'>

print(type("a")) #<class 'str'>
print(type('a')) #<class 'str'>

print(type(u"a")) #<class 'str'>
print(type(u'a')) #<class 'str'>

print(type((1, "a", True))) #<class 'tuple'>
print(type(1, "a", True)) #<class 'tuple'>

print(type([1, "a", True])) #<class 'list'>

print(type({ "name":"Bob", "degree":"business", "age":23 })) #<class 'dict'>

print(type(range(0,10))) #<class 'range'>

Mutable

Mutable Types: List, Set, Dict

Immutable Types: Integer, Float, Complex, Boolean, String, Tuple, Frozenset

Coversions


ascii(x)   #returns string representation of object?
bin(x)     #int to binary string
bool(x)    #converts to boolean
chr(x)     #int to character
complex(x) #constructs a complex number from arguments
float(x)   #converts int or string to float
hex(x)     #converts int to hexadecimal string
int(x)     #constructs an int from number or string
oct(x)     #converts into to octal string
ord(x)     #converts character to int
repr(x)    #returns string representation of object?
str(x)     #returns string representation of object?
type(x)    #returns the type of the object

Iterators

Iterable Types: String, List, Tuple, Dict, Set, Frozenset

Unordered Types: Dict, Set, Frozenset


all(iterable) #returns True if all elements are True
any(iterable) #returns True if any elements are True

enumerate(iterable) #returns a list of tuples as (index, value)

filter(myFilter, iterable) #returns a list of the elements that passed the filter
The Filter Is A Function That Accepts One Element And Returns True Or False
myIter = iter(collection) #returns an iterable object for the collection a = next(myIter) #first value b = next(myIter) #second value len(collection) #returns the number of elements in the collection map(myMap, iterable) #applies a function to each element of a collection and returns a list of the results
The Map Is A Function That Accepts One Element And Returns Any Value
reversed(iterable) #returns new collection in the reversed order sorted(iterable) #returns new collection in the sorted order zip(iterableA, iterableB, ...) #returns a list of tuples made up of one element from each provided collection a = ["A","B","C"] b = [1,2,3] c = zip(a, b) #equals [("A",1),("B",2),("C",3)]
Sequence Types

string, list, tuple, range, buffer, unicode string

Declarations:

    stringA = "abc"
    stringB = 'abc'
    stringRaw = r'\a\b\c' #"\a\b\c"

    tupleA = (1, 'a', True)
    #parentheses are optional
    tupleB = 1, 'a', True 
    #an empty tuple requires parentheses
    tupleC = ()
    #a single element tuple requires parentheses and a comma
    #or it will be interpreted as the simple type (int, in this case)
    tupleD = (1, )

    listA = [1, 'a', True]
    listB = []
    
    unicodeA = u"abc"
    unicodeB = u'abc'
    
    #buffer objects are not directly supported, they are returned by some functions
    
    #range objects are not directly supported, they are returned by the "range()" function

String escape characters:
\n newline
\t tab horizontal
\\ backslash
\' single quote
\" double quote

String, List, Tuple

Operators:

    if element in sequence:
        print(True)
    if "out" in "without":
        print(True)
    if 2 in [1,2,3]:
        print(True)
        
    if element not in sequence:
        print(False)
        
    concatA = sequenceA + sequenceB
    concatB = sequenceA sequenceB

    #concatenate x shallow copies of sequence together
    repeatedA = sequenceA * count
    repeatedB = count * sequenceB
    
    #get ith element, 0-based index
    elementA = sequence[i]
    
    #get ith through (j-1)th elements in a new sequence
    sliceA = sequence[i:j]
    
    #get ith through (j-1)th elements, with steps of k
    sliceB = sequence[i:j:k]
    
    length = len(sequence)
    
    minElement = min(sequence)
    
    maxElement = max(sequence)

Indexing:

    #0 origin
    s = "abcdefg"
    print(s[0]) #"a"
    print(s[-1]) #"g"

Slice:

x = "0123456789"

 #boundaries default to start and end of sequence

print(x[2:5]) #234
print(x[-2:-5]) #prints empty line, cannot reverse a string this way
print(x[::-1]) #9876543210 #this is how you reverse a string
print(x[-5:-2]) #567

print(x[2:]) #23456789
print(x[:5]) #01234

print(s[:]) #"abcdefg" #makes a shallow copy of the sequence

Slice - third optional param is "stride" - how many characters to increment on each step

print(x[2::1]) #2345678
print(x[2::2]) #2468
print(x[2::3]) #258
print(x[2::-1]) #210 - went backward and stopped on its own
print(x[2:5:-1]) #prints empty line

Slice Assignment:

x = "0123456789"
 #slice assignment is like deleting, then inserting
s[2:4] = "123456"
print(s) #"ab123456efg"

Slice with step or stride:

s = "abcdefg"
print(s[0:5:1]) #"abcde"
print(s[0:5:2]) #"ace"
print(s[0:5:3]) #"ad"

print(s[::-1]) #"gfedcba" #reverses the sequence
print(s[4:1:-2]) #"ec"

Numbers

Number types are split into integers, floating point (fractions), and complex (imaginary).
Booleans are a subtype of integer.

Number types are created with numeric literals.

Complex numbers are in the form A+Bi where i is the imaginary number.
Python uses j instead of i, so the form is A+Bj.

x = 3+4J
y = 3+4j
z = complex(3,4)

Scientific numbers can be defined in the form nEm or nem.

x = 5E244
y = 5e244

Binary (digits 0-1):

x = 0B100
y = 0b100
z = 4

Octal (digits 0-7):

x = 0O100
y = 0o100
z = 64

Hexadecimal (digits 0-F):

x = 0X100
y = 0x100
z = 256

Set

Operators

x = {1, 2, 3}
y = {2, 3, 4}

print(x - y) #{1}

print(x | y) #{1, 2, 3, 4} #x + y is not valid syntax

print (x & y) #{2, 3}

print(x ^ y) #{1, 4}

Add - add a value to the set
If the value is already in the set, there is no error

x = {1, 2, 3}
x.add(5)
print(x) #{1, 2, 3, 5} #order may vary

Update - add each value in the iterable to the set

x = {1, 2, 3}
x.update([5, 6, 7, 7, 7])
print(x) #{1, 2, 3, 5, 6, 7} #order may vary

Remove - removes one value from the set
Throws a KeyError if the value is not in the set

x = {1, 2, 3}
x.remove(2)
print(x) #{1, 3}

Copy - create a new set with all the same values in it

x = {1, 2, 3}
y = x.copy()
print(y) #{1, 2, 3}

Frozenset

A frozenset is an immutable set. You'll need this if you want to create a set of sets; the inner sets will have to be frozensets.


a = set([1,2,3])
b = set([4,5,6])

a2 = frozenset(a)
b2 = frozenset(b)

c = set([a2, b2])

Dictionary

A dictionary is an unordered list of key:value pairs.
Dictionaries are mutable.

Keys must be unique within the dictionary.
Keys can be any immutable type.
The keys are used as indexes to access the values.


x = { "name":"Bob", "degree":"business", "age":23 }

print(x) #{'name': 'Bob', 'degree': 'business', 'age': 23}
print(x["name"]) #Bob

z = {} #empty dictionary

Assignment

x = { "name":"Bob", "degree":"business", "age":23 }
x["age"] = 45
print(x["age"]) #45

In - returns boolean for whether a given key is in the dictionary

x = { "name":"Bob", "degree":"business", "age":23 }
if "name" in x:
    print(x["name"])

Keys - returns an iterable view of the dictionary keys

x = { "name":"Bob", "degree":"business", "age":23 }
for key in x.keys():
    print(key, "=", x[key])

You cannot edit a dictionary while iterating over the key view. You have to create a copy of the iterable first.

x = { "name":"Bob", "degree":"business", "age":23 }
keys = list(x.keys())
for key in keys:
    if x[key] == 23:
        del x[key]

Values - returns an iterable view of the dictionary values

x = { "name":"Bob", "degree":"business", "age":23 }
for value in x.values():
    print(value)

Delete - remove a key and its value from a dictionary

x = { "name":"Bob", "degree":"business", "age":23 }
del x["degree"]
print(x) #{ "name":"Bob", "age":23 }

Pop - returns a value from a key, or a default if the key doesn't exist; also deletes the key:value

x = { "name":"Bob", "degree":"business", "age":23 }
y = x.pop("degree", None)
print(x) #{ "name":"Bob", "age":23 }
print(y) #"business"

Zip - create a dictionary from a list of keys and a list of values

    x = dict(zip(('a','b','c','d','e'),(1,2,3,4,5)))
    #equivalent to {'a':1, 'b':2, 'c':3, 'd':4, 'e':5 }

None

None is a singularity object, so all Nones are equal.

None is always returned from functions that don't return anything.

None's truth value is False.

You can use None anywhere it makes sense to you.


x = None

Conversions

Int - convert string to integer; throws an error if conversion is not possible

x = int("45") #45

Str - convert anything to a string

x = str(359) #"359"

Ord - convert character to ascii code integer

x = ord('a')
print(x) #97

Chr - convert integer ascii code to character

x = chr(97) 
print(x) #'a'

Bin - convert integer to binary (base 2) form

x = bin(10)
print(x) #"0b1010"

Maximums

Python has not maximum size on integers.

Collections have a maximum size.

import sys
print(sys.maxsize)

DateTime


import datetime

datetime_string = "Jun 28 2018 7:40AM"
datetime_parsed = datetime.datetime.strptime(datetime_string, "%b %d %Y %I:%M%p")

datetime_string = "2018-06-29 08:15:27.243860"
datetime_parsed = datetime.datetime.strptime(datetime_string, "%Y-%m-%d %H:%M:%S.%f")

print('Date:', datetime_parsed.date())
print('Time:', datetime_parsed.time())
print('Date-time:', datetime_parsed)

print('My format:', datetime_parsed.strftime("%Y-%m-%d"))

%Y year in 4 digits
%m month
%d day of month
%H hour in 24 hour cycle
%M minutes
%S seconds
%f microseconds
full datetime documentation

Big Numbers

Integer

Python implicitly supports very large integers.

Float Or Decimal

To use very precise floating point numbers:

import decimal

Set Precision
decimal.getcontext().prec = 100
Calculate
x = decimal.Decimal(y - z)
Controls

Switch is not supported in Python.

Keywords "continue" and "break" and "pass" are supported.

If


if a == b:
    print("a == b")
elif b == c:
    print("b == c")
else:
    print("none of the above")

Single line (the else is required here)

x = 5 if a == b else 6 #if a == b then x = 5 otherwise x = 6

For

For can iterate over any sequence: string, list, tuple, set, dictionary

x = ["a", "b", "c"]
for myVar in x:
    print(myVar)
    
y = {"a":1, "b":2, "c":3}
for key in y:
    print(key, "=", y[key])
for v in y.values():
    print(v)

A for loop can have an else statement, which executes at the end of the loop (unless the loop was broken)

for x in y:
    print("hey")
else:
    print("done")

You cannot edit the for loop iterator like in javascript or C#

backedUp = False
for i in range(5):
    print(i)
    if not backedUp:
        i -= 1
        backedUp = True
 #outputs 0 1 2 3 4

break: exit the loop, skipping the else statement

continue: skip ahead to the next iteration of the loop

While

Continues looping until the condition is False.


while x < 10:
    print(x)
    x += 1

While can also have an else that is executed once at the end, if loop is not broken.

while x < 10:
    print(x)
    x+=1
else:
    print("done")

break: exit the loop, skipping the else statement

continue: skip ahead to the next iteration of the loop

Enumerate

Enumerate adds a built-in counter or index or row number to a loop.
The enumerate actually returns tuples of (index, value), which are being mapped to our two variables.

for index, value in enumerate(myCollection):
    #do stuff

Enumerator starts count at zero by default, but you can specify the starting index.

for index, value in enumerate(myCollection, 5):
    #do stuff

Comprehension

A comprehension is a one-line expression defining a sequence of values that allows you to filter and edit the results.


x = [0 for i in range(10)] #[0,0,0,0,0,0,0,0,0,0]

y = [i*i for i in range(10)] #[0,1,4,9,16,25,36,49,64,81]

z = [str(i) for i in range(10) if i % 2 == 0] #["0","2","4","6","8"]


Operators

Operator: arithmetic and logical symbols
Operand: the values the operator uses

Arithmetic
    + plus
    - minus
    * times
    / divide
    // divide then floor
    % modulus (remainder of divide then floor)
    ** power
    abs(x) absolute value
    divmod(x, y) returns tuple (x/y, x%y)

Python does not support ++ or --

You can separate arithmetic phrases with parentheses ( )

Logic
    ==
    !=
    >
    <
    >=
    <=
    not
    and (also written &)
    or (also written |)
    
You can separate logical phrases with parentheses ( )
    
Assignment
    =
    +=
    -=
    *=
    /=
    //=
    %=
    **=
    
Bitwise
    & and
    | or
    ^ xor (when only one is true)
    ~ not
    << shift left
    >> shift right

Built-In Functions

Math


abs(a) #absolute value

divmod(a, b)

min(iterable)
min(a, b, c...)

max(iterable)
max(a, b, c...)

pow(a, b) #a to the power b

round(a) #rounds a float to an int

sum(iterable)
sum(a, b, c...)

Range

Range returns a sequence of consecutive integers.
Sequence-type "range".

Common usage

for myVar in range(5):
    print(myVar) #0 1 2 3 4

Return range from 0 to N-1

x = range(5) #basically 0, 1, 2, 3, 4
print(type(x)) #<class 'range'>
print(x) #range(0, 5)

Returns a range from N to M-1

x = range(2, 5) #basically 2, 3, 4

Returns a range from N to M-1, with strides of P

x = range(2, 20, 3) #basically 2, 5, 8, 11, 14, 17

Convert a range to a list

x = list(range(10)) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Proving that ranges are determined once, at the beginning of the loop

s = "a"
for i in range(0, len(s)):
    s += "a"
    print(s)
    if len(s) > 10:
        break
 #results in "aa"

String

Join - join the elements of a collection into one string
The syntax begins with the string delimiter to be used

x = ["a", "b", "c"]
y = "_".join(x)
print(y) #a_b_c

Split - divide a string into a list of strings, based on a delimiter

x = "a,b,cde"
y = x.split(",") #["a", "b", "cde"]

text = "Bob,business,45"
name,degree,age = text.split(",")

Strip - remove leading and trailing white spaces and end-line characters

x = " text ".strip()
print(x) #"text"

IsDigit - returns boolean - could this string be converted to am integer?

if myString.isdigit():
    x = int(myString)

EndsWith - returns boolean - does string end with substring?

if "hello".endswith("llo"):
    print(True)

List

Append - add an object to the end of the list

x = [1, 2, 3]
x.append(4)
print(x) #[1, 2, 3, 4]

x.append([5, 6])
print(x) #[1, 2, 3, 4, [5, 6]]

Extend - add each element in the iterable to the end of the list

x = [1, 2, 3]
x.extend([4, 5, 6])
print(x) #[1, 2, 3, 4, 5, 6]

Insert - insert an element into a position in the list

x = ["a", "e", "i"]
x.insert(1, "b") #insert "b" at index position 1
print(x) #["a", "b", "e", "i"]

Index - returns the index of the first instance of this value in the list
Throws an error if the value is not in the list

x = ["a", "b", "c"]
y = x.index("c")
print(y) #2

Delete - remove an element by index

x = ["a", "b", "c"]
del x[0]
print(x) #["b", "c"]

Remove - remove the first instance of this value from the list

x = ["a", "b", "c", "b"]
x.remove("b")
print(x) #["a", "c", "b"]

Clear - remove all elements from the list

x = ["a", "b", "c"]
x.clear()
print(x) #[]
Basic ascending sort

x = [5, 3, 7, 4, 1]
x.sort() #only works on lists
print(x) #[1, 3, 4, 5, 7]

Basic ascending sort for any iterable

a = { 1:"one", 2:"two", 3:"three", 6:"six", 7:"seven", 9:"nine" }
x = [6,3,7,9,3,2]
y = sorted(x)
print(y) #[2, 3, 3, 6, 7, 9]

z = sorted(x, key=lambda s:a[s]) #can specify sort key
print(z) #[9, 1, 3, 3, 2, 7, 6]

Object

Return a list of string-names of all the methods and properties of an object

x = dir(myObject)

Files

Write

The write command does not automatically add an end-line character.

Create or overwrite file

f = open("filename.txt", "w") #w for write
f.write("text")
f.close()

Append to file

f = open("filename.txt", "a") #a for append
f.write("more text")
f.close()

Append or create

f = open("filename.txt", "a+")

Read

Read - read the entire file as one string

f = open("filename.txt", "r") #r for read
text = f.read() #read will read in the entire file at once as a string
f.close()

Read(charCount) - read a few characters at a time

f = open("filename.txt", "r") #r for read
charCount = 1
next = f.read(charCount) #read a few chars at a time
while next != "":
    next = f.read(charCount)
f.close()

Readline - read one line at a time
The line still has the end-line character

f = open("filename.txt", "r") #r for read
next = f.readline() #reads one line (to \n char)
while next != "":
    next = f.readline()
f.close()

Readlines - read in all lines at once, as a list of strings
Each line still has the end-line character

f = open("filename.txt", "r") #r for read
text = f.readlines()
f.close()

Byte Position

Tell - returns the current byte position of the cursor in the file

currentBytePosition = file.tell()

Seek(position) - move to an absolute byte position in the file

file.seek(absoluteBytePosition)
file.seek(absoluteBytePosition, 0)

Seek(position, 1) - move forward the specified number of bytes

file.seek(relativeBytePosition, 1)

Seek(position, 2) - move to the specified byte position, counting backward from the end of the file

file.seek(fromEndOfFile, 2)

Exists


import os.path
from os import path

Path or file exists

x = path.exists('path/fileName.txt')

Path is a directory

x = path.isdir('path')

Path is a file

x = path.isfile('path/fileName.txt')
Functions

Functions must be defined before they are called (higher in the text file).
Functions that are in an imported file can call each other in any order.


def funcName(): #definition
    print("my function")
    
funcName() #invocation

Empty function:

def empty():
    pass

First Class

Functions are first class objects in Python.

They can be passed as arguments.
They can be returned from functions.

They can be nested within other functions.
Nested functions are only in-scope to their direct parent.
Nested functions have access to parent variables.

Scope

Variables assigned a value anywhere in the function are assumed local, unless explicitly declared global.

x = 1

def funcA():
    global x
    x = 2
    print(x) #prints 2
    
funcA()    
print(x) #prints 2

Declaring a new variable global within a function will define it as a global variable, but you should not rely on this because the variable does not exist until the function is run

def funcA():
    global x
    x = 2
    print(x)
    
funcA()    
print(x) #throws an error if funcA is not run first

Method Overloading

Python does not support method overloading.

This example won't work, because the last function definition overwrites the earlier ones of the same name.

 #!!! This won't work !!!
def myFunc(a):
    print("a")
def myFunc(a, b):
    print("a b")
def myFunc(a, b, c):
    print("a b c")
myFunc(1)
myFunc(1, 2)
myFunc(1, 2, 3)

Monkey Patching

Monkey patching is overwriting the behavior of a function or object after it is defined. Do not do this.

Default Parameters

You can use default parameter values to get close to method overloading.


def myFunc(a, b=None, c=None):
    if c == None:
        if b == None:
            print(a)
            return
        print(a, b)
        return
    print(a, b, c)
myFunc(1)
myFunc(1, 2)
myFunc(1, 2, 3)

Passing Parameters

Python passes all arguments as pass-by-object-reference. That means that all object operations used in a function will affect the original argument, except for instantiation.

Immutable objects are de-facto pass-by-value because any edit operation automatically re-instantiates the object.


def funcA(listA):
    listA.append("a") #most object operations affect the original argument
def funcB(listB):
    listB = ["b", "b", "b"] #instantiate does not affect the original argument

x = [1, 2, 3]
print(x) #[1, 2, 3]

funcA(x)
print(x) #[1, 2, 3, "a"]

funcB(x)
print(x) #[1, 2, 3, "a"]

Strict Signatures

Use strict signatures to enforce usage standards for how to pass arguments to methods. This can protect callers from breaking changes as the argument list of a function changes over time.
- For instance, any parameters that are likely to be removed later, or to be reordered for the legibility of the function signature, can be marked as "keyword passing only" so that calling code is not broken by changes to the function.
- "positional passing only" parameters should be completely solid arguments that are central to the use case of the function, and the function call should be legible without seeing their argument names.
- Can ensure all calls to the function are formatted consistently.

Force all callers to use positional passing for the parameters left of the "/"

def funcA(paramA, paramB, /, paramC, paramD):
    something
    
 # ways to all the function    
funcA("a", "b", "c", "d")
funcA("a", "b", "c", paramD="d")
funcA("a", "b", paramC="c", paramD="d")

Force all callers to use keyword passing for the parameters right of the "*"

def funcA(paramA, paramB, *, paramC, paramD):
    something
    
 # ways to all the function    
funcA("a", "b", paramC="c", paramD="d")
funcA("a", paramB="b", paramC="c", paramD="d")
funcA(paramA="a", paramB="b", paramC="c", paramD="d")

Doing both at once

def funcA(paramA, paramB, /, *, paramC, paramD):
    something

 # ways to all the function    
funcA("a", "b", paramC="c", paramD="d")

*args **kwargs

args is a tuple containing an undefined number of function arguments.

kwargs is a dictionary containing an undefined number of named (aka keyword) arguments.


def myMethod(a, b, *args, **kwargs):
    print(a)
    print(b)
    print(args)
    print(kwargs["name"])

myMethod(1, 2, 3, 4, 5, name="Bob", age=6)
    #prints:
    #1
    #2
    #(3, 4, 5)
    #Bob

"args" and "kwargs" are conventions. You can call these parameters anything you want, provided they start with "*" and "**".

You cannot list normal parameters between the *args and *kwargs.

You cannot enter keyword arguments into the *args tuple, and you cannot enter normal arguments into the **kwargs dictionary.

Closures

Functions can be defined within in functions in Python, like in Javascript.

The inner function has access to the outer function's local variables and parameters, even after the outer function has completed.

See Javascript notes about closures.

Lambda

Lambdas are single-expression, anonymous functions. They do not use a "return" keyword, they simply return the result of their single expression, if any.


myDisplay = lambda: print("hey")
myDisplay() #prints "hey"

myCalc = lambda: 5 + 3
print(myCalc()) #prints 8

Lambdas with parameters:

myCalc = lambda x: x + 3
print(myCalc(6)) #prints 9

myCalc = lambda x, y, z: x + y + z
print(myCalc(3, 4, 5)) #prints 12

Type Hinting

Python 3.5 and higher.

Specify what parameter types are expected, and what type will be returned.

The types of parameters passed in are checked at runtime; sending in the wrong type will result in a TypeError exception.
Subtypes of the specified type are accepted.

The return type is not verified, so you can actually return any value you want.


    #expects a string parameter, and returns a string
    def hello_world(name: str) -> str:
        return(f"Hello {name}")

Python 3.5.2 and higher: type hinting for function signatures.

    from typing import Callable

    #parameter is expected to be a function that accepts two integers as arguments and returns a string
    def method_a(callback: Callable[[int,int],str]) -> None:
        print(callback(1,2))

    #parameter is expected to be a function (with any arguments) that returns a string
    def method_b(callback: Callable[...,str]) -> None:
        print(callback([], "a", 1)

Type Aliasing

Python 3.5 and higher. Related to type hinting.

You can define an alias for a type, and then use the alias anywhere you'd use the type.


    MyAlias = str

    def hello_world(name: MyAlias) -> MyAlias:
        return(f"Hello {name}")

    print(hello_world("Bob")) #outputs "Hello Bob"
    print(hello_world(1))     #throw TypeError
	

    Example from documentation:

    from typing import List, Dict, Tuple, Sequence

    Vector = List[float]
    ConnectionOptions = Dict[str,str]
    Address = Tuple[str,int]

    #these alias can be used to enforce parameter types

New Type

Python 3.5 and higher. (Maybe belongs in a different section of notes)

Easily define a new subtype of a type, with a distinct name.


from typing import NewType

UserId = NewType('UserId', int)

Note that using UserId in an operation will return an int.
Class


class MyClass:
    def __init__(self, inputA):
        self.varA = inputA
        print("object initialization")
    
    def funcA(self):
        print("hey")

x = MyClass(5) #"object initialization"
print(x.varA) #5
x.funcA() #"hey"

Variables

Instance variables
    no privacy option
    naming convention is __var__ to indicate the variable should be treated as private

class MyClass:
    def __init__(self):
        self.instance_variable = 3;

    def print(self):
        print(self.instance_variable)

x = MyClass()
x.print() #"3"
print(x.instance_variable) #"3"

Class variables (aka Static variables)
    no privacy option
    value can change
    editing a class variable affects all instances of the class
    class_var and self.class_var are different variables

class MyClass:
    class_variable = 4;

    def __init__(self):
        self.class_variable = 3;
        
    def print(self):
        print(MyClass.class_variable, self.class_variable)

x = MyClass()
x.print() #"4 3"
MyClass.class_variable = 5
x.print() #"5 3"

Method Types

(In Python 3)

Instance methods can modify the current instance, through "self".
Instance methods can modify the current class, through "self.__class__".

Class methods can modify class state, affecting all instances of the class, through "cls".

Static methods cannot modify the class nor instance state. They are primarily used to namespace a method.


class MyClass:
    #this is an instance method
    def methodA(self):
        return ("instance method called", self)
    
    @classmethod
    def methodB(cls):
        return ("class method called", cls)
        
    @staticmethod
    def methodC():
        return "static method called"
        
 #calling the methods
x = MyClass()
x.methodA()
 #or
MyClass.methodA(x) #x.methodA() really means this

x.methodB()
MyClass.methodB()

x.methodC()
MyClass.methodC()

Note that calling the default parameters "self" and "cls" is a convention, you an call them anything.

Example of using classmethod for the factory pattern

import datetime
import date
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    @classmethod
    def fromBirthYear(cls, name, birthYear):
        return cls(name, date.today().year - birthYear)
        
x = Person("Bob", 49)
y = Person.fromBirthYear("Jane", 1978)

Overloading Operators

This topic is called operator overloading in general. Python specifically calls it magic methods.

These methods are not invoke explicitly, they are invoked during object instantiation or through a mathematical or logical operation.


class MyClass:
    def __init__(self, x):
        self.x = x
        
    #overload + operator
    def __add__(self, other):
        return MyClass(self.x + other.x)

+ __add__
* __mul__
- __sub__
% __mod__
/ __truediv__
// __floordiv__
** __pow__(self, power)
abs(x) __abs__
-x __neg__
+x __pos__
< __lt__
<= __le__
== __eq__
!= __ne__
> __gt__
>= __ge__
<< __lshift__
>> __rshift__
and __and__
or __or__
xor __xor__
not __invert__
[index] __getitem__(self, index)
in __contains__(self, value)
len __len__
str __str__
__iter__
__getitem__
__getslice__
__setitem__
__setslice__
__setitem__
__delitem__
__delslice__
__delitem__
int(x) __int__
long(x) __long__
float(x) __float__
complex(x) __complex__
divmod(x, y) __divmod__

Property

By convention, you are not supposed to manually access a variable that starts with an underscore, but that is not enforced by Python. The closest thing to a private variable in Python is to create a closure.

This is the recommended way to make class properties in Python.


class MyClass(object):
    def __init__(self):
        print("init")
        self._x = None

    def getx(self):
        print("get")
        return self._x
    def setx(self, value):
        print("set")
        self._x = value
    def delx(self):
        print("delete")
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

a = MyClass()
a.x = 5
print(a.x)
print(a._x) #does not trigger the getx method
a._x = 6 #does not trigger the setx method
print(a.x)
print(a._x) #does not trigger the getx method

This is the same thing, using decorators.


class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Attributes

You can assign class attributes within the class, and outside the class.
You can access all attributes of a class instance.

class A(object):
    pass

a = A()
a.x = 10
a.y = "string"
print(a.__dict__) #outputs {'x': 10, 'y':'string'}

Listing all the public attributes of a class (not an instance of a class):

import random
cls = "random" # name of the class as a string
all_attributes = [x for x in dir(eval(cls)) if not x.startswith("__") and callable(eval(cls + "." + x))]
print(all_attributes)

Slots

To stop users from assigning whatever attributes they want to a class, you must define slots.

class A(object):
    __slots__ = ['x'] #this line here

    def __init__(self, x):
        self.x = x
        
a = A(10)
a.x = 20
a.y = "string" #AttributeError: 'A' object has no attribute 'y'

With slots, you define the full list of attributes for the object once, and no new attributes can be added by anyone.

Attribute __dict__ is not available when you use slots.
If you define __dict__ manually, it will not be automatically filled.

Inheritance

Classes can inherit from multiple classes.

Method overriding: subclass methods override superclass methods of the same name.

Basic inheritance:

class Person:
    def __init__(self, firstName, lastName):
        self.firstName = firstName
        self.lastName = lastName
    def GetName(self):
        return self.firstName + " " + self.lastName
    
class Employee(Person):
    def __init__(self, firstName, lastName, id):
        Person.__init__(self, firstName, lastName) #call base constructor
        self.id = id

Diamond inheritance: the first class inherited from takes priority over later classes in the inheritance list.

class A:
    def id(self):
        print("A")

class B(A):
    def id(self):
        print("B")
    
class C(A):
    def id(self):
        print("C")

class D(B,C):
    pass

class E(C,B):
    pass

d = D();
d.id(); #outputs B

e = E();
e.id(); # outputs C

Diamond inheritance in Python 3:
- a subclass that overrides a superclass method takes priority over a subclass listed earlier in the inheritance list that does not override the superclass method.
- The "Method Resolution Order" (MRO) is depth-first (later generation classes first) then left-to-right as the classes are ordered in the inheritance list. (MRO is also called C3 superclass linearisation.)

 #C.id takes priority even when B is earlier in the inheritance list 
 #because it is closer to D and E in the inheritance tree than A is.
class A:
    def id(self):
        print("A")

class B(A):
    pass
    
class C(A):
    def id(self):
        print("C")

class D(B,C):
    pass

class E(C,B):
    pass

d = D();
d.id(); #outputs C

e = E();
e.id(); #outputs C

This demonstrates that depth is determined as which superclass is closest to the calling class, rather than which superclass is furthest from the top-level/origin class.

class A:
    def id(self):
        print("A")

class B(A):
    def id(self):
        print("B")
    
class C(A):
    def id(self):
        print("C")

class D(C):
    def id(self):
        print("D")

class E(B,D):
    pass
    
class F(D,B):
    pass
    
 #inheritance tree:
 # A <= B <= E,F
 # A <= C <= D <= E,F

e = E();
e.id(); #outputs B

f = F();
f.id(); #outputs D

When calling base methods, there are two ways to do it in Python 3:

Person.__init__(self, firstName, lastName)
 #or
super().__init__(firstName, lastName) #super() refers the the MRO superclass

class A:
    def id(self):
        print("A")

class B(A):
    pass
    
class C(A):
    def id(self):
        print("C")

class D(B,C):
    def id(self):
        print("D")
        C.id(self)
        B.id(self)
        A.id(self)

d = D();
d.id(); # outputs D C A A

Metaclass

An instance of a metaclass is a class.
All metaclasses must inherit from "type" instead of "object".

required = True

class MyMetaClass(type):
    def __init__(cls, clsname, superclasses, attributedict):
        if required:
            cls.myAttribute = "Required"
        else:
            cls.myAttribute = "Optional"

class A(metaclass=MyMetaClass):
    pass
    
required = False

class B(metaclass=MyMetaClass):
    pass
    
a = A()
print(a.myAttribute) #outputs Required

b = B()
print(b.myAttribute) #outputs Optional
In this example, A and B inherit from different instances of MyMetaClass. The behavior of the instances they inherit from was changed by the state of the program.

You can use a metaclass to create a Singleton object:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]
    
class SingletonClass(metaclass=Singleton):
    pass

class RegularClass():
    pass

x = SingletonClass()
y = SingletonClass()
print(x == y) #outputs True

x = RegularClass()
y = RegularClass()
print(x == y) #outputs False
But since you can do the same thing with normal classes, why use metaclasses?

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = object.__new__(cls, *args, **kwargs)
        return cls._instance
    
class SingletonClass(Singleton):
    pass

class RegularClass():
    pass

x = SingletonClass()
y = SingletonClass()
print(x == y) #outputs True

x = RegularClass()
y = RegularClass()
print(x == y) #outputs False

Using metaclass to automatically apply a decorator to every method in each subclass:

class FuncCallCounter(type):
    #this metaclass will decorate all the methods of the subclass with "call_counter"
    
    @staticmethod
    def call_counter(func):
        # decorator for counting the number of function/method calls
        def helper(*args, **kwargs):
            helper.calls += 1
            return func(*args, **kwargs)
        helper.calls = 0
        helper.__name__= func.__name__
        return helper
    
    
    def __new__(cls, clsname, superclasses, attributedict):
        for attr in attributedict:
            if callable(attributedict[attr]) and not attr.startswith("__"):
                attributedict[attr] = cls.call_counter(attributedict[attr]) #applying decorator to subclass method
        return type.__new__(cls, clsname, superclasses, attributedict)
    
class A(metaclass=FuncCallCounter):
    
    def B(self):
        pass
    
    def C(self):
        pass

x = A()
print(x.B.calls, x.C.calls) #outputs 0 0

x.B()
print(x.B.calls, x.C.calls) #outputs 1 0

x.B()
x.C()
print(x.B.calls, x.C.calls) #outputs 2 1

Abstract Class

Python does not automatically support abstract class, but it does provide a module that supports it.
Module "abc" stands for "abstract base class"

from abc import ABC, abstractmethod
 
class MyAbstractClass(ABC): #must inherit from ABC
 
    def __init__(self, value):
        self.value = value
        super().__init__() #not required
    
    @abstractmethod #must contain at least one abstractmethod
    def do_something(self):
        pass
        
class A(MyAbstractClass):

    def do_something(self):
        print("do something")
        
a = A(10)
a.do_something() #outputs do something

b = MyAbstractClass(10) #TypeError: Can't instantiate abstract class MyAbstractClass with abstract methods do_something

A class that inherits from ABC but does not contain any abstractmethods is not treated as abstract.

Abstract classes are allowed to contain non-abstractmethods. Inheritance of these methods works like normal inheritance.

A class that inherits from an abstract class must override all abstractmethods of that class. Or it must inherit all those methods from elsewhere before inheriting from the abstract class.

from abc import ABC, abstractmethod
 
class MyAbstractClass(ABC):
 
    def __init__(self, value):
        self.value = value
    
    @abstractmethod
    def do_something(self):
        pass
        
class A():
    
    def do_something(self):
        print("do something")
        
class B(A, MyAbstractClass):
    pass
        
b = B(10)
b.do_something() #outputs do something

Abstract methods may contain default behavior. The inheriting subclass must still override the abstract method, but can call the default behavior:

from abc import ABC, abstractmethod
 
class MyAbstractClass(ABC):
 
    def __init__(self, value):
        self.value = value
    
    @abstractmethod
    def do_something(self):
        print("abstract do something")
        
class A(MyAbstractClass):
    
    def do_something(self):
        super().do_something()
        
a = A(10)
a.do_something() #outputs abstract do something
Decorator

Decorators look like this:

@text

Class Methods

See the class section for more about the @classmethod, @staticmethod, and @property decorators.

Method As Decorator

Function A can be decorated with another function B. When A is run, it is actually passed as a parameter to function B, and function B can choose when/if to run it.


def myDecorator(f):
    def wrapper():
        print("Starting...")
        f()
        print("Ending...")
    return wrapper
    
def plainFunction():
    print("plain function")
    
x = myDecorator(plainFunction)
x()

 #outputs:
 #Starting...
 #plain function
 #Ending...

Simplified syntax:

def myDecorator(f):
    def wrapper():
        print("Starting...")
        f()
        print("Ending...")
    return wrapper

@myDecorator    
def plainFunction():
    print("plain function")
    
plainFunction()

 #outputs:
 #Starting...
 #plain function
 #Ending...

When would you use a decorator?
To time the length of functions
To apply regular logging
To wait before executing a function - rate limiting
To require a login for a function
To validate the arguments passed into a function
etc
Regular Expressions

Compile

Compile - compile an expression for reuse

import re
myRegEx = re.compile('ab*')

Compile has some optional flags

import re
myRegEx = re.compile('ab*', re.IGNORECASE)

Match

Match - check if regular expression matches text
Match will only find matches at the start of the string

import re
myRegEx = re.compile('ab*')
myMatch = myRegEx.match("ggttrr")
print(myMatch) #None

myMatch = myRegEx.match("abbbcab")
print(myMatch.group()) #'abbb' #the string that matched
print(myMatch.start()) #0 #index of start of match
print(myMatch.end()) #4 #index of first char NOT in match
print(myMatch.span()) #(0, 4) #tuple of start and end indexes

Search

Search - check if regular expression matches text
Search will find the first match anywhere in the string

import re
myRegEx = re.compile('ab*')
myMatch = myRegEx.search("ffabbbcab")
print(myMatch.group()) #'abbb' #the string that matched
print(myMatch.start()) #2 #index of start of match
print(myMatch.end()) #6 #index of first char NOT in match
print(myMatch.span()) #(2, 6) #tuple of start and end indexes

FindAll

FindAll - check if regular expression matches text
FindAll will return of list of all matches anywhere in the string

import re
myRegEx = re.compile('ab*')
myMatches = myRegEx.findall("ffabbbcab")
print(myMatches) #['abbb', 'ab'] #list of matched strings

FindIter

FindIter - like FindAll but you can iterate through the details of each match

import re
myRegEx = re.compile('ab*')
myMatches = myRegEx.finditer("ffabbbcab")
for myMatch in myMatches:
    print(myMatch.group())
    print(myMatch.start())
    print(myMatch.end())
    print(myMatch.span())

Back Reference

This example finds matches for "56xx3" where each x is the same digit.


import re
myRegEx = re.compile('56(?P<a>\d)(?=Pa)3')
print(myRegEx.match("56003")) #matches 56003

The phrase "(?P<a>\d)" matches a digit (\d) and names it "a" (?P<a>).

The phrase "(?=Pa)" matches the same substring that "a" matched.

Subprocess


import subprocess
cmdLine = r"c:\path\myProgram.exe"
myProcess = subprocess.Popen(cmdLine)
muProcess.wait() #wait until process completes

Numpy Array

Numpy is a core python library. It is very commonly shortened to "np".


import numpy as np

x = np.array([0, 1, 2, 3]) #create a rank 1 array

print(type(x)) #outputs <class 'numpy.darray'>
print(x) #outputs [0, 1, 2, 3]
print(x[0]) #outputs 0
print(x.shape) #outputs (4,) because the length is 4

y = np.array([[1, 2, 3], [10, 20, 30]]) #create a rank 2 array

print(y[1, 2]) #outputs 30
print(y.shape) #outputs (2, 3) because the outer length is 2, the inner length is 3

Create an array filled with default values.

import numpy as np

x = np.zeros((1, 2)) #creates [[0, 0]]
y = np.ones((2, 2)) #creates [[1, 1], [1, 1]]
z = np.full((3, 1), 7) #creates [[7], [7], [7]]

Create an array filled with random values.

import numpy as np

x = np.random.random((2, 2)) #creates [[a, b], [c, d]] where those are random values

Errors

Try Catch


try:
    print("hey")
except:
    print("error")
    
try:
    x = input("a number:")
except IOError:
    print("IO Error")
except TypeError as e:
    print("TypeError: " + str(e)) #prints the error message
except:
    print("some other error")

Raise Error


raise ValueError("message")

Convention says do not raise generic Exception

Error Types

IOError, ValueError, ImportError, EOFError, KeyboardInterrupt

Custom Errors

"Errors" all inherit from the base "Exception" class.
The naming convention is that all errors/exception Types end with "Error".

Define a custom error type:

class MyError(Exception):
    def __init__(self, expression, message):
        self.expression = expression
        self.message = message
Random

Seed random number generator with an integer or long.

import random

random.seed(5)
If you pass in None, the generator will seed with current time or something like it.

Get a random float from 0 inclusive to 1 exclusive.

import random

x = random.random()

Get a random integer from N to M, both inclusive.

import random

x = random.randint(0, 5)

Select a random value from a sequence.

import random

x = [1, 2, 3, 4, 5]
y = random.choice(x)

Randomly sort a sequence in place.
You can optionally provide your own random number generator function, provided it returns values [0, 1).

import random

x = [1, 2, 3, 4, 5]
random.shuffle(x)

random.shuffle(x, myRandomFunction)
DateTime

Using the datetime library.


import datetime

currentDateTime = datetime.datetime.now()
currentTime = currentDateTime.time()
Unit Tests

To create a unit test class, make a subclass of unittest.TestCase.
Each class method named with prefix "test" will be run as a test.

"setUp" is run before each test method.
"tearDown" is run after each test method, whether it succeeded or not.
"setUpClass" is run once before the class tests are run.
"tearDownClass" is run once after the class tests are run.

"unittest.main()" provides a command line interface to the tests.


import unittest

class MyTests(unittest.TestCase):
    def test_upper(self):
        self.assertEqual("foo".upper(), "FOO")
    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())
    def test_split(self):
        #test that split fails without separater
        s = 'hey oooh'
        with self.assertRaises(TypeError):
            s.split(2)
            
    def setUp(self):
        print("set up")
    def tearDown(self):
        print("tear down")
        
    def setUpClass(cls):
        print("set up class")
    def tearDownClass(cls):
        print("tear down class")

if __name__ == "__main__":
    unittest.main()

Asserts


assertEqual(a, b)
assertTrue(a == b)
assertFalse(a == b)

 #verify a specific error was raised
assertRaises(exception)

Command Line

You can run your tests in different ways.

The test case file contains the "unittest.main()" section, and will run itself.

python MyTests.py
(the ".py" is optional)

The test case file does not contain the "unittest.main()" section. This will run it.

python -m unittest MyTests.py

You can run multiple test files.

python -m unittest MyTestsA.py MyTestsB.py

You can run just one test class from a file the contains multiple classes.

python -m unittest MyTests.TestCustomer

You can run just one test method from a class.

python -m unittest MyTests.TestCustomer.test_payment

Options:
    -v verbose, more information shown
    -f fail fast, stop at first failed test
    -c catch, Control-C while tests run will (after current test completes) display results so far
    -b buffer, output is sent to a buffer to be saved
    --locals shows local variables in tracebacks
    
Test discovery: from the current directory, unittest will search here and below for tests to run

python -m unittest discover
If you don't add command options, then "discover" is optional.

Discover Options:
    -s specify a starting directory
    -p specify a pattern that the filename must match

Note that the file at path A/B/C.py will be imported as A.B.C    

Suite

Running unittest.main() will run all the tests in the file.
You can also build an explicit suite, and run just those tests.


import unittest

def suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTests("test_a"))
    suite.addTest(MyTests("test_b"))
    return suite
    
class MyTests(unittest.TestCase):
    def test_a(self):
        self.assertTrue(True)
    def test_b(self):
        self.assertTrue(True)
    def test_c(self):
        self.assertTrue(True)
        
if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(suite())

Skip

You can mark test methods to be skipped.
You can use the same syntax to mark an entire class to be skipped.


import unittest

class MyTests(unittest.TestCase):
    
    @unittest.skip("message")
    def test_a(self):
        self.assertTrue(True)
        
    @unittest.skipIf(myVersion < 4, "message")
    def test_b(self):
        self.assertTrue(True)
        
    @unittest.skipUnless(operatingSystem == "Windows", "message")
    def test_c(self):
        self.assertTrue(True)

You can conditionally skip a test at any point.

import unittest

class MyTests(unittest.TestCase):
    def test_a(self):
        if not resourceIsSetup:
            unittest.skip("message")
        self.assertTrue(True)

Expected Failure

You can mark a method as an expected failure. It will not be counted as a failure on the test result.


import unittest

class MyTests(unittest.TestCase):

    @unittest.expectedFailure
    def test_a(self):
        self.assertTrue(False)

Sub Test

You can run multiple tests within a loop.


import unittest

class MyTests(unittest.TestCase):
    def test_even(self):
        for number in range(0, 10, 2):
            with self.subTest(x=number):
                self.assertEqual(x%2, 0)
Each iteration will be reported as a unique test, specifying the arguments used.
Even if one iteration fails, all the iterations in the method will be run.

Pytest

run all tests recursive-within directory

pytest path/folder

run all tests in file

pytest path/folder/file.py

run all tests that contain this string in the method name

pytest path/folder/file.py::this_string

run all tests that contain this string in the method name, with these parameters filled in

pytest path/folder/file.py::this_string[4]

PyODBC

Microsoft's recommended python library for accessing SQL Server.

Install


pip install pyodbc
(Might need to run this at C:\Users\<YOUR NAME>\AppData\Local\Programs\Python\Python<VERSION>\Scripts)

Connect To Database

Using current user's Windows login:

import pyodbc 

connStr = (
    r"DRIVER={SQL Server Native Client 11.0};"
    r"SERVER=(localdb)\MSSQLLocalDB;"
    r"Database=MyDatabase;"
    r"Trusted_Connection=yes;")
print(connStr)
conn = pyodbc.connect(connStr, autocommit=True)

cursor = conn.cursor()
cursor.execute('SELECT ColumnA, ColumnB FROM dbo.myTable')

for row in cursor: #row is a Tuple
    print(row)

Errors:
- I got a login error when actually I had a typo in the database name.


--SQLExpress
    r"SERVER=(localdb)\MSSQLLocalDB;"
--SQL Server
    r"SERVER=localhost;"

ODBC Drivers

{SQL Server} - released with SQL Server 2000
{SQL Native Client} - released with SQL Server 2005 (also known as version 9.0)
{SQL Server Native Client 10.0} - released with SQL Server 2008
{SQL Server Native Client 11.0} - released with SQL Server 2012
{ODBC Driver 11 for SQL Server} - supports SQL Server 2005 through 2014
{ODBC Driver 13 for SQL Server} - supports SQL Server 2005 through 2016
{ODBC Driver 13.1 for SQL Server} - supports SQL Server 2008 through 2016
{ODBC Driver 17 for SQL Server} - supports SQL Server 2008 through 2017

UI PyQt

Python implementation of the Qt library.
Not sure about the license, but it looks like the free PyQt isn't available for commercial applications.
UI Tkinter

The standard python UI library.
A wrapper around Tcl/Tk

You can check that tkinter is properly installed on your system by running "python -m tkinter" from the command line; this should open a window demonstrating a simple Tk interface.

Basic

Open an empty window:

import tkinter

class Window(tkinter.Frame):
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.master.title("Example") #title displayed on window
        self.pack(fill=tkinter.BOTH, expand=1) #layout of window
        
root = tkinter.Tk()
root.geometry("400x300") #size of window
app = Window(root)
root.mainloop()

Add a button, quit the app:

import tkinter

class Window(tkinter.Frame):
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.master.title("Example")
        self.pack(fill=tkinter.BOTH, expand=1)
        self.init_layout()

    def init_layout(self):
        quitButton = tkinter.Button(self, text="Quit", command=self.client_quit)
        quitButton.place(x=10, y=20)
        
    def client_quit(self):
        exit()

root = tkinter.Tk()
root.geometry("400x300")
app = Window(root)
root.mainloop()

Add a main menu bar:

import tkinter

class Window(tkinter.Frame):
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.master.title("Example")
        self.pack(fill=tkinter.BOTH, expand=1)
        self.init_layout()

    def init_layout(self):
        menu = tkinter.Menu(self.master)
        self.master.config(menu=menu)
        file = tkinter.Menu(menu)
        file.add_command(label="Quit", command=self.client_quit)
        menu.add_cascade(label="File", menu=file)
        
    def client_quit(self):
        exit()

root = tkinter.Tk()
root.geometry("400x300")
app = Window(root)
root.mainloop()

Geometry Manager: Place

Place: arrange widgets by relative or absolute values


import tkinter

class Window(tkinter.Frame):
    
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.master.geometry("800x600")
        self.pack(fill=tkinter.BOTH, expand=1)
        self.initLayout()
        
    def initLayout(self):
        self.inputControl = tkinter.Text(self, height=20, width=30)
        self.inputControl.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) #center widget in parent
        
root = tkinter.Tk()
app = Window(root)
root.mainloop()


import tkinter

class Window(tkinter.Frame):
    
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.master.geometry("800x600")
        self.master.title("Calculator")
        self.pack(fill=tkinter.BOTH, expand=1)
        self.initLayout()
        
    def initLayout(self):
        self.inputControl = tkinter.Text(self, height=20, width=30)
        self.inputControl.place(x=10, y=10) #place absolute
        self.outputControl = tkinter.Text(self, height=20, width=30)
        self.outputControl.place(x=200, y=200) #place absolute
        
root = tkinter.Tk()
app = Window(root)
root.mainloop()

Geometry Manager: Grid

Grid: arrange widgets in a 2d table


import tkinter

class Window(tkinter.Frame):
    
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.master.geometry("800x600")
        self.master.title("Calculator")
        self.pack(fill=tkinter.BOTH, expand=1)
        self.initLayout()
        
    def initLayout(self):
        self.inputControl = tkinter.Text(self, height=10, width=20)
        self.inputControl.grid(row=0, column=0)
        self.outputControl = tkinter.Text(self, height=20, width=30)
        self.outputControl.grid(row=1, column=1)
        
root = tkinter.Tk()
app = Window(root)
root.mainloop()

Empty rows and columns are ignored.

Widgets default to centered in their cells.
To change that, add "sticky" option with one of these cardinal directions: N,S,E,W,NE,NW,SE,SW,NS,EW,NSEW etc.

self.inputControl = tkinter.Text(self, height=10, width=20).grid(row=0, column=0, sticky=tkinter.W)

Additional options:
columnspan=number of column cells to span
rowspan=number of row cells to span

If you call grid() with no row or column specified, then the column defaults to 0 and the row to the first currently empty row.

Methods:
    widget.grid_forget() #removes widget from grid layout
    
Stretch widgets to fill space:
- the important points here are
    (A) give a positive integer weight to any column or row you want to stretch
    (B) make the widget inside the cell sticky to the opposing walls

import parser
import tkinter
from tkinter import ttk

class Window(tkinter.Frame):
    
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.master.geometry("800x600")
        self.pack(fill=tkinter.BOTH, expand=1, padx=5, pady=5)
        self.init_layout()
    
    def init_layout(self):
        self.frame_history = tkinter.Frame(self)
        self.frame_history.grid(row=0, column=0, stick=tkinter.EW)
        self.columnconfigure(0, weight=1)

        tkinter.Label(self.frame_history, text="Input").grid(row=0, column=0, sticky=tkinter.EW)
        ttk.Separator(self.frame_history, orient=tkinter.VERTICAL).grid(row=0, column=1, stick=tkinter.NS)
        tkinter.Label(self.frame_history, text="Output").grid(row=0, column=2, sticky=tkinter.EW)
        self.frame_history.columnconfigure(0, weight=1)
        self.frame_history.columnconfigure(2, weight=1)

        self.input_equation = tkinter.Text(self, height=20, width=30)
        self.input_equation.grid(row=1, column=0, sticky=tkinter.EW)
        
        self.append_output("Test Input", "Test Output")
        
    def append_output(self, equation, result):
        row = 1
        output_equation = tkinter.Text(self.frame_history, height=1, width=30)
        output_equation.insert(tkinter.END, equation)
        output_equation.grid(row=row, column=0, sticky=tkinter.EW)
        ttk.Separator(self.frame_history, orient=tkinter.VERTICAL).grid(row=row, column=1, stick=tkinter.NS)
        output_result = tkinter.Text(self.frame_history, height=1, width=30)
        output_result.insert(tkinter.END, result)
        output_result.grid(row=row, column=2, sticky=tkinter.EW)

root = tkinter.Tk()
app = Window(root)
root.mainloop()

Geometry Manager: Pack

Pack
(do not use Grid and Pack in the same window)

Methods:
    widget.pack_forget() #removes widget from pack layout

todo

Events

widget.bind(event, handler)

You can call a global event handler (see examples below), or call self.handler to use a handler in your current class (in that case, the handler must have parameters for self and event).

Mouse left-click:

import tkinter

def callback(event):
    print("clicked at", event.x, event.y)

root = tkinter.Tk()
frame = tkinter.Frame(root, width=100, height=100)
frame.bind("<Button-1>", callback)
frame.pack()
root.mainloop()

Keyboard press:

import tkinter

def key(event):
    print("pressed", repr(event.char))

root = tkinter.Tk()
frame = tkinter.Frame(root, width=100, height=100)
frame.bind("<Key>", key)
frame.pack()
frame.focus_set()
root.mainloop()

Event names are formatted as "<modifier-type-detail>"
    <Button-1> = left mouse click
    <Button-2> = middle mouse click
    <Button-3> = right mouse click
(Motion and Release events will be delivered to the widget where the mouse button was depressed, even if you move the mouse outside of it)
    <B1-Motion> = mouse is moved with left button held down
    <ButtonRelease-1> = left mouse button released
    <Double-Button-1> = left mouse button double clicked (<Button-1> will still be called)
    <Triple-Button-1> = left mouse button triple clicked
    <MouseWheel> = mouse wheel moved
    <Enter> = cursor entered widget
    <Leave> = cursor exited widget
    <Motion> = cursor is moving and staying in the widget
    <FocusOut> = widget lost keyboard focus
    <Key> = a keyboard key was pressed
    x = the "x" keyboard key was pressed (pattern works with other keys)
    <KeyRelease> = a keyboard key was released after being pressed
    <Shift-Up> = shift key is held down while the up arrow key is pressed (pattern works with other keys)
    <Alt-Up> = alt key is held down while the up arrow key is pressed (pattern works with other keys)
    <Control-Up> = control key is held down while the up arrow key is pressed (pattern works with other keys)
    <Return> = the "Enter" key was pressed
Special keyboard key names: Return, space, less (for less than), Cancel (for break), BackSpace, Tab, Shift_L (for either shift), Control_L (for either control), Alt_L (for either alt), Pause, Caps_Lock, Escape, Prior (for page up), Next (for page down), End, Home, Left, Right, Up, Down, Print, Insert, Delete, F1 (and so on), Num_Lock, Scroll_Lock
    <Configure> = widget changed sized
    <Activate> = widget state became active
    <Deactivate> = widget state became deactive
    <Destroy> = widget is being destroyed
    <Expose> = widget is now visually visible after being covered by something else
    <Map> = widget is being made visible in the application (like when you call .grid())
    <Unmap> = widget is being made not visible in the application
    <Visibility> = some part of the application window has become visible on the screen

Event object attributes:
    widget = the actual widget object that generated the event
    x and y = current mouse position in pixels
    x_root and y_root = current mouse position relative to the whole screen
    char = (for keyboard events) the key pressed, as a string
    keysym = (for keyboard events) the key symbol pressed
    keycode = (for keyboard events) the key code pressed
    num = (for mouse events) the mouse button pressed
    width and height = (for configure events) the new dimensions of the widget
    type = the event type
    
Binding levels:
    1. widget instance (use bind)
    2. widget's root window (use bind)
    3. widget's class (use bind_class)
    4. the application (use bind_all)
If your bindings overlap (such as <Key> and <Enter>), the most specific one is chosen.
If you have the same bindings at multiple levels (such as instance and all), both will be called. The lower level will be called first.

How to disable a standard event, such as typing a newline in a text field? That default event is at the widget class level.

def ignore(event):
    return "break" #use this specific return value to cancel the propagation of the event
myText.bind("<Return>", ignore)

 #or

myText.bind("<Return>", lambda event: "break")

 #or, to actually block all <Return> events on all Text widgets
 #this is not suggested
top.bind_class("Text", "<Return>", lambda event: None)

Widgets

Methods and options general to all ui widgets.

Set focus on widget/control:

x = tkinter.Text(self)
x.focus_set()

Get real time widget dimensions:

print(widget.winfo_width(), widget.winfo_height())

For containers: a list of child widgets is in attribute "childen.values()"

Text

The Text control/widget: Text(parent, options...)

Remember that width and height are in character units. There is NO easy way to specify pixel units - everyone suggests setting the parent container size and packing the text into it.

Options:
bg=background color
bd=border width (default 2px)
cursor=hover cursor
exportselection=1 or 0 (default 1 allows user to copy text from control)
font=font object
fg=foreground (text) color (can be changed for sections of text)
height=number of rows of text
highlightbackground=color of highlights when widget is out of focus
highlightthickness=thickness of the highlight, default 1, set to 0 to hide highlight
insertbackground=color of "insert" cursor, default black
insertborderwidth=size of "insert" cursor border, default 0
insertofftime=milliseconds "insert" cursor blinks off per cycle, default 300
insertontime=milliseconds "insert" cursor blinks on per cycle, default 600
insertwidth=pixel width of "insert" cursor, deafult 2px (height is set to tallest char in line)
padx=internal padding on left and right in pixels, default 1px
pady=internal padding on top and bottom in pixels, default 1px
relief=3D appearance of widget, default SUNKEN, can be SUNKEN, RAISED, GROOVE, RIDGE, FLAT
selectbackground=background color of selected text
selectborderwidth=pixel width of border around selected text
spacing1=extra vertical space above lines, default 0, does not affect a wrapped line
spacing2=extra vertical space above wrapped lines, default 0
spacing3=extra vertical space below lines, default 0, does not affect a wrapped line
spacing4=extra vertical space below wrapped lines, default 0
state=NORMAL(responds to keyboard and mouse events) DISABLED(does not, also cannot edit programmaticly)
tabs=?something about tab characters?
width=width measured in characters
wrap=WORD(wrap line on word break) CHAR(wrap line on any character)
xscrollcommand=?used to add a horizonal scroll?
yscrollcommand=?used to add a vertical scroll?

Methods:
delete(startIndex [,endIndex]) #deletes a range of text
get(startIndex [,endIndex]) #gets a range of text
index(index) ???
insert(index, string) #insert text at index
see(index) #returns true if the text at this index is visible
mark_set("insert", index) #move cursor to an index in the text

Indexes: there are many ways to index a Text widget
    Line index starts at 1
    Character index within a line starts at 0

print(text.get("1.0")) #prints the character from line 1 at array-index 0

 #or
index = "%d.%d" % (line, column)
print(text.get(index))

print(text.get(tkinter.CURRENT)) #character just before the cursor

print(text.get("1.0", tkinter.END)) #first line of text

todo: see more indexing at http://effbot.org/tkinterbook/text.htm

Label


import tkinter

class Window(tkinter.Frame):
    
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.master.geometry("800x600")
        self.pack(fill=tkinter.BOTH, expand=1)
        self.initLayout()
        
    def initLayout(self):
        tkinter.Label(self, text="Input").grid(row=0, column=0)
        tkinter.Label(self, text="Output").grid(row=0, column=1)
        
root = tkinter.Tk()
app = Window(root)
root.mainloop()

options:
    anchor = the cardinal direction to justify the text to: N,S,W,E
    justify = ? not sure how this works with anchor: LEFT, RIGHT

Separator

A vertical or horizontal 2px wide separator.
Make sure to set the "sticky" option, otherwise the separator will be 1px long.


import tkinter
from tkinter import ttk

class Window(tkinter.Frame):
    
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.master.geometry("800x600")
        self.pack(fill=tkinter.BOTH, expand=1, padx=5, pady=5)
        self.init_layout()
    
    def init_layout(self):
        tkinter.Label(self, text="Input").grid(row=0, column=0, sticky=tkinter.W)
        ttk.Separator(self, orient=tkinter.VERTICAL).grid(row=0, column=1, stick=tkinter.NS)
        tkinter.Label(self, text="Output").grid(row=0, column=2, sticky=tkinter.W)

root = tkinter.Tk()
app = Window(root)
root.mainloop()

Scrollbar

Scrollbars can only be associated with a few widget types: List, Textbox, Canvas, and Entry

To use scrollbars with other widgets, or layouts of widgets, the usually solution seems to be to create a scrolling canvas, embed one frame in the canvas, and embed your other widgets in the frame. Make sure the dimensions of the frame are fed into the canvas "scrollregion" option.


from tkinter import *

root=Tk()

frame=Frame(root,width=300,height=300)
frame.grid(row=0,column=0)

canvas=Canvas(frame,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))

hbar=Scrollbar(frame,orient=HORIZONTAL)
hbar.pack(side=BOTTOM,fill=X)
hbar.config(command=canvas.xview)

vbar=Scrollbar(frame,orient=VERTICAL)
vbar.pack(side=RIGHT,fill=Y)
vbar.config(command=canvas.yview)

canvas.config(width=300,height=300)
canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=True,fill=BOTH)

root.mainloop()

General use scrolling canvas - put anything inside the "inner_frame":

import tkinter
from tkinter import ttk

class ScrollFrame(tkinter.Frame):

    def __init__(self, master):
        tkinter.Frame.__init__(self, master)
        self.master = master

        self.canvas = tkinter.Canvas(self)
        self.canvas.grid(row=0, column=0, sticky=tkinter.NS)
        #self.canvas.grid_propagate(False) #don't let the inner canvas resize ?
        
        self.scrollbar = tkinter.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.scrollbar.grid(row=0, column=1, sticky=tkinter.NS)
        self.canvas.configure(yscrollcommand=self.scrollbar.set)

        self.inner_frame = tkinter.Frame(self.canvas)
        self.inner_frame.grid(row=0, column=0, sticky=tkinter.NS)

        self.canvas.columnconfigure(0, weight=1)
        self.canvas.rowconfigure(0, weight=1)

        self.canvas.create_window(0, 0, window=self.inner_frame, anchor='nw')
        self.update_scroll_region()
        
    def update_scroll_region(self):
        self.master.update() #make sure the layout is completely calculated
        self.canvas.configure(scrollregion=self.canvas.bbox("all")) #get a bounding box around all child widgets
full example:

import parser
import tkinter
from tkinter import ttk

class ScrollFrame(tkinter.Frame):

    def __init__(self, master):
        tkinter.Frame.__init__(self, master)
        self.master = master

        self.canvas = tkinter.Canvas(self)
        self.canvas.grid(row=0, column=0, sticky=tkinter.NS)
        #self.canvas.grid_propagate(False) #don't let the inner canvas resize ?
        
        self.scrollbar = tkinter.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.scrollbar.grid(row=0, column=1, sticky=tkinter.NS)
        self.canvas.configure(yscrollcommand=self.scrollbar.set)

        self.inner_frame = tkinter.Frame(self.canvas)
        self.inner_frame.grid(row=0, column=0, sticky=tkinter.NS)

        self.canvas.columnconfigure(0, weight=1)
        self.canvas.rowconfigure(0, weight=1)

        self.canvas.create_window(0, 0, window=self.inner_frame, anchor='nw')
        self.update_scroll_region()
        
    def update_scroll_region(self):
        self.master.update() #make sure the layout is completely calculated
        self.canvas.configure(scrollregion=self.canvas.bbox("all")) #get a bounding box around all child widgets

class Window(tkinter.Frame):
    
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.master.geometry("800x600")
        self.master.title("Python Calculator")
        self.pack(fill=tkinter.BOTH, expand=1, padx=5, pady=5)
        self.init_data()
        self.init_layout()
    
    def init_data(self):
        self.list_equations = []
        self.list_results = []
        self.index_history = None
        
    def init_layout(self):
        self.scroll_frame = ScrollFrame(self)
        self.scroll_frame.grid(row=0, column=0, sticky=tkinter.NS)
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
    
        self.frame_history = tkinter.Frame(self.scroll_frame.inner_frame)
        self.frame_history.grid(row=0, column=0, sticky=tkinter.EW)
        tkinter.Label(self.frame_history, text="Input", anchor=tkinter.W).grid(row=0, column=0, sticky=tkinter.EW)
        ttk.Separator(self.frame_history, orient=tkinter.VERTICAL).grid(row=0, column=1, stick=tkinter.NS)
        tkinter.Label(self.frame_history, text="Output", anchor=tkinter.W).grid(row=0, column=2, sticky=tkinter.EW)
        self.frame_history.columnconfigure(0, weight=1)
        self.frame_history.columnconfigure(2, weight=1)
        self.input_equation = tkinter.Text(self.scroll_frame.inner_frame, height=3, width=30, relief=tkinter.FLAT)
        self.input_equation.grid(row=1, column=0, sticky=tkinter.EW)
        self.input_equation.focus_set()
        
        for i in range(0, 25):
            self.append_output("aaaaaaa", "bbbbbbb")
        
    def append_output(self, equation, result):
        self.list_equations.append(equation)
        self.list_results.append(result)
        row = len(self.list_equations) + 1
        output_equation = tkinter.Text(self.frame_history, height=1, width=30, relief=tkinter.FLAT)
        output_equation.insert(tkinter.END, equation)
        output_equation.config(state=tkinter.DISABLED)
        output_equation.grid(row=row, column=0, sticky=tkinter.EW)
        ttk.Separator(self.frame_history, orient=tkinter.VERTICAL).grid(row=row, column=1, stick=tkinter.NS)
        output_result = tkinter.Text(self.frame_history, height=1, width=30, relief=tkinter.FLAT)
        output_result.insert(tkinter.END, result)
        output_result.config(state=tkinter.DISABLED)
        output_result.grid(row=row, column=2, sticky=tkinter.EW)
        self.scroll_frame.update_scroll_region()

root = tkinter.Tk()
app = Window(root)
root.mainloop()

Scroll to top of area:

scollable_widget.yview_moveto(0)

Scroll to bottom of area:

scollable_widget.yview_moveto(1)

Font

Python 3 uses the tkinter.font library.
You must create your root window (tkinter.Tk()) before you create your font.


import tkinter
from tkinter import font

root = tkinter.Tk()
default_font = font.Font(family="Helvetica", size=10, weight="normal")


UI WxPython

Python implementation of the wxWidgets library. See the Phoenix Project if using Python 3.
Images - Pillow

Pillow library (fork of PIL Python Image Library). Pillow and PIL cannot be installed at the same time.

[Installation Instructions]

Getting Started

From existing image:

from PIL import Image
 
img = Image.open('myImage.png')

Create new image:

from PIL import Image
 
img = Image.new(mode = 'RGB', size = (60, 30), color = 'red')
img.save('../output/test.png')
"size" is tuple (width, height)
"color" sets the initial background color

Mode

1 - (1-bit pixels, black and white, stored with one pixel per byte)
L - (8-bit pixels, black and white)
P - (8-bit pixels, mapped to any other mode using a color palette)
RGB - (3x8-bit pixels, true color)
RGBA - (4x8-bit pixels, true color with transparency mask)
CMYK - (4x8-bit pixels, color separation)
YCbCr - (3x8-bit pixels, color video format) Note that this refers to the JPEG, and not the ITU-R BT.2020, standard
LAB - (3x8-bit pixels, the L*a*b color space)
HSV - (3x8-bit pixels, Hue, Saturation, Value color space)
I - (32-bit signed integer pixels)
F - (32-bit floating point pixels)

Lines


from PIL import Image, ImageDraw

image = Image.new('RGB', (800,600), 'white')
draw = ImageDraw.Draw(image)
draw.line([(0,0),(100,400)], fill='gray', width=1)
image.save('../output/test.png')
The list of point-tuples can be as long as you need.

Anti-Aliasing

There's not much support for anti-aliasing.

One suggestion: The only way to do it natively is with supersampling. Render your image at a multiple of the size you require, then resize it with filter=Image.ANTIALIAS.


Images - ImageIO

[imageio Home Page]

Save Animated GIF

Must start with all "frames" saved to file system.


    using imageio

    #imageFilenames is a list of filenames for the "frames"
    images = []
    for imageFilename in imageFilenames:
        images.append(imageio.imread(imageFilename))
    imageio.mimsave(saveToFilename, images, fps=framesPerSecond)
PyLint

Enable

PyLint will only run on *.py files in a folder that contains an __init__.py file.

Disable Warnings

The {warning-type} will be printed at the end of each PyLint warning in the report, such as (invalid-name) or (consider-using-set-comprehension).

to disable a warning type for a whole file, place these at the top of the file

Pylint: Disable={warning-type}

to disable a warning on one statement, place this at the end of the lin

x = my_function()  # pylint: disable={warning-type}