Escolar Documentos
Profissional Documentos
Cultura Documentos
If you want to become an expert in Python, you should definitely watch this PyData talk from James Powell.
Video Index
metaclasses: 18:50
metaclasses(explained): 40:40
decorator: 45:20
generator: 1:04:30
context manager: 1:22:37
summary: 1:40:00
Definitions Python is a language orientated around protocols - Some behavior or syntax or bytecode or some
top level function and there is a way to tell python how to implement that on an arbitrary object via underscore
methods. The exact correspondance is usually guessable, but if you can't guess it you can it... google python
data model
Metaclass Mechanism: Some hook into the class construction process. Questions: Do you have these methods
implemented. Meaning: Library code & User code? How do you enforce a constraint?
Decorator Hooks into idea that everything creates a structure at run time. Wrap sets of functions with a before
and after behavior.
Generators Take a single computation that would otherwise run eagerly from the injection of its parameters to
the final computation and interleaving with other code by adding yield points where you can yield the intermediate
result values or one small piece of the computation and also yield back to the caller. Think of a generator of a way
to take one long piece of computation and break it up into small parts.
Context managers Two structures that allow you to tie two actions together. A setup action and a teardown
action and make sure they always happen in concordance with each other.
In [7]: # some behavior that I want to implement -> write some __ function __
# top-level function or top-level syntax -> corresponding __
# x + y -> __add__
# init x -> __init__
# repr(x) --> __repr__
# x() -> __call__
class Polynomial:
def __init__(self, *coeffs):
self.coeffs = coeffs
def __repr__(self):
return 'Polynomial(*{!r})'.format(self.coeffs)
def __len__(self):
return len(self.coeffs)
def __call__(self):
pass
In [8]: p1 = Polynomial(1, 2, 3)
p2 = Polynomial(3, 4, 3)
In [9]: p1 + p2
In [10]: len(p1)
Out[10]: 3
Metaclasses
In [26]: # File 1 - library.py
class Base:
def food(self):
return 'foo'
In [27]: # File2 - user.py
class Derived(Base):
def bar(self):
return self.foo
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-27-08fa6af0fb76> in <module>()
1 # File2 - user.py
2
----> 3 assert hasattr(Base, 'foo'), "you broke it, you fool!"
4
5 class Derived(Base):
class Base:
def foo(self):
return self.bar()
class Derived(Base):
def bar(self):
return 'bar'
In [45]: Derived.bar
2 0 LOAD_BUILD_CLASS
2 LOAD_CONST 1 (<code object Base at 0x10f2daed0,
file "<ipython-input-18-a194b247271c>", line 2>)
4 LOAD_CONST 2 ('Base')
6 MAKE_FUNCTION 0
8 LOAD_CONST 2 ('Base')
10 CALL_FUNCTION 2
12 STORE_FAST 0 (Base)
14 LOAD_CONST 0 (None)
16 RETURN_VALUE
In [165]: # Catch Building of Classes
class Base:
def foo(self):
return self.bar()
old_bc = __build_class__
def my_bc(*a, **kw):
print('my buildclass ->', a, kw)
return old_bc(*a, **kw)
import builtins
builtins.__build_class__ = my_bc
class Base:
def foo(self):
return self.bar()
old_bc = __build_class__
def my_bc(fun, name, base=None, **kw):
if base is Base:
print('Check if bar method defined')
if base is not None:
return old_bc(fun, name, base, **kw)
return old_bc(fun, name, **kw)
import builtins
builtins.__build_class__ = my_bc
class Base(metaclass=BaseMeta):
def foo(self):
return self.bar()
class Base(metaclass=BaseMeta):
def foo(self):
return self.bar()
class Base(metaclass=BaseMeta):
def foo(self):
return self.bar()
In [8]: help(Base.__init_subclass__)
Decorators
In [12]: # dec.py
Out[14]: 30
In [15]: add
Out[28]: 'add'
Out[27]: '__main__'
Out[26]: (10,)
In [25]: # Byte code for function
add.__code__.co_code
Out[25]: b'|\x00|\x01\x17\x00S\x00'
In [32]: getsource(add)
In [33]: print(getsource(add))
Out[35]: '<ipython-input-19-3cec442ba064>'
add(10) 20
add(20, 30) 50
add("a", "b") ab
elapsed: 0.0
add(10) 20
elapsed: 9.5367431640625e-07
add(20, 30) 50
elapsed: 9.5367431640625e-07
add("a", "b") ab
sub(10) 0
sub(20, 30) -10
elapsed 9.5367431640625e-07
add(10) 20
elapsed 9.5367431640625e-07
add(20, 30) 50
elapsed 9.5367431640625e-07
add("a", "b") ab
elapsed 9.5367431640625e-07
add(10) 20
elapsed 9.5367431640625e-07
add(20, 30) 50
elapsed 9.5367431640625e-07
add("a", "b") ab
In [87]: @timer
def add_dec(x, y=10):
return x + y
@timer
def sub_dec(x, y=10):
return x - y
elapsed 9.5367431640625e-07
add(10) 20
elapsed 1.1920928955078125e-06
add(20, 30) 50
elapsed 1.1920928955078125e-06
add("a", "b") ab
elapsed 9.5367431640625e-07
sub(10) 0
elapsed 0.0
sub(20, 30) -10
@timer_k
def sub_dec(x, y=10):
return x - y
print('add(10)', add_dec(10))
print('add(20, 30)', add_dec(20, 30))
print('add("a", "b")', add_dec("a", "b"))
print('sub(10)', sub_dec(10))
print('sub(20, 30)', sub_dec(20, 30))
elapsed 9.5367431640625e-07
add(10) 20
elapsed 9.5367431640625e-07
add(20, 30) 50
elapsed 9.5367431640625e-07
add("a", "b") ab
elapsed 0.0
sub(10) 0
elapsed 9.5367431640625e-07
sub(20, 30) -10
In [94]: # Let's have add run 3 times in a row and sub run twice in a row
In [97]: n = 2
def ntimes(f):
def wrapper(*args, **kwargs):
for _ in range(n):
print('running {.__name__}'.format(f))
rv = f(*args, **kwargs)
return rv
return wrapper
@ntimes
def add_dec(x, y=10):
return x + y
@ntimes
def sub_dec(x, y=10):
return x - y
In [98]: print('add(10)', add_dec(10))
print('add(20, 30)', add_dec(20, 30))
print('add("a", "b")', add_dec("a", "b"))
print('sub(10)', sub_dec(10))
print('sub(20, 30)', sub_dec(20, 30))
running add_dec
running add_dec
add(10) 20
running add_dec
running add_dec
add(20, 30) 50
running add_dec
running add_dec
add("a", "b") ab
running sub_dec
running sub_dec
sub(10) 0
running sub_dec
running sub_dec
sub(20, 30) -10
@ntimes(2)
def add_hdec(x, y=10):
return x + y
@ntimes(4)
def sub_hdec(x, y=10):
return x - y
In [104]: print('add(10)', add_hdec(10))
print('add(20, 30)', add_hdec(20, 30))
print('add("a", "b")', add_hdec("a", "b"))
print('sub(10)', sub_hdec(10))
print('sub(20, 30)', sub_hdec(20, 30))
running add_hdec
running add_hdec
add(10) 20
running add_hdec
running add_hdec
add(20, 30) 50
running add_hdec
running add_hdec
add("a", "b") ab
running sub_hdec
running sub_hdec
running sub_hdec
running sub_hdec
sub(10) 0
running sub_hdec
running sub_hdec
running sub_hdec
running sub_hdec
sub(20, 30) -10
Generators
In [105]: # gen.py - use whenever sequencing is needd
class Adder:
def __call__(self, x, y):
return x + y
add2 = Adder()
Out[110]: 30
Out[111]: 30
In [113]: # top-level syntax, function -> underscore method
# x() __call__
class Adder:
def __init__(self):
self.z = 0
add2 = Adder()
In [120]: compute()
Out[120]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Wasteful because we have to wait for the entire action to complete and be read into memory, when we really just
care about each number (one by one)
def __iter__(self):
self.last = 0
return self
def __next__(self):
rv = self.last
self.last += 1
if self.last > 10:
raise StopIteration()
sleep(.5)
return self.last
compute = Compute()
def compute():
for i in range(10):
sleep(.5)
yield i
0
1
2
3
4
5
6
7
8
9
Context Manager
In [2]: # cty.py
# x = ctx().__enter__
# try:
# pass
# finally:
# x.__exit__
class temptable:
def __init__(self, cur):
self.cur = cur
def __enter__(self):
print('__enter__')
self.cur.execute('create table points(x int, y int)')
def __exit__(self, *args):
print('__exit__')
self.cur.execute('drop table points')
__enter__
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(9,)
__exit__
In [162]: rm test.db
In [164]: def temptable(cur):
cur.execute('create table points(x int, y int)')
print('created table')
yield
cur.execute('drop table points')
print('dropped table')
class contextmanager:
def __init__(self, cur):
self.cur = cur
def __enter__(self):
self.gen = temptable(self.cur)
next(self.gen)
def __exit__(self, *args):
next(self.gen, None)
created table
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(9,)
dropped table
In [8]: class contextmanager:
def __init__(self, gen):
self.gen = gen
def __call__(self, *args, **kwargs):
self.args, self.kwargs = args, kwargs
return self
def __enter__(self):
self.gen_inst = self.gen(*self.args, **self.kwargs)
next(self.gen_inst)
def __exit__(self, *args):
next(self.gen_inst, None)
def temptable(cur):
cur.execute('create table points(x int, y int)')
print('created table')
yield
cur.execute('drop table points')
print('dropped table')
temptable = contextmanager(temptable)
created table
(1, 1)
(1, 2)
(2, 1)
(2, 2)
dropped table
In [12]: class contextmanager:
def __init__(self, gen):
self.gen = gen
def __call__(self, *args, **kwargs):
self.args, self.kwargs = args, kwargs
return self
def __enter__(self):
self.gen_inst = self.gen(*self.args, **self.kwargs)
next(self.gen_inst)
def __exit__(self, *args):
next(self.gen_inst, None)
@contextmanager
def temptable(cur):
cur.execute('create table points(x int, y int)')
print('created table')
yield
cur.execute('drop table points')
print('dropped table')
created table
(1, 1)
(1, 2)
(2, 1)
(2, 2)
dropped table
In [15]: from sqlite3 import connect
from contextlib import contextmanager
@contextmanager
def temptable(cur):
cur.execute('create table points(x int, y int)')
print('created table')
try:
yield
finally:
cur.execute('drop table points')
print('dropped table')
created table
(1, 1)
(1, 2)
(2, 1)
(2, 2)
dropped table