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

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

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

25 март 2015

Предговор

Области на видимост

global_one = 1


def foo():
    local_one = 2
    print(locals())


print(globals())    # {..., 'global_one': 1}
foo()               # {'local_one': 2}

Области на видимост(2)

Области на видимост(3)

А какво ще изведе следният код?

global_one = 1


def foo():
    global_one = 2
    print(global_one)
    print(locals())


foo()

class Foo:
   global_one = 2
   print(global_one)
   print(locals())

print(globals())

Животът на една променлива

Една променлива "умира" заедно със своя скоуп

Аргументи

Вложени функции

def outer(x):
    print(x)
    def inner():
        x = 0
        print(x)
    inner()
    print(x)

Функциите са първокласни обекти

Closures

Имаме closure, когато вложена функция достъпва променлива, дефинирана в обграждаща функция

def start(x):
    def increment(y):
        return x + y
    return increment

first_inc = start(0)
second_inc = start(8)

first_inc(3)
second_inc(3)

first_inc(1)
second_inc(2)

Един сериозен проблем

def spam(n):
    spams = ("spam", ) * (n - 1)
    return "I would like {} and spam".format(", ".join(spams))

def eggs(n):
    return "I would like {} eggs".format(n)

Обноски

Начин 1

def spam(n, server):
    spams = ("spam", ) * (n - 1)
    return "I would like {} and spam, dear {}".format(", ".join(spams), server)

def eggs(n, server):
    return "I would like {} eggs, dear {}".format(n, server)

spam(3, "sir")

Допреди малко говорихме за функции

def spam(n):
    spams = ("spam", ) * (n - 1)
    return "I would like {} and spam".format(", ".join(spams))

def eggs(n):
    return "I would like {} eggs".format(n)

def served_by(func, server):
    def cached_server(n):
        return "{}, dear {}".format(func(n), server)
    return cached_server

eggs = served_by(eggs, "sir")
spam = served_by(spam, "sir")

Да благодарим

def thank_you(func):
    def with_thanks(n):
        return "{}. Thank you very much!".format(func(n))
    return with_thanks

eggs = thank_you(served_by(eggs, "sir"))
spam = served_by(spam, "sir")

Започна да става сложно

Фибоначи.

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])

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

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

memorize

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

fibonacci = memorize(fibonacci)

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

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

fibonacci = memorize(fibonacci)

Декорацията става след дефиницията на функцията.

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

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

@memorize
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) # 625
#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))

Яйца?

def served_by(server):
    def decorator(func):
        def cached_server(n):
            return "{}, dear {}".format(func(n), server)
        return cached_server
    return decorator

def thank_you(func):
    def with_thanks(n):
        return "{}. Thank you very much!".format(func(n))
    return with_thanks

@served_by("sir")
def spam(n):
    spams = ("spam", ) * (n - 1)
    return "I would like {} and spam".format(", ".join(spams))

@thank_you
@served_by("sir")
def eggs(n):
    return "I would like {} eggs".format(n)

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

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

@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}' is not {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 към атрибут само за четене със същото име

b = Battery()
print(b.voltage) # 100000

@property си има и setter

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

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

@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)

Въпроси?