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)
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;
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.
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
'''
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
import sys
sys.exit()
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
import really________long_______name as shortName
import module.submodule.subsubmodule as otherShortName
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()
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"
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)
x = 5
y = "hey"
print(x) #5
print(y) #"hey"
print(str(x)+y) #"5hey"
print(f'{x} {y}') #"5 hey"
Variables are not declared. They are created when they are first assigned.
Begin with a letter or underscore
Continue with letters, numbers, or underscores
Names are case-sensitive
Only start with an underscore for special variables
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
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.
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.
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
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 Types: List, Set, Dict
Immutable Types: Integer, Float, Complex, Boolean, String, Tuple, Frozenset
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
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
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
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)]
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
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"
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
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}
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])
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 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
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"
Python has not maximum size on integers.
Collections have a maximum size.
import sys
print(sys.maxsize)
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
Python implicitly supports very large integers.
To use very precise floating point numbers:
import decimal
decimal.getcontext().prec = 100
x = decimal.Decimal(y - z)
Switch is not supported in Python.
Keywords "continue" and "break" and "pass" are supported.
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 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
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 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
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"]
Operator: arithmetic and logical symbols
Operand: the values the operator uses
+ 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 ( )
- 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 ( )
==
!=
>
<
>=
<=
not
and (also written &)
or (also written |)
You can separate logical phrases with parentheses ( )
!=
>
<
>=
<=
not
and (also written &)
or (also written |)
You can separate logical phrases with parentheses ( )
=
+=
-=
*=
/=
//=
%=
**=
+=
-=
*=
/=
//=
%=
**=
& and
| or
^ xor (when only one is true)
~ not
<< shift left
>> shift right
| or
^ xor (when only one is true)
~ not
<< shift left
>> shift right
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 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"
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)
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]
Return a list of string-names of all the methods and properties of an object
x = dir(myObject)
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 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()
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)
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 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
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.
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
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 is overwriting the behavior of a function or object after it is defined. Do not do this.
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)
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"]
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 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.
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.
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
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)
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
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 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"
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"
(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)
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__
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
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)
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.
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
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
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
Decorators look like this:
@text
See the class section for more about the @classmethod, @staticmethod, and @property decorators.
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
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 - 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 - 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 - 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 - 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())
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.
import subprocess
cmdLine = r"c:\path\myProgram.exe"
myProcess = subprocess.Popen(cmdLine)
muProcess.wait() #wait until process completes
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
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 ValueError("message")
Convention says do not raise generic Exception
IOError, ValueError, ImportError, EOFError, KeyboardInterrupt
"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
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)
Using the datetime library.
import datetime
currentDateTime = datetime.datetime.now()
currentTime = currentDateTime.time()
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()
assertEqual(a, b)
assertTrue(a == b)
assertFalse(a == b)
#verify a specific error was raised
assertRaises(exception)
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
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())
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)
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)
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.
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]
Microsoft's recommended python library for accessing SQL Server.
pip install pyodbc
(Might need to run this at C:\Users\<YOUR NAME>\AppData\Local\Programs\Python\Python<VERSION>\Scripts)
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;"
{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
Python implementation of the Qt library.
Not sure about the license, but it looks like the free PyQt isn't available for commercial applications.
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.
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()
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()
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()
Pack
(do not use Grid and Pack in the same window)
Methods:
widget.pack_forget() #removes widget from pack layout
todo
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)
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()"
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
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
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()
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)
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")
Python implementation of the wxWidgets library. See the Phoenix Project if using Python 3.
Pillow library (fork of PIL Python Image Library). Pillow and PIL cannot be installed at the same time.
[Installation Instructions]
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
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)
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.
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.
[imageio Home Page]
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 will only run on *.py files in a folder that contains an __init__.py file.
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
to disable a warning on one statement, place this at the end of the lin
x = my_function() # pylint: disable={warning-type}