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
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.
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)
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
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))
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)
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)
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
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
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
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 ;-)
In [ ]: