Advanced Python

Author: Gyuri Horak
Date: 2011-05-27

Diamond inheritance problem

Cls.__mro__ - method resolution order

py> class A(object): pass
py> class B(A): pass
py> class C(A): pass
py> class D(B,C): pass
py> D.__mro__
(<class '__main__.D'>,
 <class '__main__.B'>,
 <class '__main__.C'>,
 <class '__main__.A'>,
 <type 'object'>)

Mixins

You can add baseclasses runtime

class A(object): pass
class B(object): pass
class C(A): pass

py> C.__bases__ += (B,)
py> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>,
 <class '__main__.B'>, <type 'object'>)

Variable scope

Variable scope

def scopetest(mut, imut):
  global gvar
  gvar = 11
  lvar = 12
  mut += [2]
  imut += "apple"
  print locals()
  # {'mut': [1, 2], 'lvar': 12, 'imut': 'pineapple'}

gvar, lvar, amut, aimut = 1, 1, [1], "pine"
print locals()
# {'__builtins__': <..>, 'scopetest': <function ..>,
# '__file__': 'scope.py', 'amut': [1], 'gvar': 1,
# '__package__': None, 'lvar': 1, 'aimut': 'pine',
# '__name__': '__main__', '__doc__': None}
scopetest(amut, aimut)
print locals()
# {'__builtins__': <..>, 'scopetest': <function ..>,
# '__file__': 'scope.py', 'amut': [1, 2], 'gvar': 11,
# '__package__': None, 'lvar': 1, 'aimut': 'pine',
# '__name__': '__main__', '__doc__': None}

Iterators

py> a = [1,2,3,4,5]
py> i = a.__iter__()
py> i.next()
1
py> i.next()
2

Generators, yield

Generate the next item only when we need it

class Fib(object):
  def __init__(self, limit):
    self.limit = limit
  def __iter__(self):
    p0, p = 0, 1 # tuple pack/unpack
    while p < self.limit:
      yield p
      p0, p = p, p+p0
    raise StopIteration

py> f100 = Fib(100)
py> for x in f100:
...   print x,
1 1 2 3 5 8 13 21 34 55 89

List comprehensions

The functional way of list creation

py> [x+1 for x in f100]
[2, 2, 3, 4, 6, 9, 14, 22, 35, 56, 90]

In Python 3 dicts and sets can be created this way as well

Itertools

Useful toolset for iterators

py> from itertools import izip
py> for x,y in izip(xrange(10), f100):
...   print "(%s, %s)" % (x, y),
(0, 1) (1, 1) (2, 2) (3, 3) (4, 5) (5, 8) \
(6, 13) (7, 21) (8, 34) (9, 55)

Coroutines (PEP-342)

def grep(pattern):
  print "Looking for %s" % pattern
  while True:
    line = (yield)
    if pattern in line:
      print line

py> g = grep("python")
py> g.next()
py> g.send("No snakes here.")
py> g.send("Generators in python ROCK!")
Generators in python ROCK!
py> g.close()

Operator overloading

class Fun(object):
  def __init__(self, function):
    self.function = function
  def __call__(self, *args, **kwargs):
    return self.function(*args, **kwargs)
  def __add__(self, funinst):
    def inner(*args, **kwargs):
      return funinst(self.function(*args, **kwargs))
    return Fun(inner)

py> f1 = Fun(a)
py> f1(1,2) # 3
py> f2 = Fun(lambda x: x*x)
py> f2(2) # 4
py> f3 = f1+f2
py> f3(1,2) # 9

__getattr__

class JSObj(dict):
  def __getattr__(self, attr):
    return self.get(attr)
  def __setattr__(self, attr, value):
    self[attr] = value

py> o = JSObj({'a': 10, 'b': 20})
py> o.a # 10
py> o.c # None
py> o.c = 30
py> o['c'] # 30
py> o.c # 30

__dict__

class Borg(object):
  __shared_state = {}
  def __init__(self):
    self.__dict__ = self.__shared_state

py> a = Borg()
py> b = Borg()
py> a.x = 42
py> b.x
42
py> a == b
False

http://docs.python.org/reference/datamodel.html#special-method-names

Nothing is private (again)

def hide(o):
  class Proxy(object):
    __slots__ = ()
    def __getattr__(self, name):
      return getattr(o, name)
  return Proxy()

class A(object):
  a = 42

py> a = hide(A())
py> a.a
42
py> a.a = 43
AttributeError: 'Proxy' object has no attribute 'a'
py> a.__getattr__.func_closure[0].cell_contents
<__main__.A object at 0x7faae6e16510>

Decorators

def funct(): pass
funct = decorator(funct)
@decorator
def funct(): pass

Example decorator

def logger(f):
  def inner(*args, **kwargs):
    print "%s called with arguments: %s; %s" % (f.__name__, args, kwargs)
    retval = f(*args, **kwargs)
    print "%s returned: %s" % (f.__name__, retval)
    return retval
  return inner

@logger
def mul(a,b):
  return a*b

py> mul(1,2)
mul called with arguments: (1, 2); {}
mul returned: 2

Descriptors

Properties

class PropEx(object):

  __a = 1
  __b = 2

  @property
  def c(self):
    return self.__a + self.__b

  def getA(self): return self.__a
  def setA(self, value): self.__a = value
  def delA(self): self.__a = 1

  a = property(getA, setA, delA)
  # @property, @m.setter, @m.deleter

py> x = PropEx()
py> x.a = 12
py> x.c
14
py> x.__class__.__dict__['a'].fset
<function setA at 0x7ff241c266e0>

Functions/methods

def myclass(self):
  return self.__class__

class A(object):
  def method(self):
    pass

py> a = A(); a.__class__.__dict__['method']
<function method at 0x7ff241c26cf8>
py> a.__class__.method
<unbound method A.method>
py> a.method
<bound method A.method of <__main__.A object at 0x7ff242d56bd0>>
py> a.myclass = myclass; a.myclass()
TypeError: myclass() takes exactly 1 argument (0 given) # ???
py> a.myclass
<function myclass at 0x7ff241c26b18> # :(
py> a.myclass = myclass.__get__(a, A); a.myclass()
<class '__main__.A'>
py> a.myclass
<bound method A.myclass of <__main__.A object at 0x7ff242d56bd0>> # :)

type()

py> type(object)
<type 'type'>
py> type(type)
<type 'type'>

metaprogramming

def constr(self):
  print "new instance created"
  self.foo = "foo"

def method(self):
  print "foo: " + self.foo

py> MyClass = type("MyClass",      # classname
                   (dict, object), # baseclasses
                   {'PI': 3.14,    # __dict__
                    'method': method,
                    '__init__': constr})
py> myinstance = MyClass()
new instance created
py> myinstance.method()
foo: foo

__metaclass__

When defined, it is called instead of type at class generation

class mymeta(type):
  def __new__(mcs, name, bases, dict):
    dict['myprop'] = 'metaclass property'
    return type.__new__(mcs, name, bases, dict)

class A(object):
  __metaclass__ = mymeta

py> a = A()
py> a.myprop
'metaclass property'

You can do _anything_ here, like type checking based on docstrings (or py3 annotations), decorating all the methods, create mixins ...

Questions?

Thanks!