with
blocksStop using IDLE, it’s not sufficient for your needs
!
-commands allow for shell-scripting like syntax, but 10x betterBasically, an immutable collection of data:
Different from lists how?
You can create tuples “containing” lvalues.
# Gives index, value pairs
enumerate(seq[, start])
# Applies a function to each element in a sequence
map(function, sequence[, sequence, ...])
# Gives only elements for which the given function is true
# (uses the identity function if None)
filter(function or None, sequence)
# Applies a function to reduce a sequence to a single value
reduce(function, sequence[, initial])
# Give a list of attributes within an object
dir([object])
# "zip up" several sequences
zip(seq1 p, seq1 [...]]) # -> [(seq1[0], seq2[0], ...), (...)]
You can unzip a sequence when passing as arguments:
It’s also possible to have a function take an arbitrary number of arguments:
A similar syntax also works with keyword arguments:
How the CS department has taught classes:
class Vector(object):
__slots__ = ("x", "y")
def mkVector(x, y):
v = Vector()
v.x = x
v.y = y
return v
def addVectors(a, b):
return mkVector(a.x+b.x, a.y+b.y)
def strVector(vec):
return "({0}, {1})".format(vec.x, vec.y)
a = mkVector(3,4)
b = mkVector(1,1)
print(strVector(addVectors(a,b)))
If you do this in the real world, you will be laughed out of any workplace.
Python supports constructors and operator overloading:
class Vector(object):
# Slots are used to specifically limit members to those names
# Most real-world classes don't really use them...
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x+other.x, self.y+other.y)
def __str__(self):
return "({0}, {1})".format(self.x, self.y)
a = Vector(3,4)
b = Vector(1,1)
print(a+b)
Some of the most useful magic functions are:
__lt__
: Comparison, less than__eq__
: Comparison, equality__len__
: Provides implementation for len()
for the class__str__
: Generate a string representation__getitem__
: Support seq[key]
__call__
: Allow support for treating the object as a function__iter__
: Return an iterator, support for x in y
RIT has rit_lib
, which is a janky version of namedtuple
rit_lib
’s struct
is like a mutable namedtuple
that you can’t iterate over
Comprehensions help convert large blocks that build lists or dictionaries into single expressions.
Comprehensions, much like for loops, can be nested, possibly with conditionals:
def getLines(allFilenames):
data = []
for filename in allFilenames:
for line in open(filename):
if ">>" in line:
data.append(line.strip())
return data
# Better done as a comprehension:
def getLines(allFilenames):
return [line.strip()
for filename in allFilenames
for line in open(filename)
if ">>" in line]
Generators are basically a way to create and manipulate sequences without actually storing the full sequence in memory
They can almost be considered “functions that return several times as the function runs”
Generators can also be produced in a similar syntax to comprehensions.
The main difference is that the generator is lazy and so needs to be iterated over to operate on it
>>> gen = (x * 2 for x in range(10))
>>> gen
<generator object <genexpr> at 0x7fce54ecdd70>
>>> for i in gen:
... print(i)
...
0
2
4
6
8
10
12
14
16
18
Sometimes, you want to be able to say “return to this state after doing this”, without worrying about exceptions, etc:
This concept exists in C++ as RAII, and in Python as Context Managers
Any custom class can get context manager functionality by adding two functions:
__enter__(self)
: do any “entry” code__exit__(self, exc_type, exc_value, traceback)
: Any exit code, catches the exception given to it and passes it as argumentsThere is also contextlib, which makes it even simpler
Functions can be thought of like variables - you can pass them into functions, assign them, etc.
Functions returned from other functions can retain information about their context.
Lambda allows you to create a function inline.
There are limitations, the largest being that it only shows the “return value”.
Lambdas can greatly reduce code required for some operations
You can pass functions into other functions…
def compose(f, g):
return lambda x: return f(g(x))
f = lambda x: x*2
g = lambda x: x+5
fog = compose(f, g)
print(fog(5)) # 20
Notice that the function returned relies on the function passed in.
Let’s make a function that operates on other functions to provide debug information.
In fact, Python provides some even better syntax for using decorators:
def debugInfo(name)
def _func(func):
def runFunc(*args, **kwargs):
print("Before", name)
func(*args, **kwargs) # Forward all arguments
print("After", name)
return runFunc
def _func
@debugInfo("add")
def add(a, b): return a+b
# Equivalent to:
debugAdd = debugInfo("add")
@debugAdd
def add(a, b): return a+b
# Equivalent to:
def add(a, b): return a+b
debugAdd = debugInfo("add")
add = debugAdd(add)
Sometimes you’ll want to install different versions of Python libraries. Normally, a python app won’t/can’t specify the version of each library required. This causes problems when one program only works with libfoo==1.5
, but another requires libfoo==2.0
.
To get around this, we use virtual environments.
The easiest way to set them up is via VirtualEnvWrapper or VirtualFish (depending on your shell).
Some fun links: