在Python中继承时扩充类属性

时间:2015-03-03 00:06:47

标签: python inheritance

我在Python中有一个继承链,我希望每个子类能够添加新的自定义参数。现在我正在这样做:

class A(object):
  PARAM_NAMES = ['blah1']
  ...

class B(A):
  PARAM_NAMES = A.PARAM_NAMES + ['blah2']
  ...

我想知道是否有一个更流畅的方法,但没有引用A两次?无法使用super(),因为它不在方法定义中,afaik。我想我可以使用类方法,但这很烦人(因为我真的想要一个属性)。

正确的方法是什么?

3 个答案:

答案 0 :(得分:2)

粗糙总有黑魔法可以做......但问题只是因为你可以...... 你应该吗?

class MyMeta(type):
    items = []
    def __new__(meta, name, bases, dct):
        return super(MyMeta, meta).__new__(meta, name, bases, dct)
    def __init__(cls, name, bases, dct):
        MyMeta.items.extend(cls.items)
        cls.items = MyMeta.items[:]
        super(MyMeta, cls).__init__(name, bases, dct)

class MyKlass(object):
    __metaclass__ = MyMeta

class A(MyKlass):
    items=["a","b","c"]

class B(A):
    items=["1","2","3"]

print A.items
print B.items

因为这会创建一个副本,所以它不会遇到与其他解决方案相同的问题

(请注意,我真的不建议这样做......它只是为了表明你可以)

答案 1 :(得分:2)

这可能是也可能不是很聪明,但技术上可以使用元类来实现。与Joran的方法不同,我使用了一个属性,因此它保留了完整的动态特性(也就是说,如果在定义类之后修改任何类的私有_PARAM_NAMES列表,则每个其他派生类的相应PARAM_NAME属性反映了这种变化)。出于这个原因,我在基类上放了一个add_param方法。

此处假设使用Python 3,PARAM_NAMES属性返回set以避免重复项目。

class ParamNameBuilderMeta(type):
    def __new__(mcl, name, bases, dct):
        names = dct.get("PARAM_NAMES", [])
        names = {names} if isinstance(names, str) else set(names)
        dct["_PARAM_NAMES"] = names
        dct["PARAM_NAMES"] = property(lambda s: type(s).PARAM_NAMES)
        return super().__new__(mcl, name, bases, dct)
    @property
    def PARAM_NAMES(cls):
        # collect unique list items ONLY from our classes in the MRO
        return set().union(*(c._PARAM_NAMES for c in reversed(cls.__mro__)
                    if isinstance(c, ParamNameBuilderMeta)))

用法:

class ParamNameBuilderBase(metaclass=ParamNameBuilderMeta):
    @classmethod
    def add_param(self, param_name):
       self._PARAM_NAMES.add(param_name)

class A(ParamNameBuilderBase):
    PARAM_NAMES = 'blah1'

class B(A):
    PARAM_NAMES = 'blah1', 'blah2'

class C(B):
    pass

检查以确保它适用于类和实例:

assert C.PARAM_NAMES == {'blah1', 'blah2'}
assert C().PARAM_NAMES == {'blah1', 'blah2'}

检查以确保它仍然是动态的:

C.add_param('blah3')
assert C.PARAM_NAMES == {'blah1', 'blah2', 'blah3'}

答案 2 :(得分:1)

您所描述的行为实际上非常具体。你说过你

  

希望每个子类能够添加新的自定义参数

但是你实现它的方式,这将导致不可预测的行为。考虑:

class A(object):
  PARAM_NAMES = ['blah1']

class B(A):
  PARAM_NAMES = A.PARAM_NAMES + ['blah2']

class C(A):pass

print(A.PARAM_NAMES)
print(B.PARAM_NAMES)
print(C.PARAM_NAMES)
A.PARAM_NAMES.append('oops')
print(C.PARAM_NAMES)

我们注意到,选择添加新参数的类具有对参数列表的新引用,而不添加新参数的类具有与其父参数相同的引用。除非小心控制,否则这是不安全的行为。

仅将常量用作类属性更为可靠,或者每次都完全重新定义列表(使其成为元组),这不是" slicker"。否则,我会按照你的建议推荐类方法,并使属性成为实例变量