在Python中进行子类化时附加到成员列表

时间:2016-03-23 05:30:42

标签: python oop inheritance

说我有这个班:

class FooClass(object):
    foos = ["foo", "bar"]

    def do_foos(self):
        for foo in self.foos:
            print("I have a " + foo)

    # ...

我想为此类创建一个SpecialisedFooClass,扩展FooClass,向"spec"添加项foos(即foos包含["foo", "bar", "spec"]) 。

SpecialisedFooClass.foos应取决于FooClass.foos:如果我更改FooClass'定义,以便foos包含["foo", "bam", "bat"],则SpecialisedFooClass.foos应包含{ {1}}。

这是我到目前为止提出的最佳方式:

["foo", "bam", "bat", "spec"]

但我发现有关class SpecialisedFooClass(FooClass): foos = FooClass.foos + ["spec"] 的明确引用。当我决定添加一个中间子类(即当FooClass'超类更改时)时,我将不可避免地忘记更新此引用。事实上,我已经使用我正在处理的代码库(这实际上 处理foos,bams和bats ......)来犯下IRL这个错误。

在我的情况下实际上没有特殊要求SpecialisedFooClass是一个类成员而不是一个实例成员,所以这也可以,但我发现它很难看。此外,foos调用仍然有一个明确的类引用 - 不用担心这里因为它出现在它所在的类中。

super

还有哪些其他选择,是否有“Pythonic”方法可以做到这一点?

3 个答案:

答案 0 :(得分:2)

这可能会让你接近:

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列表。还添加了一些钻石继承,以显示它看起来。

答案 1 :(得分:2)

有几种方法可以解决这个问题。当属性需要一定量的计算时,一种选择是将其转换为property

class FooClass(object):
    foos = ["foo", "bar"]


class SpecializedFooClass(FooClass)

    @property
    def foos(self):
        return super(SpecializedFooClass, self).foos + ['spec']

您可以进行一些优化,使其只计算一次,或者可以在运行时更改,而不是每次都返回相同的内容。

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

使用属性的主要缺点是这个上下文不再是因为你必须实例化一个类才能获得值,所以它不再像一个类属性那样。

您还可以使用metaclassesgreat answer on metaclasses)。在某些情况下,这可以大大减少您的代码库,但如果您有一个非常深的继承链并且不清楚正在使用元类,它也可能会令人困惑。但从好的方面来说,它的工作方式与类属性完全相同,因为它实际上是一个类属性。

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

第三个选项是使用类装饰器。它比魔术类少一点神奇,但它也意味着你必须记住装饰每个子类。它的功能与class属性完全相同,因为它实际上是一个。

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

答案 2 :(得分:0)

使用有关元类的奇妙答案,以Django Rest Framework为例。每次我们想在理论上使用permission_classes时都有效,但是当您进行单元测试时,您可以看到确实确实在覆盖它。

获得IsAuthenticated权限。如果您在浏览器中使用,如果未登录,它将阻止访问API,但是如果我们要添加额外的权限类,例如,对于API级别的Staff,如果您测试{{1 }}。使用出色的元类示例,我们可以将其行为更改为

needs to be logged in to access StaffAPI

然后,我们只需要确保在API级别上使用属性class BaseAuthMeta(type): """Metaclass to create/read from permissions property """ def __new__(cls, name, bases, attrs): permissions = [] for base in bases: if hasattr(base, 'permissions'): permissions.extend(base.permissions) attrs['permissions'] = permissions + attrs.get('permissions', []) return type.__new__(cls, name, bases, attrs) ,但是我们会在所有API的基础上声明(如果您愿意)

permissions

在API级别

class AccessMixin(metaclass=BaseAuthMeta):
    """
    Django rest framework doesn't append permission_classes on inherited models which can cause issues when
    it comes to call an API programmatically, this way we create a metaclass that will read from a property custom
    from our subclasses and will append to the default `permission_classes` on the subclasses of AccessMixin
    """
    pass


class MyAuthMixin(AccessMixin, APIView):
    """
    Base APIView requiring login credentials to access it from the inside of the platform
    Or via request (if known)
    """
    permissions = [IsAuthenticated]

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.permission_classes = self.permissions

如果您对其进行调试并且对 class MyStaffApiView(MyAuthMixin, APIView): permissions = [MyCustomStaffPermission] ... 进行了检查,这实际上将附加您需要的内容(权限),因为您正在使用新的自定义类属性。

很抱歉这么晚了,但元类答案的确很棒:)