Програмиране с Python: Декоратори и with
Author: Стефан Кънев, Николай Бачийски и Димитър Димитров

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

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

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

#### 21.05.2008

# Декоратори

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

- `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` е забавен пример и дори има някои употреби, но избягвайте да го ползвате масово. В повечето случаи губите, а не печелите.

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

- `classmethod` — прави метода класов (приема клас, а не обект като първи аргумент)
- `staticmethod` — прави метода статичен
- `property`

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

    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(fget=None, fset=None, fdel=None, doc=None)`
- Работи само за класове от нов тип
- Doc-низът се взима от `fget`, ако `doc` е `None`

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

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

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

- Това превръща `voltage` в getter към атрибут само за четене със същото име

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

- Резултатът от израза се нарича Context Manager
- Изпълнява се метода `__enter__()` на CM и резултатът се записва в името след `as`
- Изпълнява се блока
- Ако е настъпило излючение се изпълнява `__exit__(type, value, traceback)` на CM
- Ако не е настъпило излючение се изпълнява `__exit__(None, None, None)` на CM

# with в Python 2.5

- `with` се повявява за първи път в Python 2.5
- В python 2.5 трябва да ползваме `from __future__ import with_statement`
- В 2.6 вече ще е наразделна част от езика

# Малък пример

    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`
- `nested`
- `contextmanager`

# 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

- yield изпълнява блока и аргумента му става обекта, с който работи блока

Pearl Jam FTW!!!

# Още въпроси?

- Страница на курса: http://fmi.py-bg.net/
- Форуми на курса: http://fmi.py-bg.net/forums/