Декоратори и with

„ Програмиране с Python“, ФМИ

2010/05/03

Декоратори

Декоратори наричаме функциите f от вида:

Резултата е нова функция, разширяваща функционалността на аргумента си.

Memoization

def memoize(func):
    memory = {}
    def memoized(*args):
        if args in memory: return memory[args]
        result = func(*args)
        memory[args] = result
        return result
    return memoized

def fib(x):
    if x in [0, 1]: return 1
    return fib(x - 1) + fib(x - 2)

fib = memoize(fib)
print(fib(33))

…, лошият и грозният

И все пак…

def fib(x):
    if x in [0, 1]: return 1
    return fib(x - 1) + fib(x - 2)

fib = memoize(fib)

…е грозно. А и има шанс да не видите декоратора, понеже е отдолу.

Клинт Ийстууд

@memoized
def fib(n):
    if x in [0, 1]: return 1
    return fib(x - 1) + fib(x - 2)

Динамични декоратори

Декоратор, който приема параметри.

@memoize('/tmp/fibs')
def fib(n):
    ...

е равно на

def fib(n):
    ...
fib = memoize('/tmp/fibs')(fib)

Да не се бърка с fib = memoize('/tmp/fibs', fib)

with_retries(number)

Трябва да направим функция with_retries(number), която да връща декоратор. Тя изглежда така:

def with_retries(number):
    def decorator(func):
        """Тяло на декоратора, виждащо number, тук""" 
        # TODO
    return decorator

with_retries(number) (2)

def with_retries(number):
    def decorator(func):
        def retrying(*args, **kwargs):
            retries_left = number
            while retries_left:
                try: return func(*args, **kwargs)
                except: retries_left -= 1
            return func(*args, **kwargs)
        return retrying
    return decorator

staticmethod и classmethod

Вградените функции staticmethod и classmethod също са декоратори.

class Person(object):
    _people = []
    def __init__(self, name):
        self.name = name
        Person._people.append(self)
        
    def name_register():
        return [_.name for _ in Person._people]
    name_register = staticmethod(name_register)

Друг пример за декоратор

def notifyme(f):
    def logged(*args, **kwargs):
        print(f.__name__, 'was called with', args, 'and', kwargs)
        return f(*args, **kwargs)
    return logged
    
@notifyme
def square(x): return x*x
    
res = square(25)
#square was called with (25,) and {}.

Няколко декоратора на една функция

class Mityo:
    @staticmethod
    @notifyme
    def work(): pass

Mityo.work()
work was called with () and {}

Горният код прави същото като:

def work(): pass
work = notifyme(work)
work = staticmethod(work)

или:

work = staticmethod(notifyme(work))

Първо се извикват най-вътрешните декоратори.

В лов на патици

@accepts(int, int)
def add(a, b): return a+b
add = accepts(int, int)(add)

код > думи

def accepts(*types):
  def accepter(f):
    def decorated(*args):
      for (i, (arg, t)) in enumerate(zip(args, types)):
        if not isinstance(arg, t):
          raise TypeError("Argument #{0} of '{1}' should have been " \
             "of type {2}".format(i, f.__name__, t.__name__))
          #TODO: more complex checks: tuple of a type, list of type
        return f(*args)
      return decorated
  return accepter

За патиците с любов

duck typing е много важна част от философията на Python. @accepts е забавен пример и дори има някои употреби, но избягвайте да го ползвате масово. В повечето случаи губите, а не печелите.

Вградени декоратори

Малка неконсистентност

class Person(object):
    def __init__(self, first, last):
        self.first, self.last = first, last
    def name(self, value=None):
        if value == None:
            return '{0} {1}'.format(self.first, self.last)
        else:
            self.first, self.last = value.split(None, 1)

pijo = Person('Пижо', 'Пендов')
print(pijo.first)
pijo.last = 'Пендов'
print(pijo.last)
print(pijo.name())
pijo.name('Кънчо Кънчев')
print(pijo.last)

Решение 0

class Person(object):
    def __init__(self, first, last):
        self.first, self.last = first, last
    def get_name(self):
        return '{0} {1}'.format(self.first, self.last)
    def set_name(self):
        self.first, self.last = value.split(None, 1)
    def __getattr__(self, attr):
        if 'name' == attr:
            return self.get_name()
        return object.__getattr__(self, attr)
    def __setattr__(self, attr, value):
        if 'name' == attr:
            self.set_name(value)
        else:
            object.__setattr__(self, attr, value)

Решение 1

class Person(object):
    def __init__(self, first, last):
        self.first, self.last = first, last
    def get_name(self):
        """Full name"""
        return '{0} {1}'.format(self.first, self.last)
    def set_name(self, value):
        self.first, self.last = value.split(None, 1)
    name = property(get_name, set_name)

property като декоратор

class Parrot(object):
    def __init__(self):
        self._voltage = 100000

    @property
    def voltage(self):
        """Get the current voltage."""
        return self._voltage

Meyer's substitution principle

Атрибутите на един обект трябва да бъдат достъпвани през хомогенна нотация, която не издава дали те се изчисляват или са записани.

Пример с файлове

try:
    source_file = open(src, 'r')
    buffer = []
    try:
        buffer = source_file.readlines()
    finally:
        source_file.close()

    target_file = open(target, 'w')
    try:
        for line in reversed(buffer): 
            target_file.write(line)
    finally:
        target_file.close()
except IOError:
    print("Tough luck, junior")

Още един опит

buffer = []
try:
    with open(src) as source_file:
        buffer = source_file.readlines()
    with open(target) as target_file:
        for line in reversed(buffer):
            target_file.write(line)
except IOError:
    print("Much better, now, ain't it?")

with

with израз [as име]:
    блок

with нагледно


with open('/etc/passwd') as source_file:
    buffer = source_file.readlines()
print('Done!')

е същото като


source_file = open('/etc/passwd').__enter__()
try:
  buffer = source_file.readlines()
  source_file.__exit__(None, None, None)
except Exception:
  source_file.__exit__(*sys.exc_info())
print('Done!')

Малък пример

 
class Manager: 
    def __enter__(self): 
        print("I've been entered!")
        return 42 
    def __exit__(self, type, value, traceback): 
        print("I've been exited!")
 
with Manager() as something: 
    print("Am I inside?")
    print(something)
I've been entered!
Am I inside?
42
I've been exited!

contextlib

Вграденият модул contextlib ни предлага три много полезни Context Manager-а:

closing

contextlib.closing вика метода close на обекта, с който работим, след изпълнение на блока:
class closing(object):
    def __init__(self, thing): self.thing = thing
    def __enter__(self): return thing
    def __exit__(self, type, value, traceback): self.thing.close()

…и ви позволява да пишете следното:

from contextlib import closing
import codecs

with closing(urllib.urlopen('http://www.python.org')) as page:
    for line in page:
        print(line)

nested

Този код:

from contextlib import nested

with nested(A, B, C) as (X, Y, Z):
    do_something()

…е еквивалентен на:

with A as X:
    with B as Y:
        with C as Z:
            do_something()

Още въпроси?