直接传递** kwargs到super().__ init__的关键字

时间:2018-05-25 11:16:35

标签: python class

可以有一个“神奇的关键字”(显然只有在未指定**kwargs时才有效),以便__init__(*args, ***pass_through_kwargs)使所有意外的kwargs直接传递给{ {1}}? (在后台,没有用户知识)这将使可读性更容易,并且可以通过自动完成和自动文档安全地忽略它。

在下图中,super().__init__仅使用Mouse进行实例化。只是为了让name - 调用一行,我必须添加super(以指定奶酪的**kwargs)并手动将它们交给我。我不能告诉number_of_holesMouse“无论你不知道什么:只需将它直接传递给Animal”? Class diagram with red path of <code>super()</code> calls

当然你可能想要考虑碰撞,但我认为这可能会大大提高代码的可读性

1 个答案:

答案 0 :(得分:3)

更新问题的答案

事实上, 是可能的。在提供演示代码之前,我先简单介绍一下:

  • 这种自动属性访问非常糟糕的风格。你无法记录它,让人们滥用/忽略封装概念。

  • 根本不是不言自明的;你不能使用duck typing,因为突然间每个对象都可以有任意属性。

  • 您真正想要做的是在具有清晰焦点并带来相应功能的对象中创建一致且易于理解的设计。

但是,如果您仍想测试它,可以使用**kwargs并将向上传递给超级构造函数;它始终包含在方法签名中明确设置的 not 的所有关键字参数:

def testme(color='red', **kwargs):
    print(kwargs)

testme(width=3)

# See that 'color' is not in kwargs dict if running this code:
>>> {'width': 3}

就是这种情况,因为显式设置的关键字参数被赋值给函数签名中给出的变量名;其余部分在kwargs中捕获。

此行为允许您实际过滤每个期望__init__中的关键字参数,并将其余参数传递给超级类&#39; __init__

class Animal(object):
    def __init__(self, num_legs, **kwargs):
        self.num_legs = num_legs

        for key, value in kwargs.iteritems():
            if not hasattr(self, key):
                print(
                    'Generic attribute assignment: {:s} = {:s}'
                    .format(key, str(value)))
                setattr(self, key, value)

    def __str__(self):
        res = '<Animal ({:s}):\n'.format(self.__class__.__name__)
        for key in dir(self):
            if key.startswith('__'):
                continue
            res += '  {:s} = {:s}\n'.format(key, str(getattr(self, key)))
        res += '>'
        return res


class Mouse(Animal):
    def __init__(self, color='gray', **kwargs):
        super(Mouse, self).__init__(4, **kwargs)
        self.color = color


class MickeyMouse(Mouse):
    def __init__(self, **kwargs):
        super(MickeyMouse, self).__init__(color='black', **kwargs)


if __name__ == '__main__':
    mm = MickeyMouse(dog='Pluto')
    print(mm)

这将产生:

Generic attribute assignment: dog = Pluto
<Animal (MickeyMouse):
  color = black
  dog = Pluto
  num_legs = 4
>

理由是,关键字'dog'永远不会在任何__init__方法中出现,因此始终保留在kwargs中,直到最后一个超类&#39; __init__遍历dict并设置相应的属性。打印分配消息的位置。之后,专门的__init__方法会设置其值(此处为:'color')并且您已完成。

你可以解释一下你的身材吗?箭头代表依赖关系,所有权,继承,......?

上一个/过时的答案

如果您的班级MickeyMouse需要调用CheeseMouse的初始化,则需要先确定其依赖关系。

从类定义构造具体对象会产生该类的有状态表示。因此,如果您创建一个MickeyMouse对象,则可能需要它调用其超类&#39;构造函数,例如,Mouse类之一 - 给定MickeyMouse继承自Mouse。如果您现在希望初始化MickeyMouse也调用Cheese init,它将创建一个新对象,主要有两个对象:鼠标(即MickeyMouse )和奶酪,没人知道它来自哪里。

但也许我还没有得到正确的应用案例。无论如何,关于你的实施方法:

没有方法重载

正如您所注意到的,类的__init__方法是无法重载的单个实例(魔术)方法。而且由于设计不可能,你不应该尝试重载,甚至不要创建试图实现类方法重载的方法交换实现。

在您的示例中,您声明您希望能够使用不同的args和kwargs组合创建单个类的实例,并且神奇地解析为正确的处理。

可能的方法

(1)仅使用kwargs:

class Cheese(object):
    def __init__(self, num_holes=0, weight=None, name=None):
        # Add logic depending on kwargs that are not None
        pass

像这样使用它:

cheddar = Cheese()
gouda = Cheese(num_holes=3)
owned_cheddar = Cheese(name='MickeyMouse')

(2)使用静态构造函数方法。

class Cheese(object):
    def __init__(self, num_holes, weight, name):
        # Default construct using all args
        pass

    @staticmethod
    with_owner(name):
        return Cheese(0, None, name)

像这样使用它:

cheddar = Cheese(0, None, None)
owned_cheddar = Cheese.with_owner('MickeyMouse')

请注意,使用静态方法对于您的应用程序来说是不灵活的;在构建来自不同数据结构的对象时,例如.from_dict(...).from_csv(...)等,可以更好地使用它。