metaclass 如何访问 child class 上定义的属性?
How can a metaclass access attributes defined on a child class?
我正在研究 class 某种类型的生成器。我希望能够创建许多 children of 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(以及更多 children)必须指定一个 metaclass。理想情况下,我希望将这些细节抽象掉。我希望能够做类似的事情:
class ScopedPermission(BasePermission, metaclass=ScopedPermissionMeta):
pass
class ReadJwtPermission(ScopedPermission):
required_scopes = ['read:jwt']
但在这种情况下,metaclass 看到了 ScopedPermission
而没有 required_scopes
。有没有办法让metaclass看穿这种继承关系?
but in this situation the metaclass see's ScopedPermission and no
required_scopes. Is there a way to allow the metaclass to see through
this inheritance relationship?
目前正在创建ScopedPermission
class,没有ReadJwtPermission
class。解释器无法预测 class 将成为具有 required_scopes
属性的子 class ScopedPermission
的未来。但是你可以做一些不同的事情。
Child class继承了parent的元class。如果 parent class 使用那个元 class,每个 child classes 都必须有你想要的那个属性。我还使用 __new__
在 class 创建“之前”检查该属性。这是示例:
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 某种类型的生成器。我希望能够创建许多 children of 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(以及更多 children)必须指定一个 metaclass。理想情况下,我希望将这些细节抽象掉。我希望能够做类似的事情:
class ScopedPermission(BasePermission, metaclass=ScopedPermissionMeta):
pass
class ReadJwtPermission(ScopedPermission):
required_scopes = ['read:jwt']
但在这种情况下,metaclass 看到了 ScopedPermission
而没有 required_scopes
。有没有办法让metaclass看穿这种继承关系?
but in this situation the metaclass see's ScopedPermission and no required_scopes. Is there a way to allow the metaclass to see through this inheritance relationship?
目前正在创建ScopedPermission
class,没有ReadJwtPermission
class。解释器无法预测 class 将成为具有 required_scopes
属性的子 class ScopedPermission
的未来。但是你可以做一些不同的事情。
Child class继承了parent的元class。如果 parent class 使用那个元 class,每个 child classes 都必须有你想要的那个属性。我还使用 __new__
在 class 创建“之前”检查该属性。这是示例:
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.