Index:

  1. Lists and sets
  2. Dictionaries
  3. Tuples
  4. Strings
  5. For loops
  6. List comprehensions
  7. Functions and parameters
  8. Classes
  9. Magic methods
  10. Exceptions
  11. Generators

1. Lists and sets


In [5]:
a = [1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0]  # this is a list

sa = set(a)  # this is a set, as in math class
anotherset = {3, 4, 5, 6, 7}  # syntactic sugar for declaring sets (Python is a very "sweet" language)

print('---------- creating lists and sets -----------')
print(a)
print(sa)
print(anotherset)

print('\n2. ---------- size of things -----------')
print(len(a))  # len is a function that knows the size of THINGS
print(len(sa))
print(len('I am a string'))

print('\n3. ---------- get by index -----------')
print(a[0])  # OK, you expected this
print(a[10])
print(a[-1])  # but maybe not this
print(a[-11])
try:
    sa[0]
except TypeError as e:
    print("sets are not sorted structures, so you can't use [brackets on them]")
print(list(sa))  # but you can convert a set to a list (don't assume any particular order)

print('\n4. ---------- set/list union and difference -----------')
print(sa - anotherset)  # You can subtract sets
print(sa.union(anotherset))
print(sa | anotherset)  # you can do this way to
print(sa & anotherset)  # and intersect like this
print(a + [10, 20, -30])  # You can add lists
print([] + [])  # You gotta try this in javascript... :-)

print('\n5. ---------- list slicing -----------')
print('1:5 -->', a[1:5])  # last index is not included
print('5: -->', a[5:])
print('5:-2 -->', a[5:-2])
print(':-2 -->', a[:-2])
print('-2: -->', a[-2:])
print(': -->', a[:])  # this is usually used as a hack to create a shallow copy of a list


---------- creating lists and sets -----------
[1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0]
set([0, 1, 2, 3, 4, 5])
set([3, 4, 5, 6, 7])

2. ---------- size of things -----------
11
6
13

3. ---------- get by index -----------
1
0
0
1
sets are not sorted structures, so you can't use [brackets on them]
[0, 1, 2, 3, 4, 5]

4. ---------- set/list union and difference -----------
set([0, 1, 2])
set([0, 1, 2, 3, 4, 5, 6, 7])
set([0, 1, 2, 3, 4, 5, 6, 7])
set([3, 4, 5])
[1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0, 10, 20, -30]
[]

5. ---------- list slicing -----------
('1:5 -->', [2, 3, 4, 5])
('5: -->', [5, 4, 3, 2, 1, 0])
('5:-2 -->', [5, 4, 3, 2])
(':-2 -->', [1, 2, 3, 4, 5, 5, 4, 3, 2])
('-2: -->', [1, 0])
(': -->', [1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0])

2. Dictionaries


In [50]:
d = {'a': 1, 'b': 'two', 'c': 30}  # this is a dictionary
d['d'] = 400  # this is how you add something to it
del d['b']  # And this is how you remove stuff

print(len(d))  # Like I told you, len knows the size of THINGS
print(d['a'])
print(d.keys())  # If it were me I would return a set, but this guy resturns a list. Works too.


3
1
['a', 'c', 'd']

3. Tuples


In [51]:
t = (1, 2, 3)  # This is a tuple. Think of a tuple as an IMMUTABLE list.
notatuple = (1)  # This is just a number
t1 = (1,)  # This is a tuple

print(t)
print(notatuple)
print(t1)

print('len(t) = ', len(t))
print('len(t1) = ', len(t1))  # but you already knew that

d[t] = 'really?'  # But did you know tuples could be dictionary keys? Crazy shit!

print(d)

x1, x2, x3 = t  # triple assignment, yay! (This also works with lists)
print(x2)


(1, 2, 3)
1
(1,)
('len(t) = ', 3)
('len(t1) = ', 1)
{'a': 1, 'c': 30, 'd': 400, (1, 2, 3): 'really?'}
2

4. Strings


