在Python 3中从另一个成员装饰器调用另一个成员装饰器

问题描述 投票:0回答:3

我正在尝试将成员函数装饰器重新用于其他成员函数装饰器,但出现以下错误:

'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'

你知道我怎样才能让它发挥作用吗?

python python-3.x decorator python-decorators
3个回答
3
投票

我认为这是可能的,但有两个注意事项;首先,装饰器必须移到类之外,其次,需要对名称修改进行一些调整。让我们先解决第一个问题。

移动部分功能

直接装饰一个装饰器可能看起来很直观,但它可能不会得到你想要的结果。但是,您可以装饰内部函数 - 就像如何使用

@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 开头),您必须决定如何处理它。有两种选择:

  1. 如果没有限制,只需将双下划线缩短为一个下划线(或根据需要重命名 - 只要它不以多个下划线开头)。
  2. 如果名称修改很重要,您必须像这样更改
    _OUTER_LOGIN_REQUIRED
    中的调用:
def decorated_function(self, *args, **kwargs):
    self._MyClass__check_for_valid_token(args[0], args[1])

这可能会影响继承,应该彻底测试。

总结

我已经在 python 3.9 上对此进行了一些测试,它似乎工作得很好。我注意到登录错误是在管理错误之前引发的,正如我认为所期望的那样。尽管如此,我只是粗略地探索了一下,虽然我想不出这种行为不当的原因,但我强烈建议在提交此方法之前彻底测试它(特别是如果代码包含继承,而我没有这样做)甚至触摸)。

我希望这对您有用,如果不起作用 - 请告诉我们它在哪里损坏以及如何损坏。


0
投票

如果你想装饰一个装饰器,你可以添加另一个包装层到

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

0
投票

最明智的方法是将两个装饰器分开并应用它们:

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


© www.soinside.com 2019 - 2024. All rights reserved.