我可以嵌套装饰器的最高级别是多少?

时间:2018-08-06 11:34:00

标签: python python-decorators

TLDR

我可以包装多少个实际的装饰器?实际的修饰器是指接受目标函数as作为参数的函数。

在Python中将参数传递给装饰器时,我们会执行以下操作:

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):

    print("I make decorators! And I accept arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))

    def my_decorator(func):
        # The ability to pass arguments here is a gift from closures.
        # If you are not comfortable with closures, you can assume it’s ok,
        # or read: https://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python
        print("I am the decorator. Somehow you passed me arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))

        # Don't confuse decorator arguments and function arguments!
        def wrapped(function_arg1, function_arg2) :
            print("I am the wrapper around the decorated function.\n"
                  "I can access all the variables\n"
                  "\t- from the decorator: {0} {1}\n"
                  "\t- from the function call: {2} {3}\n"
                  "Then I can pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2,
                          function_arg1, function_arg2))
            return func(function_arg1, function_arg2)

        return wrapped

    return my_decorator

此代码取自this answer

请注意,实际装饰器周围有一个包装器,用于处理提供给实际装饰器的参数。

奇怪的是,您可以使用包装器装饰目标函数,而不是像这样使用装饰器:

@decorator_maker_with_arguments("Leonard", "Sheldon")
    def decorated_function_with_arguments(function_arg1, function_arg2):
        print("I am the decorated function and only knows about my arguments: {0}"
               " {1}".format(function_arg1, function_arg2))

    decorated_function_with_arguments("Rajesh", "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function. 
#I can access all the variables 
#   - from the decorator: Leonard Sheldon 
#   - from the function call: Rajesh Howard 
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard

所以我的问题是,我可以在实际的装饰器中包装多少个功能?

以下面的代码为例:

def level0(foo):
    print("Level 0")
    def level1(foo):
        print("Level 1")
        def level2(foo):
            print("Level 2")
            def dec(some_func):
                print("Level 3")
                def wrap():
                    print("Foo is " + foo)
                    some_func()
                print("Level 3 End")
                return wrap
            return dec
        return level2
    return level1

@level0("foo")
def test():
    print("From python")

呼叫test打印

Level 0
Level 1
TypeError: level2() missing 1 required positional argument: 'foo'

那么深度限制只有2个吗?还是我做错了什么?

让我知道我身边是否需要其他任何信息。

2 个答案:

答案 0 :(得分:0)

def level0(foo):
    print(type(foo))
    print("Level 0")
    def level1(foo):
        print(type(foo))
        print("Level 1")
        def level2(foo):
            print("Level 2")
            def dec(some_func):
                print("Level 3")
                def wrap():
                    print("Foo is " + foo)
                    some_func()
                print("Level 3 End")
                return wrap
            return dec
        return level2
    return level1

尝试一下,您将看到level0和level1中的“ foo”之间的区别。 装饰器只是语法糖。在您的情况下,python会做到

test = level0("foo")(test)

但是如果您的代码更改为此

@level0
def foo():
    print("from foo")

python会做

test = level0(test)

答案 1 :(得分:0)

装饰器“覆盖”功能。它调用从以该函数作为参数的函数调用中获得的函数指针。然后使用传递给原始函数的参数调用该函数指针。

您在做什么,正在创建一个返回装饰器的函数,然后调用该函数,如下所示:

def this_returns_decorator(arg_passed_to_this_returns_decorator):
        def decorator(function):
                def wrapper(arg_passed_to_test):
                        function(arg_passed_to_this_returns_decorator + "|" + arg_passed_to_test)
                return wrapper
        return decorator

# next line is "equal" to @decorator
# ie. the function this_returns_decorator is called here and returns a decorator
@this_returns_decorator("arg passed to this_returns_decorator") 
def test(arg):
        print(arg)

# decorating is equal to "overwriting the function":
# test = this_returns_decorator("arg passed to this_returns_decorator")(test)

# will print "arg passed to this_returns_decorator|arg_passed_to_test"
test("arg_passed_to_test") 

这样您就不能获得两个以上的级别。您可以创建一个函数,该函数将返回一个将返回装饰器的函数,依此类推,但我将其解释为函数嵌套,而不是装饰器嵌套。

关于问题:

  

我可以嵌套装饰器的最高级别是什么?

我想这是您嵌套装饰器的方式:

def w1(f):
   def wrapper(name):
      return "w1 " + f(name)
   return wrapper

def w2(f):
   def wrapper(name):
       return "w2 " + f(name)
   return wrapper

@w2
@w1
def test(name):
        return name

print(test("name"))  # will print "w2 w1 name"

python语言参考只说装饰器可以嵌套,我找不到任何限制。为了有趣和测试,我创建了以下shell脚本:

#!/bin/bash
set -euo pipefail
tmp=$(mktemp)
trap 'rm $tmp' EXIT
for ((i = 10000;; i*=2)); do

        {
for i in $(seq $i); do
        printf "%s" "
def w$i(f):
        def w():
                f()
        return w
"
done
echo
for i in $(seq $i); do
        printf "%s\n" "@w$i"
done
printf "%s\n" "def test():
        print(\"test\")
print(\"Success\")
"

        } >"$tmp"

        printf "Testing levels i = %d\n" $i
        ( set -x; python3 "$tmp"; )

done

当我的电脑开始滞后时,能够升高到40万以上。装饰器的级别可能取决于系统拥有的内存量以及python解释器的性能。

@edit:

刚刚发现,不允许在decorator语句内调用函数返回的函数指针。但是,您仍然可以获得指向装饰器的指针,然后重载该函​​数。

def level0(foo):
    print(type(foo))
    print("Level 0")
    def level1(foo):
        print(type(foo))
        print("Level 1")
        def level2(foo):
            print("Level 2")
            def dec(some_func):
                print("Level 3")
                def wrap():
                    print("Foo is " + foo)
                    some_func()
                print("Level 3 End")
                return wrap
            return dec
        return level2
    return level1

# @level0("1")("2")("3") does not work - SyntaxError
decorator = level0("1")("2")("3")
@decorator
def test():
        print(test)

test()

修饰符正则表达式(来自python reference)为:

decorator               ::=  "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE

( )是可选的,只能指定一次。也许我们可以做一个简单的解决方法自己动手:

def workaround(obj, arg):
        if len(arg) == 0:
                return obj
        return workaround(obj(arg[0]), arg[1:])

@workaround(level0, ["1", "2", "3"])
def test2():
        print("test2")

那仍然是函数嵌套。