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

Появяваха се от време на време...

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

Да де, ама е рекурсивна
=> при аргумент x > 40 е изключително бавна
Без значение колко пъти я извикваме с един и същи аргумент

Как да минимизираме излишните сметки?

Повторението е баща на затъпяването

Защо просто не се опитаме да си записваме вече пресмятаните стойности?

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

Защо тогава една функция да не връща друга?

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

Резултатът е нова функция, разширяваща функционалността на първата.

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

        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)
    

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

        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
    

Meyer's substitution principle

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

Още въпроси?