In [53]:
s1 = 'abc'  # It works
s2 = "def"  # Either way
s3 = """ This is 
fine too."""
# There are four string delimiters: ['], ["], ['''], ["""]

print(s1 + s2)  # sum = concatenation

s3 = s2
s2 += s1
print(s2)  # Awwwnnnn <3
print(s3)  # Strings are immutable!
print(s2[-4:])  # Strings are indexable the same as lists (This is profound. Stop and think about this) 

s4 = ' and then '.join(['one', 'two', 'three', 'four'])  # list elements can be joined together with a string separator
print(s4)
print(s4.split(' and then '))  # and split back

tony = 'Tony'
print('Hello %s ' % tony)  # % is a replacement operator. The left hand is a string, the right hand can be a lot of things
what = 'cold'
print('Hello %s, are you %s?' % (tony, what))  # When making multiple replacements, use a TUPLE in the right hand


abcdef
defabc
def
fabc
one and then two and then three and then four
['one', 'two', 'three', 'four']
Hello Tony 
Hello Tony, are you cold?

5. For loops


In [56]:
print('--------- list loop (elements)')
for i in a:
    print(i)

print('\n-------- tuple loop (elements)')
for i in t:
    print(i)
    
print('\n-------- set loop (elements)')
for i in sa:
    print(i)
    
print('\n-------- dictionary loop (keys)')
for i in d:
    print(i)
    
print('\n-------- string loop (letters, which make sense since you can use [] on strings)')
for i in 'really?':
    print(i)
    
print(zip(a, d))  # zip produces a LIST of TUPLEs from two or more COLLECTIONS. The resulting size is TRUNCATED.
for i1, i2 in zip(a, d):
    print ('i1 = %s, i2 = %s' % (i1, i2))


--------- list loop (elements)
1
2
3
4
5
5
4
3
2
1
0

-------- tuple loop (elements)
1
2
3

-------- set loop (elements)
0
1
2
3
4
5

-------- dictionary loop (keys)
a
c
d
(1, 2, 3)

-------- string loop (letters, which make sense since you can use [] on strings)
r
e
a
l
l
y
?
[(1, 'a'), (2, 'c'), (3, 'd'), (4, (1, 2, 3))]
i1 = 1, i2 = a
i1 = 2, i2 = c
i1 = 3, i2 = d
i1 = 4, i2 = (1, 2, 3)

6. List comprehensions


In [2]:
lis = [0, 1, 2, 3, 4]

lis_squared = [x * x for x in lis]  # list comprehension
print(lis_squared)

set_squared = {x * x for x in lis}  # set comprehension
print(set_squared)

dic_squared = {x: x * x for x in lis}  # dictionary comprehension (if even tuples can be dictionary keys, why numbers wouldn't?)
print(dic_squared)


[0, 1, 4, 9, 16]
set([0, 1, 4, 16, 9])
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

7. Functions and parameters


In [70]:
def mysum(a, b):  # We don't want to override Python's native sum function, do we?
    print('mysum(%s, %s)' % (a, b))
    return a + b

print(mysum(1, 1))  # Good the sky is still blue 
print(mysum(b=1, a=2))  # You can choose your parameter passing strategy. Positional (above), or named. 
print(mysum(1, b=2))  # Or you can mix it a little bit


def mysum2(*args):  # One * means: a list of POSITIONAL parameters that will be converted to a TUPLE.
    print('mysum2(%s)' % list(args))  # If we leave args as a tuple, "%" would try to make 5 replacements but we only have one "%s"
    return sum(args)  # Python's native sum function takes in a collection, so this is fine

print(mysum2(1, 2, 3, 4, 5))
params = [10, 20, 30]
print(mysum2(*params))  # You can also do this. Here the * will "unfold" the params list into a list of parameters


