Skip to content

Instantly share code, notes, and snippets.

@bangedorrunt
Last active August 29, 2015 14:21
Show Gist options
  • Save bangedorrunt/f199e764f73cd11f230c to your computer and use it in GitHub Desktop.
Save bangedorrunt/f199e764f73cd11f230c to your computer and use it in GitHub Desktop.
Learning Python
class Spam:
def do_it(self, message):
print(message)
def do_it_without_self(message)
print(message)
spam = Spam()
bound_func = spam.do_it # Bound method object (automatically)
bound_func('yummy') # Same as spam.do_it('yummy')
bound_func2 = spam.do_it_without_self # Bound method object (automatically)
bound_func2('yummy') # Type Error: do_it_without_self() takes 1 positional argument but 2 were given
# because `bound_func2` automatically pass `self` or `spam` instance to method
# that does not expect one
unbound_func = Spam.do_it # Unbound method object
print(unbound_func) # Unbound methods are functions
unbound_func(spam, 'yummy') # Pass in instance
unbound_func2 = Spam.do_it_without_self # Unbound methods are functions
unbound_func2('yummy') #=> yummy
"""
Super: Defines a `method` function and a `delegate` that expects
an `action` in a subclass
Inheritor: Doesn't provide any new names, so it gets everything
defined in `Super`
Replacer: Overrides `Super`'s method with a version of its own
Extender: Customize `Super`'s method by overriding and all back
to run the default
Provider: Implement `action` method expected byu `Super` delegate method
"""
class Super:
def method(self):
print('in Super.method') # Default behavior
def delegate(self):
self.action() # Expected to be defined in subclass
class Inheritor(Super):
pass
class Replacer(Super): # Replace method completely
def method(self):
print('in Replacer.method')
class Extender(Super): # Extend method behavior
def method(self):
print('Starting Extender.method')
Super.method(self)
print('Ending Extender.method')
class Provider(Super): # Define required action method
def action(self):
print('in Provider.action')
for c in (Inheritor, Replacer, Extender):
print('\n' + c.__name__ + ': ')
c().method()
print('\nProvider: ')
Provider().delegate() # `delegate` is called from `Super` method
# with `self` is referenced to `Provider instance` passed in
# Then `action` is called
class Coordinate(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return "Coord: " + str(self.__dict__)
def add(a, b):
return Coordinate(a.x + b.x, a.y + b.y)
def sub(a, b):
return Coordinate(a.x - b.x, a.y - b.y)
def wrapper(func):
def checker(a, b): # 1
if a.x < 0 or a.y < 0:
a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
if b.x < 0 or b.y < 0:
b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
ret = func(a, b)
if ret.x < 0 or ret.y < 0:
ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
return ret
return checker
one = Coordinate(100, 200)
two = Coordinate(300, 200)
three = Coordinate(-100, -100)
add = wrapper(add) # This is a decorator
sub = wrapper(sub) # This is a decorator
sub(one, two) #=> Coord: {'y': 0, 'x': 0}
add(one, three) #=> Coord: {'y': 200, 'x': 100}
# Alternatively, use decorator
@wrapper
def add(a, b):
return Coordinate(a.x + b.x, a.y + b.y)
@wrapper
def sub(a, b):
return Coordinate(a.x - b.x, a.y - b.y)
sub(one, two) #=> Coord: {'y': 0, 'x': 0}
add(one, three) #=> Coord: {'y': 200, 'x': 100}
# Sequence assignment
spam, ham = 'yum', 'yuck' # Tuple assignment
spam, ham = ham, spam # Tuple swap values
[spam, ham] = ['yum', 'yuck'] # List assignment
spam, ham = ['yum', 'yuck']
[spam, ham] = ('yum', 'yuck')
s = 'Spam'
a, b, c, d = s
(a, b, c, d) #=> ('S', 'p', 'a', 'm')
((a, b), c) = ('Sp', 'am') # Nested destructure
(a, b, c) #=> ('S', 'p', 'am')
for (a, b, c) in ((1, 2, 3), (4, 5, 6)):
print(a, b, c) #=> 1 2 3
#=> 4 5 5
[a + b + c for (a, b, c) in [(1, 2, 3), (4, 5, 6)]] #=> [6, 15]
tuple(a + b + c for (a, b, c) in [(1, 2, 3), (4, 5, 6)]) #=> (6, 15)
{'Sum of {}, {}, {}'.format(a, b, c): a + b + c for (a, b, c) in [(1, 2, 3), (4, 5, 6)]} #=> {'Sum of 1, 2, 3': 6, 'sum of 4, 5, 6': 15}
# Extended Sequence Unpacking
# This is similar to `rest ...` operator in JavaScript
seq = 'Spam'
a, b, c, d = seq
a, b, c, d #=> ('S', 'p', 'a', 'm')
a, *b, c = seq
a, b, c #=> ('S', ['p', 'a'], 'm')
a, b, *c = seq
a, b, c #=> ('S', 'p', ['a', 'm'])
a, b, *c, d = seq
a, b, c, d #=> ('S', 'p', ['a'], 'm')
a, b, *c, d, e = seq
a, b, c, d, e #=> ('S', 'p', [], 'a', 'm')
d = {'food': 'Spam', 'quantity': 4, 'color': 'pink'}
d['food'] #=> 'food' fetch value of key `food`
d['quantity'] += 1
d #=> {'food': 'Spam', 'quantity': 5, 'color': 'pink'}
del d['color'] #=> {'food': 'Spam', 'quantity': 5}
d = {}
d['name'] = 'babygau' #=> Create key by assignment
d['job'] = 'freelancer' #=> Create key by assignment
d #=> {'name': 'babygau', 'job': 'freelancer'}
d1 = dict(name='babygau', job='freelancer') #=> {'name': 'babygau', 'job': 'freelancer'}
d2 = dict(zip(['name', 'job'], ['babygau', 'freelancer']) #=> {'name': 'babygau', 'job': 'freelancer'}
d3 = dict([('name', 'babygau'), ('job', 'freelancer')])
d4 = dict.fromkeys(['name', 'job'], None)
d5 = dict.fromkeys(['name', 'job'])
d6 = {k:None for k in ['name', 'job']}
# Dictionary methods
list(d.keys()) #=> ['name', 'job'] `d.keys()` return iterable objects so wrap them in a list to collect their values
d.values() #=> ['babygau', 'freelancer'] `d.values()` return iterable objects too
d.items() #=> [('name', 'babygau'), ('job', 'freelancer')]
d.get('name') #=> babygau
d.get('gender') #=> NONE because `gender` is missing
d.get('gender', 'male') #=> 'male' as default value if 'gender' key is missing
d2 = {'gender': 'male'}
d.update(d2) #=> {'name': 'babygau', 'job': 'freelancer', 'gender': 'male'} merge `d2` into d
d.pop('gender') #=> {'name': 'babygau', 'job': 'freelancer'}
d.copy()
d.clear()
# Testing keys
if not 'discount' in d:
print('missing')
# Sorting keys
ks = list(d.keys())
ks.sort()
ks #=> ['color', 'food', 'quantity']
for key in ks:
print(key, '=>', d[key]) #=> 'color' => 'pink'
#=> 'food' => 'Spam'
#=> 'quantity' => '5'
for key in sorted(d):
print(key, '=>', d[key])
def generator_plus(number):
for i in range(number):
yield i
iter = generator_plus(5) # Return an iterator object
next(iter) #=> Start generator
next(iter) #=> 1
next(iter) #=> 2
next(iter) #=> 3
next(iter) #=> 4
next(iter) #=> StopIteration
# 2 way communication
# Note: `yield expression` must obtain value from `iterator's send()` method
# otherwise, `yield` will get `None` value
def generator_2way():
a = 0
b = (yield a) + 1
c = (yield a + b)
print("a = {}, b = {}, c = {}".format(a, b, c))
yield # Stupid fix for StopIteration occured before printing out values
iter = generator_2way()
next(iter) # Start generator for the first-run
# Don't send value, because just-started
# generator don't accept non-None value
#=> Yield out 0
iter.send(7) #=> Yield out 8
iter.send(10) #=> Yield nothing
#=> a = 0, b = 8, c = 10
# `zip`
list(zip('abc', 'xyz')) #=> [('a', 'x'), ('b', 'y'), ('c', 'z')]
# map(func, *iterables)
tuple(map(lambda data: data * 2, range(3))) #=> (0, 2, 4)
m = map(lambda data: data *2, range(3))
m.__iter__().__next__() #=> 0
m.__iter__().__next__() #=> 2
m.__iter__().__next__() #=> 4
m.__iter__().__next__() #=> StopIteration Exception
# filter(func or None, *iterable)
tuple(filter(lambda data: data % 2, range(10))) #=> (1, 3, 5, 7, 9)
# Note: List is the most general sequence provided by Python.
# they have no fixed size and are also mutable - lists can
# be modified in place by assignment to offsets as well as a
# variety of list method calls.
l = [123, 'hello', 1.23]
len(l) #=> 3
l[0] #=> 123
l[:-1] #=> [123, 'hello']
l + [4, 5, 6] #=> [123, 'hello', 1.23, 4, 5, 6]
l * 2 #=> [123, 'hello', 1.23, 123, 'hello', 1.23]
# List methods
l.append('world') #=> [123, 'hello', 1.23, 'world']
l.pop(2) #=> 1.23
l.pop(0) #=> 123
l #=> ['hello', 'world']
l.sort() #=> ['hello', 'world']
l.reverse() #=> ['world', 'hello']
l.sort(key=str.lower) #=> ['hello', 'world']
l.sort(key=str.lower, reverse=True) #=> ['world', 'hello']
l.extend([456]) #=> ['world', 'hello', 456]
l.insert(0, '!') #=> ['!', 'world', 'hello', 456]
l.index('!') #=> 0
l.count(456) #=> 1
l.pop(3) #=> ['!', 'world', 'hello']
l.remove('!') #=> ['world', 'hello']
del l[0] #=> ['hello']
lCopy = l.copy() #=> `lCopy` is not reference of `l` any more
lCopy = l[:] #=> The same with `copy()` method but use `empty-limit slice` approach
# Multi-dimensional array
m = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]] #=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
m[1] #=> [4, 5, 6]
m[1][2] #=> 6 get row 2, then get item 3 within the row
# List comprehensions
col2 = [row[1] for row in m] #=> [2, 5, 8]
col2 = [row[1] + 1 for row in m] #=> [3, 6, 9]
col2 = [row[1] for row in m if row[1] % 2 == 0] #[2, 8] fill out odd items
diag = [m[i][i] for i in [0, 1, 2]] #=> [1, 5, 9] collect a diagonal from matrix
list(range(4)) #=> [0, 1, 2, 3]
list(range(-6, 7, 2)) #=> [-6, -4, -2, 0, 2, 4, 6] collect of range from -6 to +6 by step of 2
# Create generators
g = (sum(row) for row in m)
next(g) #=> 6
next(g) #=> 15
next(g) #=> 24
list(map(sum, m)) #=> [6, 15, 24]
def func():
data = 'local variable'
def nested_func():
data += ' inside nested_func' # Bug here: `data + ' inside nested_func'` is evaluated first
# but actually it is not defined inside `nested_func`
print(data)
return nested_func
test = func()
test() #=> UnboundLocalError: local variable 'data' referenced before assignment
# Fix:
def func():
data = 'local variable'
def nested_func():
global data
data += ' inside nested_func'
print(data)
return nested_func
test = func()
test() #=> 'local variable inside nested_func'
# Module Search Path
# ------------------
"""
1. The home directory of program
2. PYTHONPATH directory
3. Standard library directories
4. The contents of any .pth files
5. The site-packages home of 3rd party extensions
"""
# Module Usage
# ------------
import module1
module1.printer('Spam') #=> Spam
from module1 import printer
printer('Spam') #=> Spam
from module1 import *
printer('Spam') #=> Spam
another_printer('Ham') #=> Ham
# Imports are passed by reference
# -------------------------------
# mod1.py
int = 1 # This is immutable value
l = [1, 2, 3] # This is mutable list
# mod2.py
from mod1 import int, l
int = 2 # Change local `int` only
l[0] = 0 # Changes shared mutable `l`
import mod1
mod1.x #=> 1
mod1.l #=> [0, 2, 3]
# `import` and `from` equivalence
# -------------------------------
from module1 import x, y
import module1
x = module1.x
y = module1.y
del module1 # Get rid of module1 name
# Name conflicts among modules
# ----------------------------
# mod1.py
def func():
# mod2.py
def func():
# main.py
from mod1 import func
from mod2 import func # This will overwrites `func` from `mod1`
func() # Only call `mod2.func`
# Fix
# main.py
import mod1
import mod2
mod1.func()
mod2.func()
# A better approach
# main.py
from mod1 import func as mod1_func
from mod2 import func as mod2_func
mod1_func()
mod2_func()
# Module Packages (or Regular Packages)
# -------------------------------------
"""
In addition to a module name, an import can name a directory path.
A directory of Python code is said to be a package, so such imports are known
as package imports
At least until Python 3.3, each directory named within the path of a package
import statement must contain a file named `__init__.py__` or your package
import will fail
"""
# dir0/dir1/dir2/mod.py
# dir0/
# dir1/
# __init__.py
# dir2/
# __init__.py
# mod.py
import dir1.dir2.mod
from dir1.dir2.mod import x, y
# Package Relative Imports
# ------------------------
"""
Python imports select between relative (in the containing directory)
and absolute (in the directory on sys.path) resolution as follows:
- Dotted imports: from . import mod
Are relative-only in both 2.X and 3.X
- Non-dotted imports: import mod, from mod import x
Are relative-then-absolute in 2.X
To change 2.X import behavior to absolute only, use: from __future__import absolute_import
Are absolute-only in 3.X
"""
# mypkg/
# __init__.py
# main.py
# string.py
# In 3.X
import string # Imports standard `string` lib
from string import name # Imports `name` from standard `string` lib
from . import string # Imports `string` lib from `mypkg`
from .string import name # Imports name from `string` lib from `mypkg`
from mypkg import string # Import `string` lib from `mypkg` only if
# `mypkg` is on `sys.path`
# Namespace Packages
# ------------------
# mypkg/module1/sub/mod1.py
# mypkg/module2/sub/mod2.py
# Givent that `mypkg/module1` and `mypkg/module2` is on `sys.path`
import sub
sub #=> <module 'sub' (namespace)> namespace packages: nested search paths
sub.__path__ #=> _NamespacePath(['/usr/mypkg/module1', '/usr/mypkg/module2'])
from sub import mod1
from sub import mod2
# mod1.py
from . import mod2
from .mod2 import x, y
# mod2.py
from . import mod1
from .mod1 import a, z
# Module Attribute __name__
# -------------------------
"""
Each module has a built-in attribute called `__name__`, which Python creates
and assigns automatically as follows:
- If the file is being run as a top-level program: `__name__` is set to `__main__`
- If the file is being imported instead: `__name__` is set to `module_name`
"""
def tester():
print("Spam is yummy")
if __name__ == '__main__':
tester() # Only called when run program at top-level
# Not when imported
# Note: By default, all the names assigned inside a function definition are
# put in the local scope (the namespace associated with the function call).
# If you want to assign a name that lives in an `enclosing def`, you can do
# so by declaring it in `nonlocal` statement. If you need to assign a name
# that lives at the top level of the module enclosing the function, you can do
# so by declaring it in an `global` statement.
value = 'global'
def func():
def enclosing_func():
nonlocal value # Explicitly declare non-local variable
value = 'non local' # `value` can live in `func()`
value = 'local' # Local value
enclosing_func()
print(value)
func() #=> 'non local' is printed
123 + 234 #=> 357
1.5 * 4 #=> 6.0
2 ** 3 #=> 8
len(str(2 ** 1000000)) # How many digits in a really BIG number? => 301030
# Use standard module
import math
math.pi #=> 3.141592653589793
math.sqrt(4) #=> 2
import random
random.random() #=> 0.52342314324234
random.choice([1, 2, 3, 4, 5]) #=> 1
# `__getitem__` and `__setitem`
class Indexer:
data = [1, 2, 3 , 4]
def __getitem__(self, index):
print('getitem: ', index)
return self.data[index]
x = Indexer()
x[0] #=> 1
x[-1] #=> 4
x[1:3] #=> [2, 3]
for item in Indexer:
print(item, end=' ') #=> 1 2 3 4
# `__iter__` and `__next__`
class Squares:
def __init__(self, start, stop):
self.value = start - 1
self.stop = stop
def __iter__(self): # Get iterator object on iterable
return self
def __next__(self):
if self.value == self.stop:
raise StopIteration
self.value += 1
return self.value ** 2
for item in Square(1, 5):
print(item, end=' ') # 1 4 9 16 25
iterator = iter(Square(1, 5)) # Obtain iterator from Square
next(iterator) #=> 1
next(iterator) #=> 4
next(iterator) #=> 9
next(iterator) #=> 16
next(iterator) #=> 25
next(iterator) #=> StopIteration Exception
# All parameters (arguments) in the Python language are passed by reference.
# It means if you change what a parameter refers to within a function, the
# change also reflects back in the calling function
list = [1, 2, 3]
id(list) #=> 4432037256
def func(arg):
id(arg)
func(list) #=> 4432037256
# Note: Sets are unordered collections of unique and immutable objects
x = set('Spam')
y = {'m', 'e'}
x, y #=> ({'S', 'p', 'a', 'm'}, {'m', 'e'}) a tuple of two sets without parentheses
x & y #=> {'m'} intersection
x.intersection(y) #=> {'m'} intersection
x | y #=> {'S', 'p', 'a', 'm', 'e'} union
x - y #=> {'a', 'p', 'S'} differences from `y`
x > y #=> False superset
x.add('m') #=> insert one item at a time
x.update(y) #=> merge (union)
x.remove('m') #=> remove one item at a time
# Set comprehensions
{n ** 2 for n in [1, 2, 3]} #=> {9, 1, 4}
# Filtering out duplicates
list(set([1, 2, 2, 3, 4, 4])) #=> [1, 2, 3, 4]
# Finding differences in collections
set('Spam') - set('me') #=> {'S', 'p', 'a'}
# Testing equality
set('Spam') == set('maSp') #=> True
# Testing set items and other collections
'p' in set('Spam'), 's' in list('Spam'), 'h' in 'hello' #=> (True, False, True)
# Note: String is immutable, they cannot be changed in place after
# they are created.
s = 'hello' # Make a 5 character string, and assign it to a name
multiline = """hello
world""" #=> 'hello\n\tworld'
len(s) #=> 5
s[0] #=> 'h'
s[4] #=> 'o'
s[-1] #=> 'o' last item from the end
s[-2] #=> 'l'
s[1:3] #=> 'el' slice of `s` offsets from 1 through 2 (not 3)
s[1:] #=> 'ello' everything past the first character
s[0:3] == s[:3] #=> True
s + ' world' #=> 'hello world' concatenation
s.__add__('world') #=> 'hello world' concatenation
s * 3 #=> 'hellohellohello' repetition
# String methods
s.find('ll') #=> 2 offset if a substring in `s`
s.replace('ll', 'LL') #=> 'heLLo' replace occurrences of a string in `s` with another
s.upper() #=> 'HELLO' upper-case conversion
s.lower() #=> 'hello' lower-case conversion
line = 'hello,world,!'
line.split(',') #=> ['hello', 'world', '!'] split on a delimiter `,` into a list of substrings
'{0} {1}'.format('hello', 'world') #=> 'hello world'
'{} {}'.format('hello', 'world') #=> 'hello world'
# Pattern matching
import re
match = re.match('hello[ \t]*(.*)world', 'hello python world')
match.groups() #=> ('python ',)
match.group(1) #=> 'python '
match = re.match('[/:](.*)[/:](.*)[/:](.*)', '/usr/home:babygau')
match.groups() #=> ('usr', 'home', 'babygau')
re.split('[/:]', '/usr/home/babygau') #=> ['', 'usr', 'home', 'babygau']
# Note: Tuples are immutable like Strings
# Tuples like a list, but support fewer operation. They are
# not generally used as often as list in practice, but their
# immutability is the key point. If you pass a collection of
# objects around your program as a list, it can be changed
# anywhere; if you use tuple, it cannot.
t = (1, 2, 3, 4)
len(t) #=> 4
t + (5, 6) #=> (1, 2, 3, 4, 5, 6) concatenation
t[1:3] #=> (2, 3)
t.index(4) #=> 3
t.count(4) #=> 1
t[0] = 7 #=> Error because `tuples` are immutable
t = 'Spam', 1, 2.3, [4, 5, 6] # `Tuples` support mixed types and nesting
# Parenthese is OPTIONAL
tuple('Spam') #=> ('S', 'p', 'a', 'm')
tuple([1, 2, 3]) #=> (1, 2, 3)
tuple({'a': 1, 'b': 2}) #=> ('a', 'b')
# Use `namedtuple`
# Reference: http://stackoverflow.com/questions/2970608/what-are-named-tuples-in-python
from collections import namedtuple
Point = namedtuple('Point', 'x y')
pt1 = Point(1.0, 5.0) #=> Point(x=1.0, y=5.0)
pt2 = Point(2.5, 1.5) #=> Point(x=2.5, y=1.5)
pt1.x == pt1[0] #=> True
pt2.y == pt2[1] #=> True
from math import sqrt
line_length = sqrt((pt1.x-pt2.x)**2 + (pt1.y-pt2.y)**2)
# Super-class
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def last_name(self):
return self.name.split()[-1]
def give_raise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __repr__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
# Sub-class
class Manager(Person):
def __init__(self, name, pay):
Person.__init__(self, name, 'mgr', pay)
def give_raise(self, percent, bonus=.10) # Override superclass method
Person.give_raise(self, percent + bonus)
# Embedding-based class (composite class)
# make use of `__getattr__` operator overloading
# However, note that: generic attribute managers such as
# `__getattr__` or `__getattribute__` never implicitly invoke
# built-in operator overlading methods such as `__repr__`, __str__`
# We have to define them explicitly if we want to use
class Manager
def __init__(self, name, pay):
self.person = Person(name, 'mgr', pay) # Embed a `Person` instance
def give_raise(self, percent, bonus=0.10):
self.person.give_raise(percent + bonus) # Intercept and delegate
def __getattr__(self, attr):
return getattr(self.person, attr) # Delegate all undefined attrs to `self.person`
# In other words, extending all `Person` attrs
def __repr__(self): # Must overload again in 3.X
return str(self.person)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment