2010-01-08 2 views
2

Я думал, что понял декораторов, но не больше. Выполняют ли декораторы только при создании функции?Python: Очень растерялся о декораторах

Я хотел создать ряд функций, для которых у всех есть необходимый аргумент, называемый «ticket_params», который является словарем. а затем украсить их чем-то вроде @param_checker(['req_param_1', 'req_param_2']), а затем, если «req_param_1» и «req_param_2» не находятся в словаре, поднимите пользовательский подкласс «Исключение». Я думаю, что все это неправильно?

Было бы что-то подобное в вызывающем коде:

@param_checker(['req_param_1', 'req_param_2']) 
def my_decorated_function(params): 
    # do stuff 

params = {'req_param_1': 'Some Value'} 
my_decorated_function(params) 

# exception would be raised here from decorator. 

ответ

11

Декоратор применяется сразу после заявления def; эквивалентность является:

@param_checker(['req_param_1', 'req_param_2']) 
def my_decorated_function(params): 
    # do stuff 

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

def my_decorated_function(params): 
    # do stuff 
my_decorated_function = param_checker(['req_param_1', 'req_param_2'])(my_decorated_function) 

Так работа param_checker является возвращение функции, которая принимает в качестве аргумента функции должны быть оформлены и возвращает еще другая функция, которая делает то, что вам нужно. До сих пор?

Edit: так, вот одна реализация ...:

import functools 

def param_checker(reqs): 
    reqs = set(reqs) 
    def middling(f): 
    @functools.wraps(f) 
    def wrapper(params): 
     missing = reqs.difference(params) 
     if missing: 
     raise TypeError('Missing parms: %s' % ', '.join(sorted(missing))) 
     return f(params) 
    return wrapper 
    return middling 
+1

Иногда я думаю, что у меня довольно хорошее понимание чего-то на Python, а затем Алекс * объясняет * это, и я понимаю, что я вдруг не понимаю его снова. Теперь мне придется провести медитацию на выходных на комбинате functools и bellybutton lint. –

+0

@Peter, два других ответа объясняют аргументы, принимающие декораторы как классы, - принимая аргументы в '__init__', а затем функцию для украшения в' __call__'. Это тоже нормально, если реальная вещь (декоративные декораторы - функции, возвращающие функцию более высокого порядка - в частности: возврат функции, которая принимает функцию и возвращает функцию ;-) чувствует себя слишком странно ;-). Но приведенный выше пример действительно довольно прост (и 'functools.wraps' - просто аккуратное прикосновение, чтобы сохранить имя и docstring украшенной функции над будущей интроспекцией ...!). –

+0

my_decorated_function = param_checker (['req_param_1', 'req_param_2']) (my_decorated_function). Я этого не понимаю. Что происходит, помещая украшенную функцию в parens после утверждения и как она реагирует на то, чтобы быть рядом с вызовом функции? – orokusaki

5

Декораторы только вызывается один раз на функцию, то есть, когда def оператор разобран так:

@mydecorator 
def myfunction(): ... 

Я предполагаю, что вы имеете в виду что-то вроде этого:

class param_checker: 
    def __init__(self, l): 
    self.l = l 

    def __call__(self, functionToBeDecorated): 
    def wrapper(*args, **kwargs): 
     if any(necessary not in kwargs["ticket_params"] for necessary in self.l): 
     raise MyCustomException 
     return functionToBeDecorated(*args, **kwargs) 

    return wrapper 

Скажите, пожалуйста, 't понять это;)

+0

BTW в вашем примере это, конечно, проще сделать простое утверждение вместо того, чтобы использовать декоратор. – AndiDog

+0

Означает ли это, что каждый раз, когда функция вызывается, функция переписывается динамически? – orokusaki

+0

Я не понимаю, как возвращает объект функции «обертка» что-либо. Это потому, что нам не нужно называть это, потому что это происходит в процессе вызова? Раньше я бы предположил, что вместо этого возвращаю wrapper(), но теперь я понимаю. Также, как сделать утверждение вместо этого. Спасибо за этот просветляющий ответ, хотя, потому что я не понимал, что вы можете в основном переопределить __call __() на функции, выполнив это. – orokusaki

3

Вот полный пример, основанный на @ AndiDog пример М.. Помните, что любой вызываемый может использоваться как декоратор, он не должен быть классом.

class MyCustomException(Exception): 
    pass 

# The decorator - instances of this class are callable as it implements __call__ 
class param_checker: 
    # In this example l is the parameter you pass to the decorator. 
    # For example, l could be ['req_param_1', 'req_param_2']. 
    def __init__(self, l): 
     self.l = l 

    # This makes the instance callable 
    def __call__(self, functionToBeDecorated): 
     def wrapper(*args, **kwargs): 
      # For the successful call below args =() and 
      # kwargs = {'ticket_params': {'req_param_1': 'param_1', 'req_param_2': 'param_2'}} 
      if "ticket_params" not in kwargs or any(necessary not in kwargs["ticket_params"] for necessary in self.l): 
       # if the ticket params parameter has not been specified, or if 
       # any of the required parameters are not present raise an exception 
       raise MyCustomException 
      return functionToBeDecorated(*args, **kwargs) 
     return wrapper 

