2012-06-26 2 views
4

Я новичок в декораторах python. Я понял основные понятия с помощью простых примеров. Но когда я попытался прочитать этот более практичный декоратор, я чувствую себя потерянным. Ниже приведен код, а затем мои вопросы:Невозможно понять этот декоратор python

class countcalls(object): 
    "Decorator that keeps track of the number of times a function is called." 

    __instances = {} 

    def __init__(self, f): 
     self.__f = f 
     self.__numcalls = 0 
     countcalls.__instances[f] = self 

    def __call__(self, *args, **kwargs): 
     self.__numcalls += 1 
     return self.__f(*args, **kwargs) 

    def count(self): 
     "Return the number of times the function f was called." 
     return countcalls.__instances[self.__f].__numcalls 

@countcalls 
def f(): 
    print 'f called' 

f() 
f() 
f() 
print f.count() # prints 3 

Мои сомнения:

  1. Когда префикс декоратора на функцию, это означает, что мы создаем объект права класса декоратор там? В нашем случае, когда он говорит:

    @countcalls def f(): print 'f called'

Является @countcalls эквивалентно созданию countcalls объекта и передавая функцию ниже его метода __init__?

  1. __call__ принимает три аргумента. self в порядке, если на вопрос выше ответили. Какого черта есть два других аргумента: *args, **kwargs и что они достигают?

  2. Как я могу стать лучше на декораторах?

+0

один из моих фаворитов: http://stackoverflow.com/q/739654/1025391 – moooeeeep

+0

Руководство по быстрому стилю, не используйте двойное подчеркивание для личных переменных, используйте только один. –

+1

@ChinmayKanchi: двойное подчеркивание имеет особое значение в Python; [это приводит к искажению имен] (http://docs.python.org/reference/expressions.html#atom-identifiers). –

ответ

5

Этот код имеет некоторую странность. Давайте поговорим о слегка более простой код

class countcalls(object): 

    def __init__(self, f): 
     self._f = f 
     self._numcalls = 0 

    def __call__(self, *args, **kwargs): 
     self._numcalls += 1 
     return self._f(*args, **kwargs) 

    def count(self): 
     return self._numcalls 

@countcalls 
def f(): 
    print 'f called' 

f() 
f() 
f() 
print f.count() 

# output: 
# f called 
# f called 
# f called 
# 3 

Запомнить

@countcalls 
def f(): 
    print 'f called' 

это то же самое, как

def f(): 
    print 'f called' 
f = countcalls(f) 

так, когда мы используем декоратор, функция сохраняется с помощью атрибута _f ,

So f является экземпляром countcalls. Когда вы делаете f(...), вы вызываете f.__call__(...) - вот как вы реализуете синтаксис () для своих собственных экземпляров. Поэтому, когда вы звоните f, что происходит?

def __call__(self, *args, **kwargs): 
     self._numcalls += 1 
     return self._f(*args, **kwargs) 

Во-первых, вы используете *args и **kwargs, что в конденсируются определения все позиционные и ключевые аргументы в кортеже и Словаре, а затем в вызове расширить последовательность и Dict в аргументы (см 4.7.4 в официальный учебник для получения дополнительной информации). Вот пример частичного

>>> def f(*args): print args 
... 
>>> f(1, 2) 
(1, 2) 
>>> f() 
() 
>>> def add(a, b): return a + b 
... 
>>> add(*[4, 3]) 
7 
>>> add(**{'b': 5, 'a': 9}) 
14 

так def f(*args, **kwargs): return g(*args, **kwargs) просто делает передачу из всех аргументов.

Кроме того, вы просто отслеживаете, сколько раз вы были в __call__ для этого экземпляра (сколько раз вы звонили f).

Просто помните, что @decdef f(...): ... такой же, как def f(...): ...f = dec(f), и вы должны быть в состоянии выяснить, большинство декораторов хорошо, учитывая достаточное количество времени. Как и все, практика поможет вам сделать это быстрее и легче.

+0

еще один вопрос: Как 'self .__ f = f' отличается от' self.f = f'? Я знаю, что '__' преобразует его в приватную переменную, но это то, что здесь происходит? – ritratt

+1

Атрибуты, которые называются '__something', называются именами, то есть тогда, когда они хранятся, а не являются« __f »', они фактически хранятся как '_countcalls__f'. Это не делает их фактически частными, просто произносит их немного по-другому. Эта функция не очень часто полезна и может затруднить работу с кодом, поэтому многие из нас избегают этого. –

+0

простите меня, я получил еще одно сомнение. Предположим, мы используем функцию как декоратор вместо класса. Тогда он принимает ОБЪЕКТ функции, которую он украшает правильно? Теперь, если это объект, я должен иметь доступ к его переменным, атрибутам и т. Д. Значит ли это, что я могу получить доступ к переменной, объявленной внутри функции, которая будет украшена из функции декоратора? – ritratt

0
  1. Да, с той оговоркой, что он также связывает этот объект с именем, заданным в опр.

  2. Читайте о них, читайте примеры, играйте с некоторыми.

2

Когда префикс декоратора на функцию, это означает, что мы создаем объект класса декоратора прямо там?

Существует очень простой способ узнать об этом.

>>> @countcalls 
... def f(): pass 
>>> f 
<a.countcalls object at 0x3175310> 
>>> isinstance(f, countcalls) 
True 

Да, да.

Является @countcalls эквивалентно создавая объект countcalls и передавая функцию ниже его инициализации метод?

Практически. Это равносильно тому, что и тогда назначая результат имени функции, т.е.

def f(): 
    print 'f called' 
f = countcalls(f) 

Как я могу получить лучше декораторов?

  1. Практика
  2. Читать Чужие код, который использует их
  3. исследование соответствующие Peps
2
  1. Это эквивалентно следующему:

    f = countcalls(f)

    Другими словами: да, мы создаем объект countcalls и передаем его конструктору конструкцию f.

  2. Это аргументы функции. В вашем случае, f не принимает никаких аргументов, но предположим f был назван так:

    f(3, 4, keyword=5)

    затем *args будет содержать 3 и 4 и **kwargs будет содержать пару ключ/значение keyword=5. Для получения дополнительной информации о *args и **kwargs, см. this question.

  3. Практика делает совершенным.

Смежные вопросы