def concatsum(*args, **kwargs):  # Two ** means a list of named (KEYWORD) parameters that will be converted to a DICTIONARY
    print('concatsum(%s, %s)' % (list(args), kwargs))
    numbersum1 = sum(args)
    stringsum = ''.join(kwargs.keys())
    numbersum2 = sum([kwargs[k] for k in kwargs])  # assuming the named parameters contain numbers
    return stringsum, numbersum1 + numbersum2  # Returning a tuple. Don't need () because of the return keyword! 
    
    
print(concatsum(1))
print(concatsum(1, 2))
letters, number = concatsum(1, 2, vixe=10, maria=20)
print(letters)
print(number)


mysum(1, 1)
2
mysum(2, 1)
3
mysum(1, 2)
3
mysum2([1, 2, 3, 4, 5])
15
mysum2([10, 20, 30])
60
concatsum([1], {})
('', 1)
concatsum([1, 2], {})
('', 3)
concatsum([1, 2], {'vixe': 10, 'maria': 20})
vixemaria
33

8. Classes


In [4]:
class A(object):
    x = 1
    
    # __init__ (read "dunder init" is a special method = the constructor)
    # for instance methods, the fisrt argument is always the instance
    # As a convention, pythonists name that argument "self"
    def __init__(self, y):  
        self.y = y
    
    def m(self, z):
        print('called method 1 (%s)' % z)
        self.z = z

print('1. ----- what is a class ----')
print(A)  # An object of type class
print(A.x)  # this class has x = 1
print(A.m)  # it has another attribute called m, which is an UNBOUND method

print('\n2. ----- what can you do with it ----')
a1 = A(20)  # A class is a recipe to create objects. 
a1.m(300)  # a1.m is a BOUND (to an object) method, so we don't need to pass in the first parameter 
a2 = A(21)
A.m(a2, 320)  # This is more or less the same thing as doing a2.m(320)
a1.w = 4000  # Adding attributes to objects is permitted (though probably not a very good idea)
a2.x = 2
A.x = 3  # Next instance of A will have x = 3, but will this change a1 or a2? We'll see (*)
a3 = A(22)
amethod = a3.m  # Get a reference for a3's BOUND method m
amethod(340)  # This will change a3.z (don't try this with javascript!)

print(a1.m)  # See? A BOUND method
print(a1.m == A.m)  # Those two are not the same object
print(a1.m == a2.m)  # UNBOUND method A.m is a recipe for creating BOUND methods 
print('a3', a3.x, a3.y, a3.z)  # a3.x = 3, ok this makes sense.
print('a2', a2.x, a2.y, a2.z)  # a2.x = 2, yeah, we change this one too
print('a1', a1.x, a1.y, a1.z, a1.w)  # a1.x = 3?? Why not 1?? Go play with it a little bit and see if you can find out :-)


class B(A):  # subclass o A
    def __init__(self, y):
        super(B, self).__init__(y)  # invoke parent constructor if you want
    
    def m2(self):
        print('inside M2')
        
print('\n3. ------- inheritance -------')
b = B(30)
b.m(400)
b.m2()
print('b', b.x, b.y, b.z)  # pretty much expected


1. ----- what is a class ----
<class '__main__.A'>
1
<unbound method A.m>

2. ----- what can you do with it ----
called method 1 (300)
called method 1 (320)
called method 1 (340)
<bound method A.m of <__main__.A object at 0x7fa08b216a10>>
False
False
('a3', 3, 22, 340)
('a2', 2, 21, 320)
('a1', 3, 20, 300, 4000)

3. ------- inheritance -------
called method 1 (400)
inside M2
('b', 3, 30, 400)

9. Magic methods


In [13]:
class Water(object):
    def __init__(self, liters):
        self.liters = liters
    
    # Read as "dunder-add"
    def __add__(self, other):  # overloads '+' operator
        return Water(self.liters + other.liters)
    
    def __str__(self):  # used when python needs to convert it to string
        return 'Water(%s)' % self.liters

w1 = Water(10)
w2 = Water(20)
wsum = w1 + w2  # python will call __add__ here 

print('1. overloading +')
print('%s + %s = %s' % (w1, w2, wsum))