@param_checker(['req_param_1', 'req_param_2']) 
def myfunction(ticket_params=None): 
    # if the two required params are present this will print 
    print "params ", ticket_params 

if __name__ == "__main__": 
    try: 
     myfunction() 
    except MyCustomException: 
     print "all required params not supplied" 
    try: 
     myfunction(ticket_params={'req_param_1': 'param_1'}) 
    except MyCustomException: 
     print "all required params not supplied" 
    myfunction(ticket_params={'req_param_1': 'param_1', 'req_param_2': 'param_2'}) 
+0

Большое спасибо. Я закончил тем, что принял ответ Алекса, потому что он помог мне понять базовые декораторы функций совсем немного, но ответ Анди поднял вопросы, на которые ответил мой ответ. Тем не менее, я проголосовал за все 3 ваших ответа. – orokusaki

+0

Кажется, в этой статье self.l фактически был бы объектом функции: http://www.artima.com/weblogs/viewpost.jsp?thread=240808 – orokusaki

+0

Я бы тоже принял Алекса, t действительно отвечая на вопрос, просто хотел дать вам какой-то рабочий код для игры. Не уверен, что вы подразумеваете под вторым комментарием. –

2

Проверьте ответ Алекса, чтобы понять декораторы питона; кстати:

1) что вы не понимаете декораторов? Разве вы не понимаете декораторов как общую концепцию или декораторов Python? Опасайтесь, «классический» узор декоратора, аннотации java и декораторы питона - это разные вещи.

2) Декораторы python должны всегда возвращать функцию, например. в вашем коде возвращаемое значение param_checker ([...]) должно быть функцией, которая принимает функцию как параметр (func для оформления) и возвращает функцию с той же сигнатурой, что и my_decorated_function. Взгляните на следующий пример; функция декоратора выполняется только один раз (когда создается класс), тогда как украшенный func затем выполняется при каждом вызове. В этом конкретном примере он затем вызывает исходную функцию, но это не является обязательным требованием.

def decorator(orig_func): 
    print orig_func 

    def decorated(self, a): 
     print "aahahah", orig_func(self, a) 

    return decorated 


class Example(object): 
    @decorator 
    def do_example(self, a): 
     return 2 * a 


m = Example() 
m.do_example(1) 

3) вы, возможно, не делаете лучшего в том, как вы используете декораторы. Их обычно следует использовать, когда есть понятие, которое довольно ортогонально тому, что вы на самом деле программируете, и его можно использовать повторно - это, по сути, способ POPON для создания АОП.Ваш param_checker может быть не ортогональным - если ваш декоратор просто используется один раз, то, вероятно, это не очень хорошее место для использования декоратора. Кажется, ваш param_checker - это предполагает, что украшенный func принимает один аргумент, который является словарем, - есть ли в вашем коде много funcs с такой подписью и поведением? Если ответ «нет», просто проверьте параметры в начале func и вызовите исключение, если они отсутствуют.

0

Это хорошее объяснение для тех, кто задают тот же вопрос:

# This does nothing. 

class donothing(object): 
    def __init__(self, func): 
     """ 
     The 'func' argument is the function being decorated because in this 
     case, we're not instantiating the decorator class. Instead we are just 
     using the class object as a callable (a class is always callable as this 
     is how an instance is returned) to use as a decorator, which means that 
     it is being instantiated upon definition of the decorated function and 
     the decorated function is being passed in as an argument to the class's 
     __init__ method. 
     """ 
     self.func = func 

    def __call__(self, *args, **kwargs): 
     """ 
     The __call__ function is called when the decorated function is called 
     because the function has be eaten by the decorator class. Now it's up to 
     the this method to return a call to the original function. The arguments 
     are passed in as args, kwargs to be manipulated. 
     """ 
     # Returns original function call with original arguments. 
     return self.func(*args, **kwargs) 

@donothing 
def printer(text): 
    print(text) 

printer('hello world') 

# The printer function is now an alias for the donothing instance created, so 
# the preceding was the same as: 
# 
# instance = donothing(printer) 
# instance('hello world') 
# 


# Next example: 

class checkforkeysinparams(object): 
    def __init__(self, required): 
     self.required = set(required) 

    def __call__(self, params): 
     def wrapper(params): 
      missing = self.required.difference(params) 
      if missing: 
       raise TypeError('Missing from "params" argument: %s' % ', '.join(sorted(missing))) 
     return wrapper 


# Apply decorator class, passing in the __init__'s 'required' argument. 

@checkforkeysinparams(['name', 'pass', 'code']) 
def complex_function(params): 
    # Obviously these three are needed or a KeyError will be raised. 
    print(params['name']) 
    print(params['pass']) 
    print(params['code']) 


# Create params to pass in. Note, I've commented out one of the required params. 

params = { 
    'name': 'John Doe', 
    'pass': 'OpenSesame', 
    #'code': '1134', 
} 

# This call will output: TypeError: Missing from "params" argument: code 

complex_function(params=params)