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

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

@reversedNames
def guys():
   return 'иксйичаБ йалокиН', 'венъК нафетС', 'вортимиД рътимиД'

21.05.2008

Декоратори

Декоратори наричаме функциите 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)

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

Декоратор, който:

Имате два варианта да го направите

stubbornOpen = withRetries(3, open)

stubbornOpen = withRerties(3)(open)

Ще предпочетем втория вариант

withRetries(number)

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

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

withRetries(number) (2)

def withRetries(number):
    def decorator(func):
        def retrying(*args, **kwargs):
            retriesLeft = number
            while retriesLeft:
                try: return func(*args, **kwargs)
                except: retriesLeft -= 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 nameRegister():
        return [_.name for _ in Person._people]
    nameRegister = staticmethod(nameRegister)

Грозният, грозният и другият грозен

И все пак…

def larodi(number):
    return doStuff(number)
larodi = decorator(larodi)

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

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

@memoized
def fib(n):
    # ...
    return result

class Person(object):
    # ...
    @staticmethod
    def nameRegister():
        return [_.name for _ in Person._people]

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

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 = classmethod(work)

или:

work = classmethod(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 #%d of '%s' should have been "
                                 "of type %s" % (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 '%s %s' % (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 getName(self):
        return '%s %s' % (self.first, self.last)
    def setName(self):
        self.first, self.last = value.split(None, 1)
    def __getattr__(self, attr):
        if 'name' == attr:
            return self.getName()
        return object.__getattr__(self, attr)
    def __setattr__(self, attr, value):
        if 'name' == attr:
            self.setName(value)
        else:
            object.__setattr__(self, attr, value)

Решение 1

class Person(object):
    def __init__(self, first, last):
        self.first, self.last = first, last
    def getName(self):
        """Full name"""
        return '%s %s' % (self.first, self.last)
    def setName(self, value):
        self.first, self.last = value.split(None, 1)
    name = property(getName, setName)

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

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

Дескриптори

class TypedAttr(object):
    def __init__(self, type, initial):
        self.initial, self.type = initial, type
        self.values = {}
    def __get__(self, instance, owner):
        print self.values
        return self.values.get(id(instance), self.initial)
    def __set__(self, instance, value):
        self.values[id(instance)] = self.type(value)
	def __delete__(self, instance):
		del self.values[id(instance)]

class C(object):
    n = TypedAttr(int, 0)
    s = TypedAttr(str, 'baba')

c = C()
c.n = 11
print c.n
c.n = 'baba'

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

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

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

Още един опит

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

with

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

with в Python 2.5

Малък пример

from __future__ import with_statement 
 
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

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 __future__ import with_statement
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()

contextmanager

contextmanager е декоратор, който превръща генератор функция в context manager:
from __future__ import with_statement
from contextlib import contextmanager

@contextmanager
def entering(whom):
    print "I've been entered by %s" % whom
    yield "ticket"
    print "I've been exited!"

with entering("someone") as something:
    print "Am I inside?"
    print something

Pearl Jam FTW!!!

Още въпроси?