# You can do the same with: *, -, /, %... you can overload everything. EVERYTHING.
# Here's another one, let's override '(...)'

class Person(object):
    def __init__(self, name):
        self.name = name
    
    def whatsyourname(self):
        print('my name is %s' % self.name)
    
    def __call__(self, *args, **kwargs):
        print('Did you call me?')

print('\n2. callable objects +')
joe = Person('John')
joe()
joe.whatsyourname()

# Is this a function or an object? A wave or a particle? You decide.
# Here's another one, let's 'in'

class NaturalNumbers(object):
    current = 0
    
    def __iter__(self):
        return self
    
    def next(self):
        r = self.current
        self.current += 1
        return r

print('\n3. iterators')
N = NaturalNumbers()
for n in N:  # To the infinity, AND BEYOND!
    print(n)
    if n > 5:
        break  # No Buzz, not today

# So, the "in" operator caused python to invoke __iter__, and expect an iterator as a result
# An iterator is supposed to have a ".next()" (no dunder) method
# next() should raise a StopIteration to let the world know it has reached the end.

# You can also override:
# '.' with __getattr__ and __setattr__ (Django does a good job using this in its ORM)
# '[...]' with __getitem__ and __setitem__ (Numpy uses this very well for its matrix object)
# pretty much everything
# A more complete list of examples: http://www.diveintopython3.net/special-method-names.html


1. overloading +
Water(10) + Water(20) = Water(30)

2. callable objects +
Did you call me?
my name is John

3. iterators
0
1
2
3
4
5
6

10. Exceptions


In [19]:
class NameTooShortException(Exception):
    def __init__(self, *args, **kwargs):
        # super constructor args? Dunno, don't care. The first is probably a string.
        super(Exception, self).__init__(*args, **kwargs)   


class Person(object):
    name = ''
    
    def setName(self, name):  # This should probably raise PythonIsNotFFingJavaException, don't do it pls.
        if len(name) == 1:
            raise NameTooShortException("%s? You can't name a Person %s!" % (name, name))
        self.name = name
        print('My name is %s' % self.name)


joe = Person()
t = Person()
try:
    joe.setName('John')
    t.setName('T')
except NameTooShortException as exc:
    print(exc)
    print('Woops, sorry!')
finally:
    t.setName('Travis')

# There's more details to exception handling, but there's no need to dig too deep at this point


My name is John
T? You can't name a Person T!
Woops, sorry!
My name is Travis

11. Generators


In [28]:
def generate123():
    yield 1
    yield 2
    yield 3

print('1. ----- Generators - getting started -----')
g = generate123()  # Normally this would be None
print(g)  # But this is an ITERATOR (see previous section), because of the "yields"
print(g.next())
print(g.next())
print(g.next())
try:
    print(g.next())
except StopIteration as exc:
    print('END')
    
# So you can do this
for i in generate123():
    print('again: %s' % i)
    
#  Those types of iterators are called GENERATOR FUNCTIONS
#  The cool thing about GFs is that they suspend execution 
#  at each yield point until next's next() invocation.


def producer():
    i = 0
    while i < 3:
        print('produced %s' % i)
        yield i
        i += 1
    print('I ran out of numbers')


def consumer(itr):
    for i in itr:
        print('consumed %s' % i)
    print('He ran out of numbers')
    
print('\n2. ----- Producer/Consumer example -----')
p = producer()
consumer(p)

#  It looks like there are 2 threads running, but it's not the case.
#  Every loop iteration on the consumer triggers execution on the producer
#  until the next stop (yield).
#  This can be useful for example when you need to process large amounts of
#  data but you don't want to load it all into memory ;-)


1. ----- Generators - getting started -----
<generator object generate123 at 0x7fa07af98050>
1
2
3
END
again: 1
again: 2
again: 3

2. ----- Producer/Consumer example -----
produced 0
consumed 0
produced 1
consumed 1
produced 2
consumed 2
I ran out of numbers
He ran out of numbers

In [ ]: