Intro
Welcome!
This is a small quick-reference work which is intended to help students who are taking Python classes and need an easily navicable reference to look at.
It is not intended to teach Python itself, but rather to help students with reminding themselves of certain concepts.
The concepts are also not in any particular order, although concepts which are usually introduced earlier in a Python class are more towards the top of the list.
Simpler concepts may also be left out entirely— for instance, there is no explanation in this reference for simple algebra, or an explanation for what a file is. However, there is a Vocabulary section.
If you find any errors, or have any suggestions, file an issue report at my Codeberg repository: https://codeberg.org/zbwells/yapr
- zbwells
Logic
To start with, here is a list of the logical comparison operators provided by Python:
==- 'is equal to'
!=- 'is not equal to'
>- 'is greater than'
>=- 'is greater than or equal to'
<- 'is less than'
<=- 'is less than or equal to'
In addition to these standard logical comparison operators, there are some natural language-derived ones:
in- 'contains'
not in- 'does not contain'
And each of the aforementioned operators can be combined with continuous logic:
andor
All of which works in the tradional sense that logic works in high-level programming. Here are some examples.
a = 5
b = 5
c = (a == b)
c is equal to True, because a is equal to b.
Boolean logic can be used anywhere any other kind of valid expression can be used, but its most common usage is in if statements.
if c == True:
print("c is True")
if c:
print("c is True")
if (a == b) == c:
print("c is True")
All of these are the same. If a variable is equal to True, there is no need to compare it to itself unless you would like to check for the inverse. Additionally, all values besides 0 and False are regarded as True in a conditional statement.
if c and a:
print("c and a are True")
# Even though a is actually 5
if a and b:
print("a and b are True")
# Even though a is 5 and b is 5
d = (0 or False or c)
# Still resolves to True
If statements can be chained together, nested, or used in any of the other usual ways standard for procedural languages.
if b or a:
if a and b:
if c:
print(c)
print(a, b)
print(b, a)
elif can be used to prevent further if statements in a chain from executing if one is True.
x = True
y = False
if y:
print(y)
elif x:
print(x)
else:
print(y, x)
And else can be used to define what will happen if none of the other statements trigger.
r = True
g = True
b = False
mystery = (r == True and (g == False or b == False) or r == False)
What is the value of mystery?
Loops
for Loops
Many programming languages support 'numeric' for loops, which work based on a range of conditions— usually whether or not a number is less than or greater to another value— and each run through the loop, test that value to see whether or not the loop should continue.
Python, on the other hand, only has 'generic' for loops, in which each run-through of the loop sees the loop iterating through a sequence of some sort.
for i in range(10):
print(i)
The above will print numbers from 0 to 9.
Keep in mind that range() is partially non-inclusive— which is to say that list(range(2, 6)) actually generates a list of [2, 3, 4, 5] when called, excluding the final digit.
Iterating through a string:
for ch in "string":
print(ch.upper(), end=" ")
S T R I N G
Iterating through a list:
items = ["food", "water", "shelter", "garbage"]
for obj in items;
if len(obj) > 6:
print(obj)
The above will print any items which have more than 6 characters, which happens to be "shelter" and "garbage".
It is also possible to iterate over multiple sequences at the same time, as well as to iterate over sequences within sequences.
list1 = [1, 2, 3, 4, 5]
list2 = [5, 4, 3, 2, 1]
for num1, num2, in zip(list1, list2):
print(num1 + num2)
zip(list1, list2) will transform list1 and list2 as a list of tuples, with each pair of values being their own tuple: [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)]. This allows for taking each value while iterating through each tuple.
while Loops
Python also features while loooping, which works as it does in other languages.
x = 0
while x < 10:
print(x)
x += 1
This is equivalent to the for loop featured earlier; it will print numbers from 1 to 9.
stuff = ""
num = 65
while 'Z' not in stuff:
stuff += chr(num)
num += 1
print(stuff)
This will print every ASCII character from A-Z:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Choosing Syntax
Which looping construct should you use? In the end, it is entirely up to you; however, you may find that some situations will be easier to handle using one or the other.
Avoiding Unnecessary Looping
There are some cases where a loop may seem like a good idea:
a = [1, 3, 5, 7]
for num in a:
if num == 5:
print(num)
When in reality, Python contains other features which make performing the same task easier— making looping either redundant or an inferior method to perform the task.
a = [1, 3, 5, 7]
if 5 in a:
print(5)
Math
Math can be used as part of any expression in Python, in the same way math might be used in mathematics outside of computing.
twenty = 10 * 2
fourty = twenty * 2
two = fourty / twenty
List of mathematical operators:
+- Addition
-- Subtraction
*- Multiplication
/- Division
**- Exponential
%- Modulus (Divide integers and return remainder)
//- Floor Division (Divide and round down)
Example of exponential calculation:
one_hundred = 10 ** 2
Example of modulus:
one = 13 % 4
# 4 goes into 13 three times, and 1 is the remainder
Example of floor division:
four = 9//2
# 9/2 is 4.5, rounded down is 4
Python expressions follow standard algebraic order of operations, but best practice is to put parentheses around different operations to make the order more obvious.
thirty_three = (5 + 6) * (2 + 1)
If you are unsure of how any of the mathematical operators in Python work, open the interpreter and try as many things as you have to until they make sense.
Lists
Lists are a way to organize data in such a way that the data can later be accessed sequentially or directly via indexing. Data can later be modified or utilized as necessary.
The syntax for defining a list is as follows:
list1 = [1, 2, 3, 4, 5]
Lists can contain variables of the same type, or they can contain variables of different types:
list2 = ['a', 1, 3.1]
The data stored in a list can be accessed via the data's corresponding "index", which is a number corresponding to a point of data in the list, numbered starting from 0.
This means that list1[1] would contain 2, and list2[0] would contain 'a'.
list2[5] would be out of range, despite the list containing 5 points of data.
Lists can also be intialized as empty lists, meanign they don't yet contain anything.
list3 = []
To add to a list, either another list can be added to the previous list with the + operator, or the .append() list method can be used.
list3.append('a')
list3 += ['b'])
print(list3)
['a', 'b']
append adds a value to the end of the list; to add a value to the beginning of the list, or anywhere else for that matter, insert can be used.
list3.insert(0, 'c')
print(list3)
['a', 'b', 'c']
To transform other data types into lists, use list().
Other List-like Sequences
Tuples
Tuples are like lists, but immutable. They cannot be modified after assignment, although the tuple itself can be reassigned as a different variable as with any other variable. Desipte their name, they can hold more than two points of data.
tup = (1, 2)
tup2 = ('a', 'b', 4, 5)
To transform other data types into tuples, use tuple().
Sets
Sets are like lists, but are unordered, only store unique data, and are not subscriptable. This means that individual points of data cannot be accessed without first transforming the set into a list or another kind of subcriptable sequence.
set1 = {1, 2, 3, 4, 5}
set1[3] would result in a TypeError exception being thrown. See Errors section for information on exceptions.
Sets can be appended using the .add() set method, as follows:
set1.add(5)
This would do nothing, because set1 as shown further above already contains 5.
set1.add(6)
This would result in set1 containing {1, 2, 3, 4, 5, 6}.
To transform other data types into sets (in the process eliminating repeat values), use set().
Other
Dictionaries and Strings are also sequences, but are different enough from lists to warrant their own pages. Also, Lists can be 'sliced'. Check the Strings section for information on how to slice sequences.
Strings
Strings provide a way to store text data (words) in variables, and through them, perform operations on that text.
string = "test"
text = "more words"
# Empty string, which can be added to later
more_text = ""
more_text += "wind"
more_text += "storm"
more_text will contain "windstorm", as more_text now contains "wind" + "storm" which acts in an intuitive sort of manner; the words are concatenated together.
To transform other kinds of data into strings, use str(). Note that when used on other kinds of sequences (Lists, Tuples, etc), str() returns a string containing that sequence, rather than a string containing the contents of that sequence.
String Slicing
Strings can also be 'sliced', which is difficult to explain in words:
string = "thunderstorm!"
string[1:4] will contain "hun" — letters 1-3.
string[-1:] will contain "!" — the last letter in the string.
string[-5:] will contain "torm!" — the last 5 letters in the string.
string[:-1] will contain "thunderstorm" — the string but with the last letter removed.
string[:-5] will contain "thunders" — the string but with the last 5 letters removed.
string[::2] will contain "tudrtr!" — the string but with every other letter removed.
string[::-1] will contain "!mrotsrednuht" — the string, but backwards.
It is likely evident that slicing is a very powerful tool, though a tad bit unintuitive. Slicing is actually a part of the subscripting syntax, and isn't unique to Strings; meaning that Lists and Tuples can also be sliced, although there may be fewer situations in which slicing is useful for other data types.
String Methods
For a full list of methods relating to the String data type, see https://docs.python.org/3/library/stdtypes.html#str.
lower
print("abc".upper())
print("ABC".lower())
ABC
abc
endswith
print("hurray!".endswith('!'))
True
isalpha
print("word".isalpha())
True
isnumeric
print("12345".isnumeric())
True
join
join is a particularly important String method, because it allows other sequences to be transformed and concatenated into strings. For instance:
list1 = ['a', 'b', 'c']
string1 = "".join(list1)
string1 now contains "abc".
join takes the first string provided and concatenates each string in the list of strings provided together, putting the first string inbetween each of them.
words = ["There is a place", "over there", "with mushrooms"]
inter = " - "
inter.join(words)
"There is a place - over there - with mushrooms"
Effectively, this means that any List method can also be used on a string, by transforming the string into a list with list(), and then retransforming the list back into a string using "".join().
Of course, this can get a little bit ridiculous, and some times there is an objectively better way to do things.
word_to_reverse = "university"
reversed_word = ''.join(list(reversed(list(word_to_reverse))))
Yes, reversed_word will now contain "ytisrevinu", but string slicing is much easier!
reversed_word = [::-1]
Errors
An exception is an event triggered when something goes wrong.
Error handling in Python is primarily done via exception handling, which is exceedingly easy to use and figure out. This is accomplished via 'try' and 'except', as follows.
try:
open("nothing", 'r')
except:
print("File not found.")
open() will throw an exception if the file 'nothing' is not found.
The problem with this is that there are many different kinds of exceptions which open() could throw, and some of them may not warrant the same response as others. This is solved by telling except which exception to handle case by case.
List of these built-in exceptions: https://docs.python.org/3/library/exceptions.html.
try:
open("nothing", 'r')
except FileNotFoundError:
print("Cannot open file: file not found")
except IsADirectoryError:
print("Cannot open file: file is a directory")
except PermissionError:
print("Cannot open file: permission denied")
except:
print("Cannot open file: file cannot be accessed")
Usually instead of just leaving a catch-all except statement at the end, it is better to let the exception occur if the exception is hard to forsee and you can't be bothered to properly make a handler for it.
This same strategy can be applied across all situations in which errors may occur. It is, however, bad practice to obscure off-by-one errors with exceptions, or to obscure errors by handling exceptions that happen due to poor design. Readability must be kept in mind at all times.
Built-In Functions
Functions such as print() and str() are examples of built-in functions.
There are a number of these functions which are built-in to Python's standard library which may have usage that is not immediately obvious.
print() is an extremely common one.
print()
print("Stuff")
print() can be combined with string formatting to place variables amongst other parts of a string.
x = 5
y = 10
print("The value of x is %i and y is %i" % (x, y))
Using the Python interpreter, you can query help pages by using the help() function. Using help() standalone will output this:
>>> help()
Welcome to Python 3.9's help utility!
Writing print while in the context of the help utility will output:
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
help(print) can be used to display the same help page. This can be done for any function which has a help page-- which includes every built-in function.
Typing help() and then builtins will show you all of the built-in exceptions, methods, and functions Python has-- which turns out to be thousands of pages worth of them. It it practically impossible to keep even half of Python's builtins in your head at any one time-- which speaks to the importance of documentation in general. This quick-reference does not strive to explain all of them, or even a quarter.
Here is a quick overview of some common built-in functions:
List
list() can be used to transform any kind of sequence into a list object; see section on Lists.
tuple1 = (1, 2, 3, 4, 5)
a = list(tuple1)
a is now equal to [1, 2, 3, 4, 5], which is a list.
Similarly, tuple() can b eused to make any kind of sequence into a tuple.
b = tuple(a)
And now b is equal to tuple1.
Range
range() can be used to generate a sequence (iterable object, technically) from a certain number to another number, with explicitly defined steps.
range_var1 = range(1, 11, 2)
list(range_var1) will contain [1, 3, 5, 7, 9].
range_var2 = range(5, 100)
list(range_var2) will contain [5, 99].
Length
len() can be used to determine the length of sequences, or any object which has a defined length.
lis = [5, 4, 3, 2, 1]
lislen = len(lis)
lislen will be 5. lis's final index is 4, because the indexes start from 0.
Min/Max
min() and max() can be used to find the greatest or least value in a sequence.
nums = [1, 2, 3, 4, 55, 6]
mn = min(nums)
mx = max(nums)
mn will be 1, and mx will be 55.
Ord/Chr
ord() and chr() can be used to find the ordinal and character values of characters and numbers respectively.
A = chr(65)
sixty_five = ord('A')
And hex() can be used to get the hexadecimal value of an integer.
oh_ex_forty_one = hex(ord('A'))
print("%s is the ascii value of 'A' in hexadecimal." % oh_ex_forty_one)
Reversed
reversed() can be used to reverse a sequence, but it will remain a 'reversed object', until otherwise read.
rev = list(reversed("berserk"))
rev will resolve to ['k', 'r', 'e', 's', 'r', 'e', 'B']
Instance
isinstance() can be used to check the type of data in a variable.
inst1 = isinstance(34, int)
inst2 = isinstance('a', float)
inst1 will return True, and inst2 and then False.
There are a plethora of other built-in functions as aforementioned-- before re-implementing a seemingly simple feature, make sure that Python doesn't provide another way to perform the function already.
See: https://docs.python.org/3/library/functions.html
Functions
Functions are an integral part of any Python program, helping piece together all of the operations in a readable and non-redundant manner.
Arguably, redunancy is the primary reason for functions to exist. Programs can be built with all of the code in a line, with no functions whatsoever, but these sorts of programs are difficult to write, difficult to read, and difficult to modify.
It is for this reason that even the most basic of languages contain functions: for when the same thing needs to be done more than one time.
Here is a basic function definition which does nothing:
def nothing():
return
From this, we can add operations to our function:
def printwords():
print("words")
Or parameters, which are variables provided to the function when it is called:
def printwords(words):
print(words)
A function can be run by calling it with its name, followed by (), with function parameters inside the parentheses.
printwords("words")
"words"
These are functions which don't return any values from them; by adding return within a function followed by a value, a function will evaluate to a value when it is called:
def one_arg_one_ret(num):
x = num + num
return (x, x+x)
When run, this function will return a tuple containing twice the number provided in the arguments list, and that number twice itself.
integer = 10
val = one_arg_one_ret(integer)
print(val)
(20, 40)
Whether or not a variable passed to a function is directly modifiable within the function, or simply a copy of the variable passed to the function, depends on the type of data the variable is.
def change_str(var):
var = var + "end"
string = "the "
change_str(string)
If we now check the string variable, it will still contain "the ".
def change_list(var):
var = var.append("end")
str_list = ["the "]
change_list(str_list)
If we check the str_list variable after calling change_list, it will contain ["the ", "end"], despite the variable not explicitly being changed.
If this seems confusing, that's because it is. There is a document called The Zen of Python which has some wisdom for us.
Explicit is better than implicit.
Simple is better than complex.
Generally, it is good practice to avoid functions which implicitly modify variables outside of the scope of the actual function.
Recursion
Recursion refers to recursive functions; functions which call themselves. Recursion can be used in a similar manner to looping. However, in Python, recursion is generally less useful than loops.
To start, Python has a built-in recursion depth that prevents recursion from going to far— this is to prevent run-away recursion from resultin gin a crash, memory overuse, or both.
def infinite(num):
print(num, end='\r')
infinite(num+1)
If the function calls itself too many times, it will arbitrarily be terminated, and an exception will be thrown.
try:
infinite(5)
except RecursionError:
print("Maximum recursion depth exceeded.")
That being said, there are some cases where recursion makes more sense than alternative methods of performing certain tasks.
def factorial(n):
if n > 1:
return n * factorial(n-1)
else:
return 1
print(factorial(5))
120
A looping factorial function, by comparison, might look like this:
def factorial(n):
product = 1
for i in range(1, n+1):
product *= i
return product
print(factorial(5))
120
In practice, recursion usually turns out to be completely optional. There are almost always different, if not better ways to do things.
Files
File handling is fairly simple in Python, as many pre-existing components of the language can be used to interface with external files present in a filesystem.
Say our file "foobar" contains these lines:
foo
bar
You can open the "foobar" file like so::
open_file = open("foobar")
And print its contents out like so:
for line in open_file:
print(line, end="")
foo
bar
Which means that the file can be treated as if it is a list of strings, containing each line in the file.
print("".join(open_file))
foo
bar
While a file object is not mutable the same way a list is, an open file can be modified if the write flag is enabled.
If you want to write to a file, it must be opened with a writable 'mode'. To add to the end of a pre-existing file, use the 'a' flag.
open_file_out = open("foobar", 'a')
File Opening Codes
r- read file, error if doesn't exist
w- write, create if doesn't exist, overwrite if file exists
w+- write or read, overwrite file if it exists
r+- read or write, starting from top of existing file
a- append to end of file, create if doesn't exist
a+- append or read, create if doesn't exist
x- write, error if file already exists
And to each of these, options can be added modifiers:
t- read as ascii text
b- read as binary (everything that isn't ascii text, more or less)
If we want to create a new file which contains non-text data, something like following would work.
new_file = open("exec", "xb")
The default value is 'rt'.
File objects are written to the same ways regardless of what mode they are opened with.
With the file.write() method:
open_file_out.write("\n123\n345\n")
Because the file is opened with the a flag, the string will be written to the end of the file, and the original file contents will not be overwritten.
print("Words", file=open_file_out)
print() contains a named argument 'file' which can be used to specify what file will be printed to. The default is (effectively, not literally) stdout, which is usually the display terminal interface the script is currently being run on.
Ways to write multiple lines to a file sequentially without a loop:
lines = ["one", "two", "three"]
File.writelines() function:
open_file_out.writelines(lines)
Alternatively, print()
print('\n', *lines, end="\n", sep="", file=open_file_out)
# recall that *<listname> expands a list as if it were separated by commas
A file won't actually be written to until it is closed, so close() must be used.
open_file_out.close()
Now, to read it:
open_file = open("foobar", "r")
print(*open_file, sep='', end='')
open_file.close()
foo
bar
123
345
Words
onetwothree
onetwothree
And to reset it to what it was before running all this code:
open_file = open("foobar", "w")
open_file.writelines(["foo\n", "bar"])
open_file.close()
User Input
Obtaining runtime user input in Python is fairly simple, and is done via built-in functions.
input() returns a string, with the trailing newline removed from the input.
print("Enter some data: ", end='')
data = input()
data now contains a string, provided by the user.
If you would like the user to enter a specific kind of data, something like this can be used.
try:
data_int = int(input("Enter an integer: "))
except ValueError:
print("Error: input was not an integer")
exit(1)
Or something like this, if you would like to ask continuously for input until the input is valid.
complete = False
while not complete:
try:
data_int2 = int(input("Enter another integer: "))
complete = True
except ValueError:
print("Please enter an integer.")
If the ValueError exception is thrown, the loop will be run again, and the input will also be asked for once more.
A standard 'yes or no' question might look like this:
yes_no = input("Would you like to print out the results of the the previous inputs? [Y/n] ")
while yes_no not in ['y', 'Y', 'n', 'N']:
yes_no = input("Sorry, response '%s' not understood. [Y/n] " % yes_no)
And of course, once the input is properly received, it can be handled like any other data.
if yes_no in ['Y', 'y']:
all_data = (data, data2, data_int, data_int2)
print(*all_data)
Dictionaries
Dictionaries are a kind of sequence which contains named key-pairs. It is a bit like having a list of named variables.
ages = { "Bob" : 50, "Ruth" : 28, "Chris" : 32, "Charlie" : 9 }
Dictionaries can be accessed in a similar way to lists, albeit using the keyname in place of a numeric index.
print(ages["Bob"])
50
They can also be modified in a similar way.
ages["Ruth"] += 1
print(ages["Ruth"])
29
They can be iterated through like a list as well, while also iterating through keys.
for k,v in ages.items():
print(k, v)
Bob 50
Ruth 29
Chris 32
Charlie 9
However, by default, only keys are iterated through:
for k in ages:
print(k, ages[k])
Dictionaries can also be appended by defining another key any time after initial declaration:
ages["Blues"] = 90
print(list(ages.items()))
[('Bob', 50), ('Ruth', 29), ('Chris', 32), ('Charlie', 9), ('Blues', 90)]
An easy way to pass a large amount of data into a function without great confusion is to turn it into a dictionary. In addition, dictionaries can be returned from functions to get the result of multiple calculations from a single function.
def similarity_table(*nums):
truths = {}
for num in nums:
comparisons = [num == i for i in nums]
truths[str(num)] = comparisons
return truths
print(similarity_table(1, 1, 2, 2, 3, 4, 5, 6))
This will output a dictionary including each number provided to the function, along with whether or not it is the same as each other number in the parameters list:
{'1': [True, True, False, False, False, False, False, False], '2': [False, False, True, True, False, False, False, False], '3': [False, False, False, False, True, False, False, False], '4': [False, False, False, False, False, True, False, False], '5': [False, False, False, False, False, False, True, False], '6': [False, False, False, False, False, False, False, True]}
When in doubt about how to organize data, a dictionary is likely a good idea.
Types
Every variable in Python has a type attached to it. Python is a dynamically typed language, so every variable can change type depending on what data is actually contained in the variable.
int
one = 1
string
garbage = "garbage"
float
one_point_one = 1.1
In actuality, this is more complicated than it seems.
Python does not have data typing in the traditional sense that languages have types; instead, all 'data types' are actually objects of a certain class.
For instance, 1 will evaluate to an object of class 'int', and "garbage" will evaluate to an object of class 'string'. This can be verified with the built-in type() function.
print(type(1))
<class 'int'>
print(type("garbage"))
<class 'str'>
print(type({1, 2, 3}))
<class 'set'>
print(type((1, 2, 3,)))
<class 'tuple'>
print(type([1, 2, 3]))
<class 'list'>
print(type({'one': 1}))
<class 'dict'>
It is not completely necssary to understand every detail behind how Python actually handles data in order to be proficient with it, but if you would like to investigate further, here are some links:
Lambda Functions
Lambda functions are one-line, anonymous functions containing a single expression, which is evaluated when the function is called.
The actual lambda expression itself returns a function object, and can be passed as an argument to other functions, or assigned to a variable, just like any other function.
# lambda [parameters]: expression
add = lambda *x: sum(x)
print(add(4, 5, 10, 15))
This will display 34.
The function add() defined above is a variable which points to the otherwise anonymous function lambda *x: sum(x), which takes any number of arguments, and creates a list out of them within the function. The full syntax equivalent of this is:
def add(*x):
return sum(x)
Which is exactly the same, although it may not appear to be. The lambda function above is actually returning the results of sum(x) after evaluating it, but is doing so implicitly, whereas the full version explicitly returns sum(x).
It is also quite useful to pass lambda functions to map() and filter, as they take function objects as arguments and use them to perform calculations based on the provided functions. For a further explanation of these functions, see the Iterators section.
test_list = [1, 2, 3, 4, 5]
new_list = list(map(lambda i: (i**2), test_list))
print(new_list)
While the actual usage of lambda is fairly limited, they can be used to avoid more potentially complicated syntax. Their main advantage is of course, that they can be placed in places that actual functions cannot be placed.
Theoretically, an entire program can be composed entirely of lambda functions, but that would likely be impractical given the wealth of other features provided by Python.
Comprehensions
List Comprehensions are usually considered an advanced topic, and most introductory programming students are not required to understand them despite their relative simplicity. However, they are very useful for preventing nesting and as situational alternatives to loops. When used properly, they can provide both readability and conciseness.
num_list = [1, 2, 3, 6, 8, 10, 50, 40, 35, 25, 27, 23, 93, 97, 104]
If you would like to create a list containing only the even numbers of another list, there are a few options:
You could use a for loop:
new_list = []
for num in num_list:
if num % 2 == 0:
new_list.append(num)
Which will result in new_list containing [2, 6, 8, 10, 50, 40, 104].
Alternatively, you could use a list comprehension:
new_list = [num for num in num_list if num % 2 == 0]
Which will result in new_list containing the same thing, but accomplished in one line.
There is a recurring format for list comprehensions which is as follows:
[expression for element in list if condition]
example_of_comp = [i*5 for i in range(50) if i % 2 != 0]
In the example above, i*5 is the expression, i is the element, and range(50) is the list. Conditionals can be added at the end of the comprehension in order to exclude or include particular values from the new list. The expression component, as previously mentioned, can be used to modify values once they are taken from the old list.
nested = [[1, 2, 3]] * 3
unnested = [num for seq in nested for num in seq]
The above is an example of a double list comprehension, where the first comprehension is used in the second comprehension. Nested comprehensions are also possible, but generally lack readability if nested beyond two layers.
nested_comp = [num % 65 for seq in [[x*2 for x in range(100)]]*5 for num in seq]
sum(nested_comp)
Understanding all that goes on in this list comprehension is nearly impossible thanks to its complicated one line nature. Nested list comprehensions shoudl generally be avoided— but in the end it is up to the developer.
def count_uppercase_chars_in_str(strings):
# Create a string containing each character in the file by combining each line using string.join()
valid_chars = "".join(strings)
# Create a list containing each character in the previous string, but removing non-uppercase chars
uppercase_chars = [char for char in valid_chars if char.isupper()]
# The length of the list of uppercase chars is the number of uppercase cars present in the file
number_of_chars = len(uppercase_chars)
return number_of_chars
strings = ["one", "two", "Three", "Four", "Five"]
charcount = count_uppercase_chars_in_str(strings)
print("There are %i uppercase characters in `strings`." % (charcount))
There are 3 uppercase characters in `strings`.
The syntax in this examples also hold true in the case of generator expressions, set comprehensions, etc. See the Iterators section for more information on generator expressions.
Classes
In Python, classes are how data types are defined. This means that defining a Class in Python is synonymous with creating a custom data type.
The purpose and supposed benefits of object orientation and classes is beyond the scope of this reference; suffice to say that if you would like to look into this more, or get a refresher on some OOB premises, https://python-textbok.readthedocs.io/en/1.0/Classes.html may be helpful.
And for a full list of double underlined built-in class methods, see https://docs.python.org/3/reference/datamodel.html#basic-customization.
Basic Class Definition
With that said, here is a class which just contains some data:
class Pet:
def __init__(self, age, weight, gender):
self.age = age
self.weight = weight
self.gender = gender
To access data in the class, you have to create an instance of said class.
pet1 = Pet(5, 50)
print(pet1.weight)
print(pet1.age)
50
5
The self portion of the class definition is not a keyword, but rather a self referencing identifier which points to the object itself after it is created.
Inheritance
If we wanted to include the Pet class's parameters in a new class:
class Cat(Pet):
def animalType(self):
return "Cat"
An instance of this class will inherit the variables listed in Pet, and once created, will also inherit the methods found in the parent (if there were any.)
joe = Cat(5, 30, "Male")
print(joe.animalType())
Cat
There are multiple built-in methods innate to Python classes which can be reimplemented in user-defined classes. These can be used to add extra features to the class, and interface with other built-in functions.
class Dog(Pet):
def __init__(self, age, weight, gender, name, breed="Unknown")
# Calling parent class's init method inside new class
super().__init__(age, weight)
self.name = name
self.breed = breed
def __repr__(self):
return "Dog: %s, %s, age %s" % (self.name, self.gender, self.age)
def __dir__(self):
return map(str, [self.age, self.weight, self.name, self.gender, self.breed])
The built-ins repr() and dir() can now be used to get a string representation or a directory listing of the class.
bob = Dog(10, 70, "Bob", "Male")
print(repr(bob))
print(dir(bob))
Dog: Male, Bob, age 10
['10', '70', 'Bob', 'Male', 'Unknown']
Be careful when using inheritance— most of the time, it serves only to make things more complicated. The super() function can be used to access parent-class specific data while within the class definition, and the parent class's init can be called inside the derived class, but both of these things only serve to make using the actual objects a more confusing and tiring venture.
Another Example
class Beeper:
def __init__(self):
self.counter = 0
self.volume = 0
def beep(self):
if self.volume > 5:
print("Beep.")
if self.volume > 10:
print("BEEP!")
else:
print("beep")
self.counter += 1
The values of counter and volume will change the outcome of beep().
alarm = Beeper()
alarm.beep()
alarm.volume = 5
alarm.beep()
print("Alarm went off", alarm.counter, "times.")
beep
Beep.
beep
Alarm went off 2 times.
Object as an Iterable
class InfiniteSet:
def __init__(self):
self.num = 0
# Called to determine what is being iterated through:
def __iter__(self):
return self
# Called to determine what the data should look like in sequence:
def __next__(self):
self.num += 1
return self.num
for i in InfiniteSet():
if i <= 500000:
print(i, end='r')
else:
print()
break
The above will display every number from 0 to 500000 on one line, and then stop at 500000 and continue on to the next line.
500000
These are only the basics— classes and object orientation are extremely complicated topics, and Python uses them profusely, and in somewhat different ways than languages such as C++ tend to do. For more information about how data is handled in Python, see https://docs.python.org/3/reference/datamodel.html#objects-values-and-types
Iterators
Iterators are objects which can be iterated over (looped through, etc.). Strings, lists, dictionaries, generators, and any other type which provides an interface for iteration (see Classes) can be converted to iterators, and effectively are converted automatically in many scenarios.
string = "abcdef"
for ch in string:
print(ch, end='')
In the above case, the string variable is acting as an iterator.
Generators
Generators "generate" a new value every time they're run, and so function as iterators. This can be helpful when holding an entire list of values in memory is not feasible.
def infinity():
num = 0
while True:
yield num
num += 1
for i in infinity():
if i <= 500000:
print(i, end='\r')
else:
break
This will print every number from 0 to 500000, with each line replacing the last. infinity() is effectively an infinite set.
Obviously, instead of generating numbers from 1-500000, a function could return a list of values containing that large range of numbers; something like:
nums = list(range(0, 500000))
But this requires storing each number individually in memory, which for larger sets of data can become problematic. range() itself acts a bit like a generator, although in reality it is of its own class of objects.
nums = range(0, 500000)
This means that working with a range() object is almost always better than working with a static list of numbers.
nums = (num**2 for num in range(10))
Generator expressions are created in the same way as List Comprehensions, but they create generators instead of lists.
print(next(nums))
print(next(nums))
25
36
Any iterator object (or object which has implemented __next__()) can use next() to travel through each value; to explicitly transform a sequence into an iterator, use iter(). This is essentially what happens during a for-loop, but done implicitly.
Map
Mappers can be used to automatically perform operations on iterable objects and return a new copy, without actually having to manually loop through them. map() returns a map object, is both iterable and an iterator, but is not a sequence.
stuff = ['a', 'b', 'c']
ascii_stuff = map(ord, stuff) # Convert everything to its ascii value
print(*ascii_stuff)
97 98 99
Additionally, map objects can be transformed into lists or other kinds of sequences:
ascii_stuff = list(map(ord, stuff))
print(ascii_stuff)
[97, 98, 99]
When combined with lambda functions, this can be used to do the same thing as comprehensions, but with different syntax.
string = "mississippi"
capital_string = "".join([ch.upper() for ch in string])
print(capital_string)
MISSISSIPPI
string = "missouri"
capital_string = "".join(map(lambda ch: ch.upper(), string))
print(capital_string)
MISSOURI
Of course, the previous two examples are overkill in the sense that the same results could be achieved by doing string.upper() by itself— but if for some you reason wanted to change each letter into its uppercase version one by one, maps and comprehensions are both viable methods.
Filter
string = "aardvark"
string_no_vowels = "".join(filter(lambda ch: ch not in "aeiou", string))
print(string_no_vowels)
rdvrk
filter() passes each value in an iterable through a function— if that function returns True, it keeps that data in the iterable— if False, it deletes that data from the iterable.
It can be handled more or less identically to map(), but with a different purpose.
Mastery of iteration concepts in Python is not exactly paramount to effectively use the language, but for many developers, mapping functions and generators will likely provide more preferable ways to perform a plethora of tasks.
External Resources
Python: https://www.python.org
Python Library Reference: https://docs.python.org/3/library/index.html
Python Setup and Usage: https://docs.python.org/3/using/index.html
Python Glossary: https://docs.python.org/3/glossary.html
Python Wiki: https://wiki.python.org/moin/
Vocabulary
For anything not in here, which is most things, try Python's glossary: https://docs.python.org/3/glossary.html
-
Interpreter
- A program which interprets instructions given either via command line or via external source file(s), and performs commands based on the instructions given. Python is an interpreted programming language. Interpreter programs can be built to parse all sorts of things.
-
Compiler
- A program which parses instructions given via text source files, then compiles, assembles, and links the compiled object files together such that they can work as an executable understandable natively by the computer. Each compiler for each compiled language works differently. The GNU C Compiler (GCC) is ubiquitous and is possibly the most common and influential compiler for the C language, along with its counterpart, the GNU C++ Compiler (G++).
-
Statement
- A snippet of code that does something, but does not evaluate to a value of any kind.
-
Expression
- A snippet of code that does nothing by itself (is normally paired with expressions), and evaluates to a value.
-
Function
- A block of code which can be called (often with supplied external data, called 'arguments') by another piece of code, which in turn performs operations, sometimes returning data for use elsewhere in the program.
-
Loop
- A block of code generally containing two components: a condition, and a body of code which runs a number of times until the condition is met. There are a plethora of different types of loops in most languages, with a few examples being while loops, generic for loops, and numeric for loops. The exact specifications of looping constructs within each programming language vary greatly, and some languages feature no explicit syntax at all, and instead some other feature of the language can be repurposed into a loop.
-
Recursion
- High-level languages which contain functions can often contain functions which call themselves— this can be used to loop in some form.
-
Class
- A language construct which can be used to store operations and data simultaneously, of which the data can be referred to as "program state", and the operations as "methods". By creating an instance of a class, called an "object", programs can be organized in granular chunks which are theoretically easier to organize when working with teams of people. There are varying opinions on whether or not this works as intended.
-
Object
- An instance of a class, through which all of the properties and methods in the class can be interacted.
-
High-level languages
- Programming languages which are far removed from the actual machine code understood by computers, with the intention for the languages to be more easily understood by humans. C is generally considered the father of modern high-level programming languages, despite originating at the beginning of the 1970s. Python is a high-level programming language.
-
Low-level languages
- Languages which humans nowadays would not actually program in, such as processor assembly, and the actual machine language itself.