Python for RIT Students

Matt Soucy ()

November 6, 2015



Stop using IDLE, it's not sufficient for your needs


Basically, an immutable collection of data:

tup = (1,3,5)
print type(tup) # tuple

Different from lists how?

Tuple Packing/Unpacking

You can create tuples "containing" lvalues.

For those who are unfamiliar, lvalues are "things you can assign to"

x, y = 5, 7 # same as `x=5;y=7`
x, y = y, x # Swaps the values of x and y
for root, dirs, files in os.walk("."): pass

Functions you might not know about

# 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

# "zip up" several sequences
zip(seq1 p, seq1 [...]]) # -> [(seq1[0], seq2[0], ...), (...)]

Special syntax

You can unzip a sequence when passing as arguments:

data = (1,5)
def add(a, b):
    return a+b

It's also possible to have a function take an arbitrary number of arguments:

def sayAll(*args):
    for arg in args:
sayAll("hello", "world", 5, 42 "stuff")

A similar syntax also works with keyword arguments:

def keywords(**kwargs):
keywords(hello="world", this="that")

Magic functions

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)

If you do this in the real world, you will be laughed out of any workplace.

Magic functions

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)

Useful magic functions

Some of the most useful magic functions are:


RIT has rit_lib, which is a janky version of namedtuple

from rit_lib import struct
class Vector(struct):
    _slots = ("x", "y")

rit_lib's struct is like a mutable namedtuple that you can't iterate over

from collections import namedtuple
Vector = namedtuple("Vector", ["x", "y"])

Statements vs. Expressions

# An expression is basically "something that has a value"
"Hello, world!"

# A statement is "something that does something"
# All expressions are also statements
# Anything that involves blocks is a statement
for i in range(10): pass
try: pass
except: pass


Comprehensions help convert large blocks that build lists or dictionaries into single expressions.

# Let's implement a "map"-like function
def doubleItems(seq):
    results = []
    for i in seq:
    return results

# And now as a comprehension
def doubleItems(seq):
    return [i*2 for i in seq]

# Return a generator instead of a list
def doubleItems(seq):
    return (i*2 for i in seq)

Complex comprehensions

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:
    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"

def genDoubles(maxVal):
    for i in range(0, maxVal):
        yield i*2 # Sends the value out of the function

for i in genDoubles(20):

val = genDoubles(20)

Comprehensions vs generators

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)

Context managers

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

Context managers

# Let's copy a file's contents!
# This is super verbose
inpfi = open("somefile")
outfi = open("newfile", "w")
outfi.write( # What if, we get an IOError?

Context managers

# Let's copy a file's contents!
# This is still super verbose..
inpfi = open("somefile")
outfi = open("newfile", "w")
except IOError:

Context managers

# Let's copy a file's contents!
# Let's use context managers!
with open("somefile") as inpfi:
    with open("newfile", "w") as outfi:

Context managers

# Let's copy a file's contents!
# Why not use both together?
with open("somefile") as inpfi, open("newfile", "w") as outfi:

Creating context managers

Any custom class can get context manager functionality by adding two functions:

There is also contextlib, which makes it even simpler

First Class Functions

Functions can be thought of like variables - you can pass them into functions, assign them, etc.

def getAdder(x):
    def add(y):
        return x+y
    return add
add5 = getAdder(5)
print(add5(5)) # 10

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".

def notALambda(x):
    return x+2
isALambda = lambda x: x+2 # Note there is no "return"

Lambdas can greatly reduce code required for some operations

def getAdder(x):
    return lambda y: x+y


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.

def debugInfo(name, func):
    def runFunc(*args, **kwargs):
        print("Before", name)
        func(*args, **kwargs) # Forward all arguments
        print("After", name)
    return runFunc

def add(a, b):
    return a+b

# Let's use our debug add as add
    add = debugInfo("add", add)


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

def add(a, b): return a+b

# Equivalent to:
debugAdd = debugInfo("add")
def add(a, b): return a+b

# Equivalent to:
def add(a, b): return a+b
debugAdd = debugInfo("add")
add = debugAdd(add)

Virtual Environments

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).

More info

Some fun links: