Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Python中进行子类化时追加到成员列表_Python_Oop_Inheritance - Fatal编程技术网

在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
的形式有点不寻常。