在Python中进行子类化时追加到成员列表
假设我有这门课:在Python中进行子类化时追加到成员列表,python,oop,inheritance,Python,Oop,Inheritance,假设我有这门课: class FooClass(object): foos = ["foo", "bar"] def do_foos(self): for foo in self.foos: print("I have a " + foo) # ... 我想为这个类创建一个专门的FooClass,它扩展了FooClass,在foos中添加一个“spec”(即foos包含[“foo”、“bar”、“spec”]) Special
class FooClass(object):
foos = ["foo", "bar"]
def do_foos(self):
for foo in self.foos:
print("I have a " + foo)
# ...
我想为这个类创建一个专门的FooClass,它扩展了FooClass
,在foos
中添加一个“spec”
(即foos
包含[“foo”、“bar”、“spec”]
)
SpecializedFooClass.foos
应取决于FooClass.foos
:如果我更改FooClass
的定义,使foos
包含[“foo”、“bam”、“bat”]
,SpecializedFooClass.foos
应包含[“foo”、“bam”、“bat”、“spec”]
这是迄今为止我想到的最好的方法:
class SpecialisedFooClass(FooClass):
foos = FooClass.foos + ["spec"]
但是我发现对FooClass
的明确引用与此有关。当我决定添加中间子类时(即,specializedFooClass
“超类更改时),我将不可避免地忘记更新此引用。事实上,我已经在使用我正在使用的代码库时犯了这个错误(它实际上不处理foos、bams和bats…)
在我的例子中,实际上没有特别的要求,
foos
是类成员而不是实例成员,因此这也可以工作,但我觉得它很难看。另外,super
调用仍然有一个显式的类引用——这里不太令人担心,因为它指向它出现在其中的类
class FooClass(object):
def __init__(self, *args, **kwargs):
self.foos = ["foo", "bar"]
def do_foos(self):
for foo in self.foos:
print("I have a " + foo)
class SpecialisedFooClass(FooClass):
def __init__(self, *args, **kwargs):
super(SpecialisedFooClass, self).__init__(*args, **kwargs)
self.foos.append("spec")
还有什么其他选择,有没有一种“Pythonic”方法可以做到这一点?这可能会让你接近:
class A(object):
foo_ = ['a']
@classmethod
def foo(cls):
return sum((getattr(c, 'foo_', []) for c in cls.__mro__[::-1]), [])
class B(A):
foo_ = ['b']
class C(B):
foo_ = ['c','d','e']
class D(A):
foo_ = ['f', 'g', 'h']
class E(B,D):
foo_ = ['i', 'j', 'k']
for cls in (A,B,C,D,E):
print cls.__name__, cls.foo()
印刷品
A ['a']
B ['a', 'b']
C ['a', 'b', 'c', 'd', 'e']
D ['a', 'f', 'g', 'h']
E ['a', 'f', 'g', 'h', 'b', 'i', 'j', 'k']
编辑
转换为classmethod,因此无需实例化即可获得foo列表。还添加了一些菱形继承以显示此外观。有几种方法可以处理此问题。当属性需要一定量的计算时,一种选择是将其转换为 您可以进行一些优化,以便只计算一次,或者在运行时对其进行更改,而不是每次都返回相同的内容
class SpecializedFooClass(FooClass)
def __init__(self):
super(SpecializedFoo, self).__init__()
self._foos = None
@property
def foos(self):
if self._foos is None:
self._foos = super(SpecializedFooClass, self).foos + ['spec']
return self._foos
使用属性的主要缺点是该上下文不再像类属性那样工作,因为必须实例化类才能获得值
也可以使用()。在某些情况下,这可能会大大减少您的代码库,但如果您有一个非常深的继承链,并且不清楚是否正在使用元类,那么这也可能会造成混乱。但从好的方面来看,它的工作方式与class属性完全相同,因为它实际上是class属性
class FooMeta(type):
def __new__(cls, name, bases, attrs):
foos = []
for base in bases:
if hasattr(base, 'foos'):
foos.extend(base.foos)
attrs['foos'] = foos + attrs.get('foos', [])
return type.__new__(cls, name, bases, attrs)
class Foo(object):
__metaclass__ = FooMeta
foos = ['one', 'two']
class SpecialFoo(Foo):
foos = ['three']
print Foo.foos
# ['one', 'two']
print SpecialFoo.foos
# ['one', 'two', 'three']
第三种选择是使用类装饰器。与元类相比,它的魔力要小一些,但它也意味着您必须记住装饰每个子类。它的功能与类属性完全相同,因为它实际上是一个类属性
def set_foos(cls):
foos = []
for base in cls.__bases__:
if hasattr(base, 'foos'):
foos.extend(base.foos)
cls.foos = foos + getattr(cls, 'foos', [])
return cls
class FooClass(object):
foos = ['foo', 'bar']
@set_foo
class SpecialFoo(FooClass):
foos = ['spec']
使用关于元类的奇妙答案,以Django Rest框架为例。每次我们想在理论上使用
permission\u类
,但当您进行单元测试时,您可以看到我们确实在覆盖它
获取IsAuthenticated
权限。如果您在浏览器中使用,如果未登录,它将阻止访问API,但如果我们希望添加额外的权限类,例如,对于API级别的员工,如果您测试需要登录才能访问StaffAPI
,则单元测试将失败。使用伟大的元类示例,我们可以将该行为更改为
class BaseAuthMeta(类型):
“”“要创建/读取权限属性的元类”
"""
定义(cls、名称、基数、属性):
权限=[]
对于基地中的基地:
如果hasattr(基本“权限”):
permissions.extend(base.permissions)
attrs['permissions']=权限+attrs.get('permissions',[])
返回类型。\uuuu新\uuuuu(cls、名称、基数、属性)
然后,我们只需要确保在API级别上使用属性permissions
,但我们在所有API的基础上声明(如果您愿意的话)
class AccessMixin(元类=BaseAuthMeta):
"""
Django rest框架不在继承的模型上附加权限类,这可能会在
它以编程方式调用API,这样我们就创建了一个元类,该元类将从自定义属性读取
并将附加到AccessMixin子类上的默认“permission_classes”
"""
通过
类MyAuthMixin(AccessMixin,APIView):
"""
需要登录凭据才能从平台内部访问的基本APIView
或通过请求(如果已知)
"""
权限=[IsAuthenticated]
定义初始化(self,*args,**kwargs)->无:
super()
self.permission\u classes=self.permissions
在API级别上
类MyStaffApiView(MyAuthMixin,APIView):
权限=[MyCustomStaffPermission]
...
如果您调试它并且检查了self.permission\u classes
,这实际上会附加您需要的内容(用于权限),因为您正在使用新的自定义类属性
class FooMeta(type):
def __new__(cls, name, bases, attrs):
foos = []
for base in bases:
if hasattr(base, 'foos'):
foos.extend(base.foos)
attrs['foos'] = foos + attrs.get('foos', [])
return type.__new__(cls, name, bases, attrs)
class Foo(object):
__metaclass__ = FooMeta
foos = ['one', 'two']
class SpecialFoo(Foo):
foos = ['three']
print Foo.foos
# ['one', 'two']
print SpecialFoo.foos
# ['one', 'two', 'three']
很抱歉这么晚了,但是元类的答案确实太棒了:)元类方法很酷,想不到!在这个小例子中,它可能不被认可,但如果这只是冰山一角,那么它可能是有意义的。添加了另一个选项-类装饰器。这种FooMeta方法真的很酷,谢谢。就我个人而言,我对更改“=”操作符的效果感到非常不舒服;这意味着,在您阅读FooMeta之前,代码的其余部分的行为与您预期的不同(即,代码
foos=[“hurdy”]
在某些上下文中实际上没有将[“hurdy”]
分配给foo
),我认为属性方法是最好的。当您访问它时,它很好而且透明,但是在阅读Foo*
类时,它完全清晰,而foos
的形式有点不寻常。