2016-12-07 7 views
1

я написал простой декоратор:декоратор: понять, почему он не смывать локальные переменные

from functools import wraps 
import random 

def my_dec(f): 
    lst = list() 

    @wraps(f) 
    def wrapper(*args): 
     lst.append(random.randint(0, 9)) 
     print(lst) 
     return f(*args) 

    return wrapper 

@my_dec 
def foo(): 
    print("foo called") 

Теперь, если я позвоню foo несколько раз lst не смывания. Вместо этого он наращивается с течением времени. Таким образом, многочисленные вызовы foo возвращают выход так:

foo() 
> [4] 
> foo called 

foo() 
> [4, 9] 
> foo called 

foo() 
> [4, 9, 1] 
> foo called 

... 

Почему? Я думал, что decorator - это просто синтаксический сахар для my_dec(foo)?! Я предположил, что каждый звонок my_dec сбрасывает lst.

+2

Вы правы, что декоратор является просто синтаксическим сахаром для 'foo = my_dec (foo)' ... Но я не вижу, как это приведет вас к мысли, что 'lst' будет« покрасневшим ». Будьте осторожны: «my_dec» вызывается только один раз, независимо от того, сколько раз вы называете 'foo' ... – mgilson

+0

О да, вы правы. Я думал, что 'my_dec' вызывается каждый раз, когда вызывается' foo', но, очевидно, это не так, поскольку 'foo = my_dec (foo)' происходит только один раз. Спасибо за разъяснения! – daniel451

+0

Дополнительный вопрос: могу ли я получить доступ к 'lst' каким-то образом? – daniel451

ответ

3

Вы правы ... Декоратор - это просто синтаксический сахар. В частности:

@decorator 
def foo(): 
    pass 

является точно то же самое:

def foo(): 
    pass 
foo = decorator(foo) 

Давайте немного более диковинные и переписать это еще один способ, который в основном эквивалент :

def bar(): 
    pass 
foo = decorator(bar) 
del bar 

Надеемся, что так получилось, что вы можете видеть, что если Я звоню foo кучу раз, я не звонил decorator кучу раз. decorator получил только один раз (чтобы помочь создать foo).

Теперь в вашем примере, ваш декоратор создает список сразу, когда она вызывается:

def my_dec(f): 
    lst = list() # list created here! 

    @wraps(f) 
    def wrapper(*args): 
     lst.append(random.randint(0, 9)) 
     print(lst) 
     return f(*args) 

    return wrapper 

Функция вернула wrapper получает назначение на ваш foo, поэтому, когда вы звоните foo, вы звоните wrapper. Обратите внимание: нет кода в wrapper, который бы сбросил lst - только код, который добавит больше элементов в lst, поэтому здесь нет ничего, чтобы указать lstдолжно получить «покраснение» между вызовами.

(в зависимости от того, что делает декоратор, вы можете увидеть некоторые различия в атрибуте функция в __name__, но в остальном это то же самое ...)


Также обратите внимание, что вы» У меня есть один lst для каждого вызова декоратора. Мы можем сойти с ума с этим, если мы, как и украсить foo дважды:

@my_dec 
@my_dec 
def foo(): 
    pass 

Или мы можем украсить более чем одну функцию:

@my_dec 
def foo(): 
    pass 

@my_dec 
def bar(): 
    pass 

И тогда, когда мы называем foo и bar, мы будем видеть, что каждый из них накапливает свои (отдельные) списки случайных чисел.Другими словами, каждый раз, когда ваш декоратор применяется к чему-то, будет создан новый список, и каждый раз, когда вызывается «что-то», список будет расти.

+0

Хорошо поставленный - приятное объяснение! Дополнительный вопрос: каждая украшенная функция 'foo_n' получает свой собственный' lst', правильно? Таким образом, я мог бы запомнить такие вещи таким образом? Есть ли способ получить доступ к 'lst'? – daniel451

+1

@ascenator - я просто обновлял свое редактирование, чтобы сказать, что каждый получает его собственный 'lst'. Для доступа к списку не существует _clean_. Вы можете добавить его как атрибут функции ('wrapper') перед тем, как вернуть ее ...' wrapper.lst = lst; return wrapper', но это начинает чувствовать себя немного kludgy – mgilson

+0

Вы имеете в виду, что это не * pythonic *, чтобы сохранить и загрузить значения с помощью декораторов таким образом? Или, как правило, нет чистого способа хранить конкретные данные (и читать позже) с помощью декораторов? Мне это пришло в голову, потому что это звучит как хорошая идея для хранения ценностей таким образом. В противном случае нужно было бы делать это вручную каждый раз, когда вызывается 'foo_n'. – daniel451