08. Декоратори

08. Декоратори

08. Декоратори

27 март 2013

Вече сме ги виждали

@staticmethod
def register(name)
    Person.people.append(name)
    print(len(Person.people), "people are registered now")
[...]
@classmethod
def greet(cls, someone)
    print(someone, "was greeted from", cls)

Предговор

Фибоначи.

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

Рекурсивната версия на fibonacci, освен че е бавна, е много бавна. особено когато x >= 40.

Проблемът е, че fibonacci се извиква стотици пъти с един и същ аргумент. Можем спокойно да прегенерираме първите стотина резултати в един речник или...

Да изчисляваме всеки резултата само по веднъж...

if x not in memory:
    memory[x] = fibonacci(x)
print(memory[x])

Разбира се тази идея може да се използва и на много повече места! Можем да я направим още по-елегантно.

Функции които опаковат други функции

memoize

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

fibonacci = memoize(fibonacci)

Красивият синтаксис

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

fibonacci = memoize(fibonacci)

Декорацията става след дефиницията на функцията, което е малко объркващо.

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

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

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

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

def notifyme(f):
    def logged(*args, **kwargs):
        print(f.__name__, ' 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))

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

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

@memoize('/tmp/fibs')
def fibonacci(n):
[...]

е равно на

def fibonacci(n):
[...]
f = memoize('/tmp/fibs')(fibonacci)

Да не се бърка с

fibonacci = memoize('/tmp/fibs', fibonacci)

На лов за патици

Всъщност, защо да не си направим следния декоратор:

@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
        return f(*args)
      return decorated
  return accepter

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

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

Полезни декоратори

@property

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

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

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

@property си има и setter

@voltage.setter
def voltage(self, value):
    self._voltage = value

Въпроси?