# Single line comments start with a number symbol.
""" Multiline strings can be written using three "s, and
are often used as documentation."""
###################################################### 1.
Primitive Datatypes and
Operators####################################################
# You have numbers3 # => 3
# Math is what you would expect1 + 1 # => 28 - 1 # => 710
* 2 # => 2035 / 5 # => 7.0
# Floor division rounds towards negative infinity5 // 3
# => 1-5 // 3 # => -25.0 // 3.0 # => 1.0 # works on
floats too-5.0 // 3.0 # => -2.0
# The result of division is always a float10.0 / 3 # =>
3.3333333333333335
# Modulo operation7 % 3 # => 1# i % j have the same sign as
j, unlike C-7 % 3 # => 2
# Exponentiation (x**y, x to the yth power)2**3 # => 8
# Enforce precedence with parentheses1 + 3 * 2 # => 7(1 +
3) * 2 # => 8
# Boolean values are primitives (Note: the capitalization)True
# => TrueFalse # => False
# negate with notnot True # => Falsenot False # => True
# Boolean Operators# Note "and" and "or" are case-
sensitiveTrue and False # => FalseFalse or True # => True
# True and False are actually 1 and 0 but with different
keywordsTrue + True # => 2True * 8 # => 8False - 5 #
=> -5
# Comparison operators look at the numerical value of True and
False0 == False # => True2 > True # => True2 == True
# => False-5 != False # => True
# None, 0, and empty strings/lists/dicts/tuples/sets all
evaluate to False.# All other values are Truebool(0) # =>
Falsebool("") # => Falsebool([]) # => Falsebool({})
# => Falsebool(()) # => Falsebool(set()) # =>
Falsebool(4) # => Truebool(-6) # => True
# Using boolean logical operators on ints casts them to
booleans for evaluation,# but their non-cast value is
returned. Don't mix up with bool(ints) and bitwise# and/or
(&,|)bool(0) # => Falsebool(2) # => True0 and 2 # =>
0bool(-5) # => Truebool(2) # => True-5 or 0 # => -5
# Equality is ==1 == 1 # => True2 == 1 # => False
# Inequality is !=1 != 1 # => False2 != 1 # => True
# More comparisons1 < 10 # => True1 > 10 # => False2 <= 2 #
=> True2 >= 2 # => True
# Seeing whether a value is in a range1 < 2 and 2 < 3 # =>
True2 < 3 and 3 < 2 # => False# Chaining makes this look
nicer1 < 2 < 3 # => True2 < 3 < 2 # => False
# (is vs. ==) is checks if two variables refer to the same
object, but == checks# if the objects pointed to have the same
values.a = [1, 2, 3, 4] # Point a at a new list, [1, 2, 3,
4]b = a # Point b at what a is pointing tob is a
# => True, a and b refer to the same objectb == a #
=> True, a's and b's objects are equalb = [1, 2, 3, 4] #
Point b at a new list, [1, 2, 3, 4]b is a # =>
False, a and b do not refer to the same objectb == a
# => True, a's and b's objects are equal
# Strings are created with " or '"This is a string."'This is
also a string.'
# Strings can be added too"Hello " + "world!" # => "Hello
world!"# String literals (but not variables) can be
concatenated without using '+'"Hello " "world!" # => "Hello
world!"
# A string can be treated like a list of characters"Hello
world!"[0] # => 'H'
# You can find the length of a stringlen("This is a string")
# => 16
# Since Python 3.6, you can use f-strings or formatted string
[Link] = "Reiko"f"She said her name is {name}." # =>
"She said her name is Reiko"# Any valid Python expression
inside these braces is returned to the string.f"{name} is
{len(name)} characters long." # => "Reiko is 5 characters
long."
# None is an objectNone # => None
# Don't use the equality "==" symbol to compare objects to
None# Use "is" instead. This checks for equality of object
identity."etc" is None # => FalseNone is None # => True
###################################################### 2.
Variables and
Collections###################################################
#
# Python has a print functionprint("I'm Python. Nice to meet
you!") # => I'm Python. Nice to meet you!
# By default the print function also prints out a newline at
the end.# Use the optional argument end to change the end
[Link]("Hello, World", end="!") # => Hello, World!
# Simple way to get input data from consoleinput_string_var =
input("Enter some data: ") # Returns the data as a string
# There are no declarations, only assignments.# Convention in
naming variables is snake_case stylesome_var = 5some_var # =>
5
# Accessing a previously unassigned variable is an exception.#
See Control Flow to learn more about exception
handling.some_unknown_var # Raises a NameError
# if can be used as an expression# Equivalent of C's '?:'
ternary operator"yay!" if 0 > 1 else "nay!" # => "nay!"
# Lists store sequencesli = []# You can start with a prefilled
listother_li = [4, 5, 6]
# Add stuff to the end of a list with [Link](1) #
li is now [1][Link](2) # li is now [1, 2][Link](4)
# li is now [1, 2, 4][Link](3) # li is now [1, 2, 4, 3]#
Remove from the end with [Link]() # => 3 and li is
now [1, 2, 4]# Let's put it [Link](3) # li is now
[1, 2, 4, 3] again.
# Access a list like you would any arrayli[0] # => 1# Look
at the last elementli[-1] # => 3
# Looking out of bounds is an IndexErrorli[4] # Raises an
IndexError
# You can look at ranges with slice syntax.# The start index
is included, the end index is not# (It's a closed/open range
for you mathy types.)li[1:3] # Return list from index 1 to 3
=> [2, 4]li[2:] # Return list starting from index 2 => [4,
3]li[:3] # Return list from beginning until index 3 => [1,
2, 4]li[::2] # Return list selecting elements with a step
size of 2 => [1, 4]li[::-1] # Return list in reverse order =>
[3, 4, 2, 1]# Use any combination of these to make advanced
slices# li[start:end:step]
# Make a one layer deep copy using slicesli2 = li[:] # => li2
= [1, 2, 4, 3] but (li2 is li) will result in false.
# Remove arbitrary elements from a list with "del"del li[2] #
li is now [1, 2, 3]
# Remove first occurrence of a [Link](2) # li is now
[1, 3][Link](2) # Raises a ValueError as 2 is not in the
list
# Insert an element at a specific [Link](1, 2) # li
is now [1, 2, 3] again
# Get the index of the first item found matching the
[Link](2) # => [Link](4) # Raises a ValueError
as 4 is not in the list
# You can add lists# Note: values for li and for other_li are
not [Link] + other_li # => [1, 2, 3, 4, 5, 6]
# Concatenate lists with "extend()"[Link](other_li) # Now
li is [1, 2, 3, 4, 5, 6]
# Check for existence in a list with "in"1 in li # => True
# Examine the length with "len()"len(li) # => 6
# Tuples are like lists but are [Link] = (1, 2,
3)tup[0] # => 1tup[0] = 3 # Raises a TypeError
# Note that a tuple of length one has to have a comma after
the last element but# tuples of other lengths, even zero, do
[Link]((1)) # => <class 'int'>type((1,)) # => <class
'tuple'>type(()) # => <class 'tuple'>
# You can do most of the list operations on tuples toolen(tup)
# => 3tup + (4, 5, 6) # => (1, 2, 3, 4, 5, 6)tup[:2]
# => (1, 2)2 in tup # => True
# You can unpack tuples (or lists) into variablesa, b, c = (1,
2, 3) # a is now 1, b is now 2 and c is now 3# You can also
do extended unpackinga, *b, c = (1, 2, 3, 4) # a is now 1, b
is now [2, 3] and c is now 4# Tuples are created by default if
you leave out the parenthesesd, e, f = 4, 5, 6 # tuple 4, 5,
6 is unpacked into variables d, e and f# respectively such
that d = 4, e = 5 and f = 6# Now look how easy it is to swap
two valuese, d = d, e # d is now 5 and e is now 4
# Dictionaries store mappings from keys to valuesempty_dict =
{}# Here is a prefilled dictionaryfilled_dict = {"one": 1,
"two": 2, "three": 3}
# Note keys for dictionaries have to be immutable types. This
is to ensure that# the key can be converted to a constant hash
value for quick look-ups.# Immutable types include ints,
floats, strings, tuples.invalid_dict = {[1,2,3]: "123"} # =>
Yield a TypeError: unhashable type: 'list'valid_dict =
{(1,2,3):[1,2,3]} # Values can be of any type, however.
# Look up values with []filled_dict["one"] # => 1
# Get all keys as an iterable with "keys()". We need to wrap
the call in list()# to turn it into a list. We'll talk about
those later. Note - for Python# versions <3.7, dictionary key
ordering is not guaranteed. Your results might# not match the
example below exactly. However, as of Python 3.7, dictionary#
items maintain the order at which they are inserted into the
[Link](filled_dict.keys()) # => ["three", "two",
"one"] in Python <3.7list(filled_dict.keys()) # => ["one",
"two", "three"] in Python 3.7+
# Get all values as an iterable with "values()". Once again we
need to wrap it# in list() to get it out of the iterable. Note
- Same as above regarding key#
[Link](filled_dict.values()) # => [3, 2, 1] in Python
<3.7list(filled_dict.values()) # => [1, 2, 3] in Python 3.7+
# Check for existence of keys in a dictionary with "in""one"
in filled_dict # => True1 in filled_dict # => False
# Looking up a non-existing key is a
KeyErrorfilled_dict["four"] # KeyError
# Use "get()" method to avoid the
KeyErrorfilled_dict.get("one") # =>
1filled_dict.get("four") # => None# The get method
supports a default argument when the value is
missingfilled_dict.get("one", 4) # =>
1filled_dict.get("four", 4) # => 4
# "setdefault()" inserts into a dictionary only if the given
key isn't presentfilled_dict.setdefault("five", 5) #
filled_dict["five"] is set to 5filled_dict.setdefault("five",
6) # filled_dict["five"] is still 5
# Adding to a dictionaryfilled_dict.update({"four":4}) # =>
{"one": 1, "two": 2, "three": 3, "four": 4}filled_dict["four"]
= 4 # another way to add to dict
# Remove keys from a dictionary with deldel filled_dict["one"]
# Removes the key "one" from filled dict
# From Python 3.5 you can also use the additional unpacking
options{"a": 1, **{"b": 2}} # => {'a': 1, 'b': 2}{"a": 1,
**{"a": 2}} # => {'a': 2}
# Sets store ... well setsempty_set = set()# Initialize a set
with a bunch of values.some_set = {1, 1, 2, 2, 3, 4} #
some_set is now {1, 2, 3, 4}
# Similar to keys of a dictionary, elements of a set have to
be immutable.invalid_set = {[1], 1} # => Raises a TypeError:
unhashable type: 'list'valid_set = {(1,), 1}
# Add one more item to the setfilled_set =
some_setfilled_set.add(5) # filled_set is now {1, 2, 3, 4,
5}# Sets do not have duplicate elementsfilled_set.add(5) # it
remains as before {1, 2, 3, 4, 5}
# Do set intersection with &other_set = {3, 4, 5, 6}filled_set
& other_set # => {3, 4, 5}
# Do set union with |filled_set | other_set # => {1, 2, 3, 4,
5, 6}
# Do set difference with -{1, 2, 3, 4} - {2, 3, 5} # => {1,
4}
# Do set symmetric difference with ^{1, 2, 3, 4} ^ {2, 3, 5}
# => {1, 4, 5}
# Check if set on the left is a superset of set on the
right{1, 2} >= {1, 2, 3} # => False
# Check if set on the left is a subset of set on the right{1,
2} <= {1, 2, 3} # => True
# Check for existence in a set with in2 in filled_set # =>
True10 in filled_set # => False
# Make a one layer deep copyfilled_set = some_set.copy() #
filled_set is {1, 2, 3, 4, 5}filled_set is some_set #
=> False
###################################################### 3.
Control Flow and
Iterables####################################################
# Let's just make a variablesome_var = 5
# Here is an if statement. Indentation is significant in
Python!# Convention is to use four spaces, not tabs.# This
prints "some_var is smaller than 10"if some_var > 10:
print("some_var is totally bigger than 10.")elif some_var
< 10: # This elif clause is optional.
print("some_var is smaller than 10.")else:
# This is optional too.
print("some_var is indeed 10.")
"""For loops iterate over listsprints: dog is a mammal
cat is a mammal mouse is a mammal"""for animal in ["dog",
"cat", "mouse"]:
# You can use format() to interpolate formatted strings
print("{} is a mammal".format(animal))
""""range(number)" returns an iterable of numbersfrom zero up
to (but excluding) the given numberprints: 0 1 2
3"""for i in range(4):
print(i)
""""range(lower, upper)" returns an iterable of numbersfrom
the lower number to the upper numberprints: 4 5 6
7"""for i in range(4, 8):
print(i)
""""range(lower, upper, step)" returns an iterable of
numbersfrom the lower number to the upper number, while
incrementingby step. If step is not indicated, the default
value is [Link]: 4 6"""for i in range(4, 8, 2):
print(i)
"""Loop over a list to retrieve both the index and the value
of each list item: 0 dog 1 cat 2 mouse"""animals =
["dog", "cat", "mouse"]for i, value in enumerate(animals):
print(i, value)
"""While loops go until a condition is no longer [Link]:
0 1 2 3"""x = 0while x < 4:
print(x)
x += 1 # Shorthand for x = x + 1
# Handle exceptions with a try/except blocktry:
# Use "raise" to raise an error
raise IndexError("This is an index error")except
IndexError as e:
pass # Refrain from this, provide a
recovery (next example).except (TypeError, NameError):
pass # Multiple exceptions can be
processed [Link]: # Optional clause
to the try/except block. Must follow
# all except blocks.
print("All good!") # Runs only if the code in try raises
no exceptionsfinally: # Execute under all
circumstances
print("We can clean up resources here")
# Instead of try/finally to cleanup resources you can use a
with statementwith open("[Link]") as f:
for line in f:
print(line)
# Writing to a filecontents = {"aa": 12, "bb": 21}with
open("[Link]", "w") as file:
[Link](str(contents)) # writes a string to a
file
import jsonwith open("[Link]", "w") as file:
[Link]([Link](contents)) # writes an object to a
file
# Reading from a filewith open("[Link]") as file:
contents = [Link]() # reads a string from a
fileprint(contents)# print: {"aa": 12, "bb": 21}
with open("[Link]", "r") as file:
contents = [Link](file) # reads a json object
from a fileprint(contents)# print: {"aa": 12, "bb": 21}
# Python offers a fundamental abstraction called the
Iterable.# An iterable is an object that can be treated as a
sequence.# The object returned by the range function, is an
iterable.
filled_dict = {"one": 1, "two": 2, "three": 3}our_iterable =
filled_dict.keys()print(our_iterable) # => dict_keys(['one',
'two', 'three']). This is an object
# that implements our Iterable interface.
# We can loop over [Link] i in our_iterable:
print(i) # Prints one, two, three
# However we cannot address elements by index.our_iterable[1]
# Raises a TypeError
# An iterable is an object that knows how to create an
iterator.our_iterator = iter(our_iterable)
# Our iterator is an object that can remember the state as we
traverse through# it. We get the next object with
"next()".next(our_iterator) # => "one"
# It maintains state as we [Link](our_iterator) # =>
"two"next(our_iterator) # => "three"
# After the iterator has returned all of its data, it raises
a# StopIteration exceptionnext(our_iterator) # Raises
StopIteration
# We can also loop over it, in fact, "for" does this
implicitly!our_iterator = iter(our_iterable)for i in
our_iterator:
print(i) # Prints one, two, three
# You can grab all the elements of an iterable or iterator by
call of list().list(our_iterable) # => Returns ["one", "two",
"three"]list(our_iterator) # => Returns [] because state is
saved
###################################################### 4.
Functions####################################################
# Use "def" to create new functionsdef add(x, y):
print("x is {} and y is {}".format(x, y))
return x + y # Return values with a return statement
# Calling functions with parametersadd(5, 6) # => prints out
"x is 5 and y is 6" and returns 11
# Another way to call functions is with keyword
argumentsadd(y=6, x=5) # Keyword arguments can arrive in any
order.
# You can define functions that take a variable number of#
positional argumentsdef varargs(*args):
return args
varargs(1, 2, 3) # => (1, 2, 3)
# You can define functions that take a variable number of#
keyword arguments, as welldef keyword_args(**kwargs):
return kwargs
# Let's call it to see what happenskeyword_args(big="foot",
loch="ness") # => {"big": "foot", "loch": "ness"}
# You can do both at once, if you likedef all_the_args(*args,
**kwargs):
print(args)
print(kwargs)"""all_the_args(1, 2, a=3, b=4) prints:
(1, 2) {"a": 3, "b": 4}"""
# When calling functions, you can do the opposite of
args/kwargs!# Use * to expand args (tuples) and use ** to
expand kwargs (dictionaries).args = (1, 2, 3, 4)kwargs = {"a":
3, "b": 4}all_the_args(*args) # equivalent:
all_the_args(1, 2, 3, 4)all_the_args(**kwargs) #
equivalent: all_the_args(a=3, b=4)all_the_args(*args,
**kwargs) # equivalent: all_the_args(1, 2, 3, 4, a=3, b=4)
# Returning multiple values (with tuple assignments)def
swap(x, y):
return y, x # Return multiple values as a tuple without
the parenthesis.
# (Note: parenthesis have been excluded but
can be included)
x = 1y = 2x, y = swap(x, y) # => x = 2, y = 1# (x, y) =
swap(x,y) # Again the use of parenthesis is optional.
# global scopex = 5
def set_x(num):
# local scope begins here
# local var x not the same as global var x
x = num # => 43
print(x) # => 43
def set_global_x(num):
# global indicates that particular var lives in the global
scope
global x
print(x) # => 5
x = num # global var x is now set to 6
print(x) # => 6
set_x(43)set_global_x(6)"""prints: 43 5 6"""
# Python has first class functionsdef create_adder(x):
def adder(y):
return x + y
return adder
add_10 = create_adder(10)add_10(3) # => 13
# Closures in nested functions:# We can use the nonlocal
keyword to work with variables in nested scope which shouldn't
be declared in the inner [Link] create_avg():
total = 0
count = 0
def avg(n):
nonlocal total, count
total += n
count += 1
return total/count
return avgavg = create_avg()avg(3) # => 3.0avg(5) #
(3+5)/2 => 4.0avg(7) # (8+7)/3 => 5.0
# There are also anonymous functions(lambda x: x > 2)(3)
# => True(lambda x, y: x ** 2 + y ** 2)(2, 1) # => 5
# There are built-in higher order functionslist(map(add_10,
[1, 2, 3])) # => [11, 12, 13]list(map(max, [1, 2, 3],
[4, 2, 1])) # => [4, 2, 3]
list(filter(lambda x: x > 5, [3, 4, 5, 6, 7])) # => [6, 7]
# We can use list comprehensions for nice maps and filters#
List comprehension stores the output as a list (which itself
may be nested).[add_10(i) for i in [1, 2, 3]] # =>
[11, 12, 13][x for x in [3, 4, 5, 6, 7] if x > 5] # => [6, 7]
# You can construct set and dict comprehensions as well.{x for
x in "abcddeef" if x not in "abc"} # => {'d', 'e', 'f'}{x:
x**2 for x in range(5)} # => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
###################################################### 5.
Modules####################################################
# You can import modulesimport mathprint([Link](16)) # =>
4.0
# You can get specific functions from a modulefrom math import
ceil, floorprint(ceil(3.7)) # => 4print(floor(3.7)) # => 3
# You can import all functions from a module.# Warning: this
is not recommendedfrom math import *
# You can shorten module namesimport math as [Link](16) ==
[Link](16) # => True
# Python modules are just ordinary Python files. You# can
write your own, and import them. The name of the# module is
the same as the name of the file.
# You can find out which functions and attributes# are defined
in a [Link] mathdir(math)
# If you have a Python script named [Link] in the same#
folder as your current script, the file [Link] will# be
loaded instead of the built-in Python module.# This happens
because the local folder has priority# over Python's built-in
libraries.
###################################################### 6.
Classes####################################################
# We use the "class" statement to create a classclass Human:
# A class attribute. It is shared by all instances of this
class
species = "H. sapiens"
# Basic initializer, this is called when this class is
instantiated.
# Note that the double leading and trailing underscores
denote objects
# or attributes that are used by Python but that live in
user-controlled
# namespaces. Methods(or objects or attributes) like:
__init__, __str__,
# __repr__ etc. are called special methods (or sometimes
called dunder
# methods). You should not invent such names on your own.
def __init__(self, name):
# Assign the argument to the instance's name attribute
[Link] = name
# Initialize property
self._age = 0 # the leading underscore indicates the
"age" property is
# intended to be used internally
# do not rely on this to be enforced:
it's a hint to other devs
# An instance method. All methods take "self" as the first
argument
def say(self, msg):
print("{name}: {message}".format(name=[Link],
message=msg))
# Another instance method
def sing(self):
return "yo... yo... microphone check... one two... one
two..."
# A class method is shared among all instances
# They are called with the calling class as the first
argument
@classmethod
def get_species(cls):
return [Link]
# A static method is called without a class or instance
reference
@staticmethod
def grunt():
return "*grunt*"
# A property is just like a getter.
# It turns the method age() into a read-only attribute of
the same name.
# There's no need to write trivial getters and setters in
Python, though.
@property
def age(self):
return self._age
# This allows the property to be set
@[Link]
def age(self, age):
self._age = age
# This allows the property to be deleted
@[Link]
def age(self):
del self._age
# When a Python interpreter reads a source file it executes
all its code.# This __name__ check makes sure this code block
is only executed when this# module is the main [Link]
__name__ == "__main__":
# Instantiate a class
i = Human(name="Ian")
[Link]("hi") # "Ian: hi"
j = Human("Joel")
[Link]("hello") # "Joel: hello"
# i and j are instances of type Human; i.e., they are
Human objects.
# Call our class method
[Link](i.get_species()) # "Ian: H. sapiens"
# Change the shared attribute
[Link] = "H. neanderthalensis"
[Link](i.get_species()) # => "Ian: H.
neanderthalensis"
[Link](j.get_species()) # => "Joel: H.
neanderthalensis"
# Call the static method
print([Link]()) # => "*grunt*"
# Static methods can be called by instances too
print([Link]()) # => "*grunt*"
# Update the property for this instance
[Link] = 42
# Get the property
[Link]([Link]) # => "Ian: 42"
[Link]([Link]) # => "Joel: 0"
# Delete the property
del [Link]
# [Link] # => this would raise an
AttributeError
###################################################### 6.1
Inheritance###################################################
#
# Inheritance allows new child classes to be defined that
inherit methods and# variables from their parent class.
# Using the Human class defined above as the base or parent
class, we can# define a child class, Superhero, which inherits
variables like "species",# "name", and "age", as well as
methods, like "sing" and "grunt"# from the Human class, but
can also have its own unique properties.
# To take advantage of modularization by file you could place
the classes above# in their own files, say, [Link]
# To import functions from other files use the following
format# from "filename-without-extension" import "function-or-
class"
from human import Human
# Specify the parent class(es) as parameters to the class
definitionclass Superhero(Human):
# If the child class should inherit all of the parent's
definitions without
# any modifications, you can just use the "pass" keyword
(and nothing else)
# but in this case it is commented out to allow for a
unique child class:
# pass
# Child classes can override their parents' attributes
species = "Superhuman"
# Children automatically inherit their parent class's
constructor including
# its arguments, but can also define additional arguments
or definitions
# and override its methods such as the class constructor.
# This constructor inherits the "name" argument from the
"Human" class and
# adds the "superpower" and "movie" arguments:
def __init__(self, name, movie=False,
superpowers=["super strength",
"bulletproofing"]):
# add additional class attributes:
[Link] = True
[Link] = movie
# be aware of mutable default values, since defaults
are shared
[Link] = superpowers
# The "super" function lets you access the parent
class's methods
# that are overridden by the child, in this case, the
__init__ method.
# This calls the parent class constructor:
super().__init__(name)
# override the sing method
def sing(self):
return "Dun, dun, DUN!"
# add an additional instance method
def boast(self):
for power in [Link]:
print("I wield the power of
{pow}!".format(pow=power))
if __name__ == "__main__":
sup = Superhero(name="Tick")
# Instance type checks
if isinstance(sup, Human):
print("I am human")
if type(sup) is Superhero:
print("I am a superhero")
# Get the "Method Resolution Order" used by both getattr()
and super()
# (the order in which classes are searched for an
attribute or method)
# This attribute is dynamic and can be updated
print(Superhero.__mro__) # => (<class
'__main__.Superhero'>,
# => <class '[Link]'>,
<class 'object'>)
# Calls parent method but uses its own class attribute
print(sup.get_species()) # => Superhuman
# Calls overridden method
print([Link]()) # => Dun, dun, DUN!
# Calls method from Human
[Link]("Spoon") # => Tick: Spoon
# Call method that exists only in Superhero
[Link]() # => I wield the power of
super strength!
# => I wield the power of
bulletproofing!
# Inherited class attribute
[Link] = 31
print([Link]) # => 31
# Attribute that only exists within Superhero
print("Am I Oscar eligible? " + str([Link]))
###################################################### 6.2
Multiple
Inheritance###################################################
#
# Another class definition# [Link] Bat:
species = "Baty"
def __init__(self, can_fly=True):
[Link] = can_fly
# This class also has a say method
def say(self, msg):
msg = "... ... ..."
return msg
# And its own method as well
def sonar(self):
return "))) ... ((("
if __name__ == "__main__":
b = Bat()
print([Link]("hello"))
print([Link])
# And yet another class definition that inherits from
Superhero and Bat# [Link] superhero import
Superherofrom bat import Bat
# Define Batman as a child that inherits from both Superhero
and Batclass Batman(Superhero, Bat):
def __init__(self, *args, **kwargs):
# Typically to inherit attributes you have to call
super:
# super(Batman, self).__init__(*args, **kwargs)
# However we are dealing with multiple inheritance
here, and super()
# only works with the next base class in the MRO list.
# So instead we explicitly call __init__ for all
ancestors.
# The use of *args and **kwargs allows for a clean way
to pass
# arguments, with each parent "peeling a layer of the
onion".
Superhero.__init__(self, "anonymous", movie=True,
superpowers=["Wealthy"], *args,
**kwargs)
Bat.__init__(self, *args, can_fly=False, **kwargs)
# override the value for the name attribute
[Link] = "Sad Affleck"
def sing(self):
return "nan nan nan nan nan batman!"
if __name__ == "__main__":
sup = Batman()
# The Method Resolution Order
print(Batman.__mro__) # => (<class '__main__.Batman'>,
# => <class
'[Link]'>,
# => <class '[Link]'>,
# => <class '[Link]'>, <class
'object'>)
# Calls parent method but uses its own class attribute
print(sup.get_species()) # => Superhuman
# Calls overridden method
print([Link]()) # => nan nan nan nan nan batman!
# Calls method from Human, because inheritance order
matters
[Link]("I agree") # => Sad Affleck: I agree
# Call method that exists only in 2nd ancestor
print([Link]()) # => ))) ... (((
# Inherited class attribute
[Link] = 100
print([Link]) # => 100
# Inherited attribute from 2nd ancestor whose default
value was overridden.
print("Can I fly? " + str([Link])) # => Can I fly? False
###################################################### 7.
Advanced####################################################
# Generators help you make lazy [Link]
double_numbers(iterable):
for i in iterable:
yield i + i
# Generators are memory-efficient because they only load the
data needed to# process the next value in the iterable. This
allows them to perform# operations on otherwise prohibitively
large value ranges.# NOTE: `range` replaces `xrange` in Python
[Link] i in double_numbers(range(1, 900000000)): # `range` is
a generator.
print(i)
if i >= 30:
break
# Just as you can create a list comprehension, you can create
generator# comprehensions as [Link] = (-x for x in
[1,2,3,4,5])for x in values:
print(x) # prints -1 -2 -3 -4 -5 to console/terminal
# You can also cast a generator comprehension directly to a
[Link] = (-x for x in [1,2,3,4,5])gen_to_list =
list(values)print(gen_to_list) # => [-1, -2, -3, -4, -5]
# Decorators are a form of syntactic sugar.# They make code
easier to read while accomplishing clunky syntax.
# Wrappers are one type of decorator.# They're really useful
for adding logging to existing functions without needing to
modify them.
def log_function(func):
def wrapper(*args, **kwargs):
print("Entering function", func.__name__)
result = func(*args, **kwargs)
print("Exiting function", func.__name__)
return result
return wrapper
@log_function # equivalent:def my_function(x,y):
# def my_function(x,y):
return x+y # return x+y
# my_function =
log_function(my_function)# The decorator @log_function tells
us as we begin reading the function definition# for
my_function that this function will be wrapped with
log_function.# When function definitions are long, it can be
hard to parse the non-decorated# assignment at the end of the
definition.
my_function(1,2) # => "Entering function my_function"
# => "3"
# => "Exiting function my_function"
# But there's a problem.# What happens if we try to get some
information about my_function?
print(my_function.__name__) # =>
'wrapper'print(my_function.__code__.co_argcount) # => 0. The
argcount is 0 because both arguments in wrapper()'s signature
are optional.
# Because our decorator is equivalent to my_function =
log_function(my_function)# we've replaced information about
my_function with information from wrapper
# Fix this using functools
from functools import wraps
def log_function(func):
@wraps(func) # this ensures docstring, function name,
arguments list, etc. are all copied
# to the wrapped function - instead of being
replaced with wrapper's info
def wrapper(*args, **kwargs):
print("Entering function", func.__name__)
result = func(*args, **kwargs)
print("Exiting function", func.__name__)
return result
return wrapper
@log_functiondef my_function(x,y):
return x+y
my_function(1,2) # => "Entering function my_function"
# => "3"
# => "Exiting function my_function"
print(my_function.__name__) # =>
'my_function'print(my_function.__code__.co_argcount) # => 2