【问题标题】:Is there a way for a decorated function to refer to an object created by a decorator?装饰函数有没有办法引用装饰器创建的对象?
【发布时间】:2019-06-16 09:31:52
【问题描述】:

我想知道装饰函数是否可以引用由装饰器的包装器创建的对象。当我考虑使用装饰器时,我的问题出现了:

  1. 制作一个包装器,用于创建带有子图的图形
  2. 在包装器内部执行装饰函数,该函数将添加一些图
  3. 最终将图形保存在包装器中

但是,装饰函数需要引用包装器创建的图形。装饰函数如何引用该对象?我们是否必须求助于全局变量?

这是一个简短的示例,我在装饰函数中引用了一个在包装器中创建的变量(但如果不使用全局变量进行调整,我就无法做到这一点):

def my_decorator(func):
    def my_decorator_wrapper(*args, **kwargs):
        global x
        x = 0
        print("x in wrapper:", x)
        return func(*args, **kwargs)
    return my_decorator_wrapper

@my_decorator
def decorated_func():
    global x
    x += 1
    print("x in decorated_func:", x)

decorated_func()
# prints:
# x in wrapper: 0
# x in decorated_func: 1

我知道这在课堂上很容易做到,但我出于好奇提出这个问题。

【问题讨论】:

  • 装饰器可以在调用包装函数时为其提供附加参数。通常这是一个坏主意,因为装饰是您可以可选地对函数执行的操作,但您所讨论的场景是装饰器和装饰器之间的耦合比正常情况要紧密得多。
  • 在 Python 3 中,您可以使用 nonlocal 来引用已知由装饰器创建的对象。
  • @user2357112 好的,对。我对闭包和动态范围感到困惑。
  • 能否加个代码sn-p让你的请求更清晰?
  • 有多种方法可以实现,但似乎没有一种特别好,而且真的,如果你的装饰函数需要了解装饰器的内部结构,它可能不是一个好的用例装饰器开始

标签: python python-decorators


【解决方案1】:

是的,函数可以通过查看自身来引用它。

装饰器结束。它只需要属性并将它们设置在函数上

如果它看起来有点复杂,那是因为接受参数的装饰器需要这种特殊的结构才能工作。见Decorators with parameters?

def declare_view(**kwds):
    """declaratively assocatiate a Django View function with resources
    """

    def actual_decorator(func):
        for k, v in kwds.items():
            setattr(func, k, v)

        return func

    return actual_decorator

调用装饰器

@declare_view(
    x=2
)
def decorated_func():
    #the function can look at its own name, because the function exists 
    #by the time it gets called.
    print("x in decorated_func:", decorated_func.x)

decorated_func()

输出

x in decorated_func: 2 

在实践中,我已经使用了很多。我的想法是将 Django 视图函数与它们必须与之协作的特定后端数据类和模板相关联。因为它是声明性的,所以我可以自省所有 Django 视图并跟踪它们关联的 URL 以及自定义数据对象和模板。工作得很好,但是是的,该函数确实希望某些属性存在于自身上。它不知道装饰器设置了它们。

哦,就我而言,没有充分的理由在我的用例中将这些作为参数传递,这些变量基本上包含从函数的 POV 不会改变的硬编码值。

一开始很奇怪,但功能非常强大,并且没有运行时或维护方面的缺点。

这里有一些活生生的例子,可以把它放在上下文中。

@declare_view(
    viewmanager_cls=backend.VueManagerDetailPSCLASSDEFN,
    template_name="pssecurity/detail.html",
    objecttype=constants.OBJECTTYPE_PERMISSION_LIST[0],
    bundle_name="pssecurity/detail.psclassdefn",
)
def psclassdefn_detail(request, CLASSID, dbr=None, PORTAL_NAME="EMPLOYEE"):
    """

    """
    f_view = psclassdefn_detail
    viewmanager = f_view.viewmanager_cls(request, mdb, f_view=f_view)
    ...do things based on the parameters...
    return viewmanager.HttpResponse(f_view.template_name)

【讨论】:

  • 你可以设置函数的属性!? ?
  • 在 Python 中,一切都是对象。所以,是的,因为视图是 Python 对象并且没有__slots__,您可以在它们上设置任意属性。在 Python 2 和 3 之间,它们自己的内部属性发生了一些变化。例如名称在 3 中可能是 func.__name__,但在 2 中可能稍微复杂一些。
  • 编辑:在我之前的评论中阅读“功能”而不是“视图”。
【解决方案2】:

尝试avoid using global variables

使用参数将对象传递给函数

有一种将值传递给函数的规范方法:参数。

在调用包装器时将对象作为参数传递给修饰函数。

from functools import wraps

def decorator(f):
    obj = 1

    @wraps(f)
    def wrapper(*args):
        return f(obj, *args)

    return wrapper

@decorator
def func(x)
    print(x)

func() # prints 1

使用默认参数传递相同的对象

如果您需要将同一个对象传递给所有函数,则可以将其存储为装饰器的默认参数。

from functools import wraps

def decorator(f, obj={}):
    @wraps(f)
    def wrapper(*args):
        return f(obj, *args)

    return wrapper

@decorator
def func(params)
    params['foo'] = True

@decorator
def gunc(params)
    print(params)

func()

# proof that gunc receives the same object
gunc() # prints {'foo': True}

上面创建了一个公共私有dict,它只能被修饰函数访问。由于dict 是可变的,因此更改将反映在函数调用中。

【讨论】:

    【解决方案3】:

    作为装饰器的类

    This article 指向作为装饰器的类,这似乎是一种更优雅的方式来指向装饰器中定义的状态。它依赖函数属性,在装饰类中使用了特殊的.__call__()方法。

    这是我使用类而不是函数作为装饰器的示例:

    class my_class_decorator:
        def __init__(self, func):
            self.func = func
            self.x =  0
    
        def __call__(self, *args, **kwargs):
            print("x in wrapper:", self.x)
            return self.func(*args, **kwargs)
    
    @my_class_decorator
    def decorated_func():
        decorated_func.x += 1
        print("x in decorated_func:", decorated_func.x)
    
    decorated_func()
    # prints:
    # x in wrapper: 0
    # x in decorated_func: 1
    

    【讨论】:

      猜你喜欢
      • 2012-05-11
      • 2014-07-21
      • 1970-01-01
      • 1970-01-01
      • 2020-06-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-19
      相关资源
      最近更新 更多