Python - 在super().__init__()中访问超类的类属性

5

我有一个类Parent,有许多实例属性,我总是通过传递一个字典来初始化一个实例。就像这样:

info = {
    "name" : "Bob",
    "col" : 5,
    "line" : 10,
    "type" : "Alien"
}

p = Parent(info)

__init__方法中,我不想为每个属性编写this.property_name = value的代码,因为代码会很长。例如:

class Parent(object):

    def __init__(self, kwargs):
        this.name = kwargs.get("name")
        this.col = kwargs.get("col")
        this.line = kwargs.get("line")
        this.type = kwargs.get("type")

所以我想使用一个函数来迭代 dict 并设置这些实例属性。这是我编写的函数:

def initialize_properties(instance, names, kwargs):
    for name in names:
        setattr(instance, name, kwargs.get(name))

看起来我需要将属性名列表 names 存储在某个地方,我决定将其作为类属性存储,因为我希望我的类是 用户友好的(当有人阅读类定义时,他知道这个类具有哪些实例属性)。因此,我将我的类定义更改如下:

class Parent(object):
    props = ("name", "col", "line", "type")

    def __init__(self, kwargs):
        initialize_properties(self, self.props, kwargs)

当不考虑继承时,这个功能可以正常工作。但是当我继承Parent时就会出现问题:

class Child(Parent):
    props = ("foo", "bar")

    def __init__(self, kwargs):
        super().__init__(kwargs)
        initialize_properties(self, self.props, kwargs)

我希望Child的实例能够继承超类Parent中的所有实例属性,并且还有一些特定于子类的实例属性。(这就是我们使用继承的原因,不是吗?) 所以,我重写了类属性props来定义子类特定的属性。

但是它并没有起作用。

info = {
    "name" : "Bob",
    "col" : 5,
    "line" : 10,
    "type" : "Alien",
    "foo" : 5,
    "bar" : 10
}

c = Child(info)

实例c只定义并设置了c.fooc.bar,而c.name未定义。

经过一番查找,我发现当通过super()函数调用Parent.__init__(self, kwargs)时,传递的self参数是Child类的,因此self.props会被解析为Child.props

如果我想要在Parent.props中设置实例属性,则必须在Parent.__init__(self, kwargs)中显式使用Parent.props,即:

class Parent(object):
    props = ("name", "col", "line", "type")

    def __init__(self, kwargs):
        initialize_properties(self, Parent.props, kwargs)

这样可以解决问题,但我认为它不太“干净”,因为您必须硬编码类名 Parent

我的问题是:在调用一系列super().__init__()来初始化子类实例时,是否有办法检测当前类并访问其类属性?


2
这似乎是很多工作,为了避免“每个属性都要写this.property_name = value”,因为代码会变得非常冗长。 - juanpa.arrivillaga
3个回答

3
你可以实现一个方法,从所有的父类中收集props并在__init__中使用它:
class Parent(object):
    props = ("name", "col", "line", "type")

    def __init__(self, **kwargs):
        def __init__(self, kwargs):
        initialize_properties(self, self.get_props(), kwargs)

    def get_props(self):
        return sum((getattr(k, 'props', ()) for k in self.__class__.mro()), ())

现在,你可以按照你想要的方式简单地定义子类。你甚至不需要重写构造函数:

class Child(Parent):
    props = ("foo", "bar")

话虽如此,你声称你希望你的类是“人性化”的。如果一个人阅读了你的子类,他们会很高兴地看到:

class Child(Parent):
    props = 'foo', 'bar', 'name', 'col', 'line', 'type'

并且在一个地方拥有类的所有 属性


1

self.props首先引用实例属性,然后是类属性和父类的类属性,...

通过定义Child.propsself.props引用Child.props类属性,停止搜索父类中的属性。

那么如何使Child.props也包括父级的props

class Child(Parent):
    props = Parent.props + ("foo", "bar")   # <---

    def __init__(self, kwargs):
        super().__init__(kwargs)
        initialize_properties(self, self.props, kwargs)

0

回答你的问题:

当你调用一系列super().init()时,有没有办法检测当前类并访问其类属性?

在我看来,没有办法。

但是,你可以使用装饰器来简化你的代码。

def add_props(names, cls=None):
    if cls is None:
        return lambda cls:add(names, cls)
    for c in cls.__bases__:
        names.extend(c.props)
    names = list(set(names))
    cls.props = names
    return cls


class A:
    props = ['a','b']


@add_props(names=['c'])
class B(A):
    pass

输出:

In [69]: B.props
Out[69]: ['a', 'b', 'c']

但是,必须说如果您想要更人性化的代码,我认为最好的方法不是使用props,而是在您的__init__方法中编写代码。

class Root:
    def __init__(self, a=None, b=None):
        self.a = a
        self.b = b


class Child(Root):
    def __init__(self, c=None, d=None, **kwargs):
        self.c = c
        self.d = d
        super().__init__(**kwargs)

你也可以方便地按照自己的喜好传递字典

data = {'a': 1}
c = Child(**data)

它不仅对人类友好,而且对大多数编辑器也友好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接