【问题标题】:How can a metaclass access attributes defined on a child class?元类如何访问子类上定义的属性?
【发布时间】:2021-10-21 15:58:45
【问题描述】:

我正在开发一个类生成器。我希望能够创建许多BasePermission 的子代,这些子代可以使用required_scopes 属性进行自定义。这是我目前所拥有的。

from rest_framework.permissions import BasePermission


class ScopedPermissionMeta(type(BasePermission)):

    def __init__(self, name, bases, attrs):
        try:
            required_scopes = attrs['required_scopes']
        except KeyError:
            raise TypeError(f'{name} must include required_scopes attribute.')

        required_scopes_list = ' '.join(required_scopes)
        attrs['message'] = f'Resource requires scope={required_scopes_list}'

        def has_permission(self, request, _view):
            """Check required scopes against requested scopes."""
            try:
                requested_scopes = request.auth.claims['scope']
            except (AttributeError, KeyError):
                return False

            return all(scope in requested_scopes for scope in required_scopes)

        attrs['has_permission'] = has_permission


class ReadJwtPermission(BasePermission, metaclass=ScopedPermissionMeta):
    required_scopes = ['read:jwt']

但是,我不喜欢ReadJwtPermisson 类(以及更多的子类)必须指定一个元类。理想情况下,我希望将这些细节抽象掉。我希望能够做类似的事情:

class ScopedPermission(BasePermission, metaclass=ScopedPermissionMeta):
    pass


class ReadJwtPermission(ScopedPermission):
    required_scopes = ['read:jwt']

但在这种情况下,元类看到的是ScopedPermission,而不是required_scopes。有没有办法让元类看穿这种继承关系?

【问题讨论】:

    标签: python metaclass


    【解决方案1】:

    但在这种情况下,元类看到的是 ScopedPermission 而没有 required_scopes。有没有办法让元类看穿 这种继承关系?

    在创建ScopedPermission 类的那一刻,没有ReadJwtPermission 类。解释器无法预测未来一个类将继承 ScopedPermission 的子类,它具有 required_scopes 属性。但是你可以做一些不同的事情。

    子类继承父类的元类。如果父类使用该元类,则每个子类都必须具有您想要的属性。我还使用__new__ 在类创建“之前”检查该属性。这是一个例子:

    class Metaclass(type):
        def __new__(mcs, name, bases, attrs):
    
            # This condition skips Base class's requiement for having "required_scopes"
            # otherwise you should specify "required_scopes" for Base class as well.
            if name == 'Base':
                return super().__new__(mcs, name, bases, attrs)
    
            try:
                required_scopes = attrs['required_scopes']
            except KeyError:
                raise TypeError(f'{name} must include "required_scopes" attribute.')
    
            required_scopes_list = ' '.join(required_scopes)
            attrs['message'] = f'Resource requires scope={required_scopes_list}'
    
            # setting "has_permission attribute here"
            attrs['has_permission'] = mcs.has_permission()
    
            return super().__new__(mcs, name, bases, attrs)
    
        # I just removed the implementation so that I can be able to run this class.
        @staticmethod
        def has_permission():
            pass
    
    
    class Base(metaclass=Metaclass):
        pass
    
    class A(Base):
        required_scopes = ['read:jwt']
    
    print(A.message)
    

    输出:

    Resource requires scope=read:jwt
    

    但现在有了:

    class B(Base):
        pass
    

    它引发错误...

    Traceback (most recent call last):
      File "<--->", line 9, in __new__
        required_scopes = attrs['required_scopes']
    KeyError: 'required_scopes'
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "<--->", line 34, in <module>
        class B(Base):
      File "<--->", line 11, in __new__
        raise TypeError(f'{name} must include "required_scopes" attribute.')
    TypeError: B must include "required_scopes" attribute.
    

    【讨论】:

    • 我在你的代码中唯一改变的是class Base(BasePermission, metaclass=ScopedPermissionMeta):class Metaclass(type(BasePermission)):,否则它会按预期工作,谢谢
    • @BradyDean 太好了,我只关注另一部分。
    猜你喜欢
    • 2015-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-05
    • 1970-01-01
    • 1970-01-01
    • 2019-05-29
    • 2013-03-24
    相关资源
    最近更新 更多