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

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

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

2009/04/13

Декоратори

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

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

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

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

stubborn_open = with_retries(3, open)

stubborn_open = with_rerties(3)(open)

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

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 larodi(number):
    return do_stuff(number)
larodi = decorator(larodi)

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

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

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

class Person(object):
    # ...
    @staticmethod
    def name_register():
        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 #{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

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

Дескриптори

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:
    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 име]:
    блок

Малък пример

 
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 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 contextlib import contextmanager

@contextmanager
def entering(whom):
    print("I've been entered by {0}".format(whom))
    yield "ticket"
    print("I've been exited!")

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

Pearl Jam FTW!!!

Още въпроси?