Python: очень запутался в декораторах - PullRequest
2 голосов
/ 09 января 2010

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

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

Это будет что-то вроде этого в коде вызова:

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

Ответы [ 5 ]

11 голосов
/ 09 января 2010

Декоратор применяется сразу после оператора 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 - возвращать функцию, которая принимает в качестве аргумента функцию для декорирования и возвращает еще одну функцию, которая выполняет то, что вам нужно. Хорошо, пока?

Редактировать : вот одна из реализаций ...:

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
5 голосов
/ 09 января 2010

Декораторы для функции вызываются только один раз, то есть когда оператор 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

Пожалуйста, скажите мне, если вы этого не понимаете;)

3 голосов
/ 09 января 2010

Вот полный пример, основанный на примере @ 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'})
2 голосов
/ 09 января 2010

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

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

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

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) вы, возможно, делаете не лучшее, что используете декораторы. Обычно их следует использовать, когда есть какая-то концепция, которая совершенно ортогональна тому, что вы на самом деле программируете, и может использоваться повторно - это, по сути, способ выполнения AOP на Python. Ваш param_checker может быть не таким ортогональным - если ваш декоратор используется только один раз, то, вероятно, вообще не стоит использовать декоратор. Ваш param_checker, кажется, имеет место - он предполагает, что оформленный func принимает один аргумент, являющийся словарем - много ли в вашем коде функций с такой сигнатурой и поведением? Если ответ «нет», просто проверьте параметры в начале функции и вызовите исключение, если они отсутствуют.

0 голосов
/ 09 января 2010

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

# 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)
...