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

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

@monkey
def lecture():
   pass

Декоратори

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

…, лошият и грозният

И все пак…

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

fib = memoize(fib)

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

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

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

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

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

@memoize('/tmp/fibs')
def fib(n):
    ...

е равно на

def fib(n):
    ...
fib = memoize('/tmp/fibs')(fib)

Да не се бърка с fib = memoize('/tmp/fibs', fib)

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 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 = staticmethod(work)

или:

work = staticmethod(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

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

Още въпроси?