如何在函数上使用的装饰器 class 中获取函数句柄?
How to get the function handle within a decorator class which is used on a function?
以下文件结构位于整个 project
文件夹中的 testing
模块中:
project/
│── testing/
│ ├── __init__.py
│ ├── store.py
│ └── store_functions.py
│
└── main.py
testing/store.py
class Store:
registered = []
def __init__(self, *args, **kwargs)
Store.registered.append(self)
def __call__(self, func):
pass
testing/store_functions.py
@Store('test-xyz')
def my_test_function(testname):
pass
基本想法是,我想在某处导入 testing
模块,并且应该自动收集带有 @Store
装饰器的定义函数并存储在 Store.registered
中。
所以里面 main.py:
import testing # this automatically collects all @Store decorators and puts all of them into Store.registered
registered_stores = Store.registered
但我的问题出在 Store.__init__
函数中,我无法获得 my_test_function
的函数句柄:
testing/store.py
class Store:
registered = []
def __init__(self, *args, **kwargs):
# here I need access to the actual function this decorator was assigned to for examle:
# @Store('test-xyz')
# def my_test_function(testname):
# pass
# I would need access to the function handle of my_test_function
这有可能吗?
或者我需要以何种方式更改我的代码才能使 design/approach 正常工作?
非常感谢任何帮助 and/or 提示!
为了解决这个问题,我将定义一个函数来接收参数,例如 'text-xyz'
和 return 装饰器将调用 Store
:
def store(*args, **kwargs):
def decor(func):
Store(func, *args, **kwargs)
return func
return decor
class Store:
def __init__(self, func, *args, **kwargs):
# use func, args aand kwargs
用法:
@store('test-xyz') # note the lower-case store
def my_test_function(testname):
pass
作为 的替代方案,如果您想将所有装饰器逻辑保留在 Store
class 中,则必须以不同方式构建它.
前言:了解接受参数的装饰器
首先,记住装饰器是如何工作的:
@foo
def bar():
pass
# is equivalent to:
def bar():
pass
bar = foo(bar)
重要的是要理解 @
之后的表达式的计算结果是 callable,它接受一个参数,这是它正在装饰的可调用对象;在这种情况下,bar
。考虑到这一点,当我们想要创建一个可以接受参数的装饰器时,我们最终会在行为上做出一个微妙但非常重要的改变,比如:
@foo('wowza')
def frobnicate():
pass
# is equivalent to:
def frobnicate():
pass
frobnicate = foo('wowza')(frobnicate)
同样,@
之后的表达式的计算结果应该是可调用的。但是,现在我们的表达式是函数 call 而不是函数 object!因此,调用 foo('wowza')
的 result 被用作装饰器,这意味着 result 将被尝试用作可调用并传递给 frobnicate
作为论据。如果结果 是 可调用的,那就太好了!但它可能不是,所以这样的装饰器必须以不同的方式构造。
创建存储装饰器
这是实现;脱离问题中的示例,您希望能够构造 Storage
带有参数的实例,然后将其存储在实例中。
import functools
class Storage:
registered = []
def __init__(self, value=None):
# Note that we don't have the decorated function object yet. This is
# intentional! Remember how decorators are evaluated! All we do here is
# store our decorator arguments.
self.value = value
# create any other needed attributes
# This is our "callable" that takes in the decorated function!
def __call__(self, func):
# Using type avoids hard-coding the class name
type(self).registered.append(func)
# do something with self.value if needed
# do something with func if needed
# The usual functools.wraps call. This preserves the decorated
# function's name and docstring.
@functools.wraps(func)
# This should be familiar; it's just like writing a normal decorator.
def wrapper(*args, **kwargs):
# do something with func, func(), and/or self.value if needed
return func(*args, **kwargs)
# This function object is what the name of the decorated function is
# bound to.
return wrapper
这是一个没有所有评论的版本:
import functools
class Storage:
registered = []
def __init__(self, value=None):
self.value = value
def __call__(self, func):
type(self).registered.append(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
应用装饰器按预期工作:
@Storage('test-xyz')
def my_test_function(testname):
pass
请注意,如果您希望装饰器参数是可选的,即使您没有传递任何参数,您仍然需要“调用”装饰器;就像您在实例化任何其他 class:
时一样
@Storage()
def my_test_function2(testname):
pass
如果您想通过大量示例更深入地解释如何创建不同种类的装饰器(包括像这样的装饰器),我强烈推荐 RealPython 的 Primer on Python Decorators。它非常详尽,比我在这里介绍的更详细。
以下文件结构位于整个 project
文件夹中的 testing
模块中:
project/
│── testing/
│ ├── __init__.py
│ ├── store.py
│ └── store_functions.py
│
└── main.py
testing/store.py
class Store:
registered = []
def __init__(self, *args, **kwargs)
Store.registered.append(self)
def __call__(self, func):
pass
testing/store_functions.py
@Store('test-xyz')
def my_test_function(testname):
pass
基本想法是,我想在某处导入 testing
模块,并且应该自动收集带有 @Store
装饰器的定义函数并存储在 Store.registered
中。
所以里面 main.py:
import testing # this automatically collects all @Store decorators and puts all of them into Store.registered
registered_stores = Store.registered
但我的问题出在 Store.__init__
函数中,我无法获得 my_test_function
的函数句柄:
testing/store.py
class Store:
registered = []
def __init__(self, *args, **kwargs):
# here I need access to the actual function this decorator was assigned to for examle:
# @Store('test-xyz')
# def my_test_function(testname):
# pass
# I would need access to the function handle of my_test_function
这有可能吗? 或者我需要以何种方式更改我的代码才能使 design/approach 正常工作? 非常感谢任何帮助 and/or 提示!
为了解决这个问题,我将定义一个函数来接收参数,例如 'text-xyz'
和 return 装饰器将调用 Store
:
def store(*args, **kwargs):
def decor(func):
Store(func, *args, **kwargs)
return func
return decor
class Store:
def __init__(self, func, *args, **kwargs):
# use func, args aand kwargs
用法:
@store('test-xyz') # note the lower-case store
def my_test_function(testname):
pass
作为 Store
class 中,则必须以不同方式构建它.
前言:了解接受参数的装饰器
首先,记住装饰器是如何工作的:
@foo
def bar():
pass
# is equivalent to:
def bar():
pass
bar = foo(bar)
重要的是要理解 @
之后的表达式的计算结果是 callable,它接受一个参数,这是它正在装饰的可调用对象;在这种情况下,bar
。考虑到这一点,当我们想要创建一个可以接受参数的装饰器时,我们最终会在行为上做出一个微妙但非常重要的改变,比如:
@foo('wowza')
def frobnicate():
pass
# is equivalent to:
def frobnicate():
pass
frobnicate = foo('wowza')(frobnicate)
同样,@
之后的表达式的计算结果应该是可调用的。但是,现在我们的表达式是函数 call 而不是函数 object!因此,调用 foo('wowza')
的 result 被用作装饰器,这意味着 result 将被尝试用作可调用并传递给 frobnicate
作为论据。如果结果 是 可调用的,那就太好了!但它可能不是,所以这样的装饰器必须以不同的方式构造。
创建存储装饰器
这是实现;脱离问题中的示例,您希望能够构造 Storage
带有参数的实例,然后将其存储在实例中。
import functools
class Storage:
registered = []
def __init__(self, value=None):
# Note that we don't have the decorated function object yet. This is
# intentional! Remember how decorators are evaluated! All we do here is
# store our decorator arguments.
self.value = value
# create any other needed attributes
# This is our "callable" that takes in the decorated function!
def __call__(self, func):
# Using type avoids hard-coding the class name
type(self).registered.append(func)
# do something with self.value if needed
# do something with func if needed
# The usual functools.wraps call. This preserves the decorated
# function's name and docstring.
@functools.wraps(func)
# This should be familiar; it's just like writing a normal decorator.
def wrapper(*args, **kwargs):
# do something with func, func(), and/or self.value if needed
return func(*args, **kwargs)
# This function object is what the name of the decorated function is
# bound to.
return wrapper
这是一个没有所有评论的版本:
import functools
class Storage:
registered = []
def __init__(self, value=None):
self.value = value
def __call__(self, func):
type(self).registered.append(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
应用装饰器按预期工作:
@Storage('test-xyz')
def my_test_function(testname):
pass
请注意,如果您希望装饰器参数是可选的,即使您没有传递任何参数,您仍然需要“调用”装饰器;就像您在实例化任何其他 class:
时一样@Storage()
def my_test_function2(testname):
pass
如果您想通过大量示例更深入地解释如何创建不同种类的装饰器(包括像这样的装饰器),我强烈推荐 RealPython 的 Primer on Python Decorators。它非常详尽,比我在这里介绍的更详细。