我正在尝试将成员函数装饰器重新用于其他成员函数装饰器,但出现以下错误:
'function' object has no attribute '_MyClass__check_for_valid_token'
基本上我有一个工作装饰器来检查用户是否已登录(
@LOGIN_REQUIRED
),我想首先在@ADMIN_REQUIRED
装饰器中调用它(所以想法是检查用户是否使用现有的登录@LOGIN_REQUIRED
装饰器,然后添加一些特定的验证来检查登录用户是否是@ADMIN_REQUIRED
装饰器中的管理员。
我当前的代码是这样的:
class MyClass:
def LOGIN_REQUIRED(func):
@wraps(func)
def decorated_function(self, *args, **kwargs):
# username and token should be the first parameters
# throws if not logged in
self.__check_for_valid_token(args[0], args[1])
return func(self, *args, **kwargs)
return decorated_function
@LOGIN_REQUIRED
def ADMIN_REQUIRED(func):
@wraps(func)
def decorated_function(self, *args, **kwargs):
is_admin = self.check_if_admin()
if not is_admin:
raise Exception()
return func(self, *args, **kwargs)
return decorated_function
@ADMIN_REQUIRED
def get_administration_data(self, username, token):
# return important_data
# currently throws 'function' object has no attribute '_MyClass__check_for_valid_token'
你知道我怎样才能让它发挥作用吗?
我认为这是可能的,但有两个注意事项;首先,装饰器必须移到类之外,其次,需要对名称修改进行一些调整。让我们先解决第一个问题。
直接装饰一个装饰器可能看起来很直观,但它可能不会得到你想要的结果。但是,您可以装饰内部函数 - 就像如何使用
@wraps
一样。由于 python 解析代码的方式,外部装饰器必须在类之外(和之前)定义,否则你会得到一个 NameError。代码应该如下所示:
def _OUTER_LOGIN_REQUIRED(func):
@wraps(func)
def decorated_function(self, *args, **kwargs):
self.__check_for_valid_token(args[0], args[1])
return func(self, *args, **kwargs)
return decorated_function
[Notice no code-changes to this function (yet)]
class MyClass:
# The following line will make the transition seamless for most methods
LOGIN_REQUIRED = _OUTER_LOGIN_REQUIRED
def ADMIN_REQUIRED(func):
@wraps(func)
@_OUTER_LOGIN_REQUIRED # <-- Note that this is where it should be decorated
def decorated_function(self, *args, **kwargs):
... [the rest of ADMIN_REQUIRED remains unchanged]
@ADMIN_REQUIRED
def get_administration_data(self, username, token):
... [this should now invoke LOGIN_REQUIRED -> ADMIN_REQUIRED -> the function]
@LOGIN_REQUIRED
def get_some_user_data(self, ...):
... [Such definitions should still work, as we added LOGIN_REQUIRED attribute to the class]
如果到目前为止这样的改变是可以接受的,那么让我们继续
由于
__check_for_calid_token
函数的名称是 mangled (因为它的名称以 dunder 开头),您必须决定如何处理它。有两种选择:
_OUTER_LOGIN_REQUIRED
中的调用:def decorated_function(self, *args, **kwargs):
self._MyClass__check_for_valid_token(args[0], args[1])
这可能会影响继承,应该彻底测试。
我已经在 python 3.9 上对此进行了一些测试,它似乎工作得很好。我注意到登录错误是在管理错误之前引发的,正如我认为所期望的那样。尽管如此,我只是粗略地探索了一下,虽然我想不出这种行为不当的原因,但我强烈建议在提交此方法之前彻底测试它(特别是如果代码包含继承,而我没有这样做)甚至触摸)。
我希望这对您有用,如果不起作用 - 请告诉我们它在哪里损坏以及如何损坏。
如果你想装饰一个装饰器,你可以添加另一个包装层到
LOGIN_REQUIRED
class MyClass:
def LOGIN_REQUIRED(dec):
def decwrapper(func):
wrapped = dec(func)
def decorated_function(self, *args, **kwargs):
# username and token should be the first parameters
# throws if not logged in
self.__check_for_valid_token(args[0], args[1])
return wrapped(self, *args, **kwargs)
return decorated_function
return decwrapper
如果您想保持装饰器定义不变,则必须将
@LOGIN_REQUIRED
装饰从 def ADMIN_REQUIRED
移动到 def get_administration_data()
@LOGIN_REQUIRED
@ADMIN_REQUIRED
def get_administration_data(self, ar1, ar2):
...
最明智的方法是将两个装饰器分开并应用它们:
from functools import wraps
def LOGIN_REQUIRED(func):
@wraps(func)
def decorated_function(self, *args, **kwargs):
# username and token should be the first parameters
# throws if not logged in
print("checking login")
self._MyClass__check_for_valid_token(args[0], args[1])
return func(self, *args, **kwargs)
return decorated_function
def ADMIN_REQUIRED(func):
@wraps(func)
def decorated_function(self, *args, **kwargs):
print("checking if admin")
is_admin = self.check_if_admin()
if not is_admin:
raise Exception()
return func(self, *args, **kwargs)
return decorated_function
class MyClass:
def __check_for_valid_token(self, username, token):
return True
def check_if_admin(self):
return True
@LOGIN_REQUIRED
@ADMIN_REQUIRED
def get_administration_data(self, username, token):
# return important_data
return 42
注意,装饰器是在类外部定义的,因为它们不是方法。它们一开始就不应该在类中定义。另请注意,您似乎想要使用双下划线名称修饰,因此您将必须手动使用
_MyClass__check_for_valid_token
而不是依赖于 __check_for_valid_token
的编译时转换。也许,最好是简单地不使用双下划线名称修改并使用单下划线。
如果你真的想要同时应用两者的东西,那么创建第三个辅助装饰器:
def LOGIN_AND_ADMIN_REQUIRED(func):
return LOGIN_REQUIRED(ADMIN_REQUIRED(func))
class MyClass:
@LOGIN_AND_ADMIN_REQUIRED
def get_administration_data(self, username, token):
# return important_data
...
但是如果你坚持让
ADMIN_REQUIRED
两者都做,那么你可以:
def ADMIN_REQUIRED(func):
# func = LOGIN_REQUIRED(func) here, ADMIN_REQUIRED would happen first
@wraps(func)
def decorated_function(self, *args, **kwargs):
print("checking if admin")
is_admin = self.check_if_admin()
if not is_admin:
raise Exception()
return func(self, *args, **kwargs)
return LOGIN_REQUIRED(decorated_function)
注意,如果您确实愿意,可以将装饰器定义嵌套在类中。这最终是一个风格问题,因为您不会被阻止这样做,但现在您应该只使用显式双重装饰语法,否则,您无法引用其他装饰器,因为类块不会创建封闭范围:
class MyClass:
def LOGIN_REQUIRED(func):
@wraps(func)
def decorated_function(self, *args, **kwargs):
# username and token should be the first parameters
# throws if not logged in
print("checking login")
self.__check_for_valid_token(args[0], args[1])
return func(self, *args, **kwargs)
return decorated_function
def ADMIN_REQUIRED(func):
@wraps(func)
def decorated_function(self, *args, **kwargs):
print("checking if admin")
is_admin = self.check_if_admin()
if not is_admin:
raise Exception()
return func(self, *args, **kwargs)
return decorated_function
def __check_for_valid_token(self, username, token):
return True
def check_if_admin(self):
return True
@LOGIN_REQUIRED
@ADMIN_REQUIRED
def get_administration_data(self, username, token):
# return important_data
return 42
注意,如果它在类定义中,则可以在
__check_for_valid_token
中使用 LOGIN_REQUIRED
。