【问题标题】:How can I parameterize Mixins in python, without calling their constructor explicitly?如何在 python 中参数化 Mixins,而不显式调用它们的构造函数?
【发布时间】:2019-12-18 17:12:21
【问题描述】:

我想在 python 中更多地使用 Mixins 并且喜欢以下模式:

class Person:
  [...]

class Greetable:
  greeting: str

  def __init__(*args, **kwargs):
    super().__init(*args, **kwargs)
    ... greeting related setup

  def greet(self):
    print(f"{self.greeting} sir or madam!")

class Sailor(Greetable, Person):
  def __init__(self):
    super().__init__()

    self.greeting = "Ahoy"

>>> Sailor().greet()
"Ahoy sir or madam!"

但我还没有解决参数化混入的问题。在我的工作中,我看到很多明确的__init__ 调用超类alá

class A(B, FloorMixin):
  def __init__(desk, chair, floor):
    B.__init__(self, desk, chair)
    FloorMixin.__init__(floor)

我看到了分解参数并显式分配它们的用途,但我想保留上述 Mixin 示例的 __init__ 的“passthrough”属性。

我只能考虑将 Mixin 的所有参数都作为特定的关键字参数,从 **kwargs 参数中获取 pop'ed 或者仅依赖于 mixin 中存在的字段并且必须在之前设置它们,这会将最后一个示例变成:

class A(FloorMixin, B):
  def __init__(desk, chair, floor):
    self._floor = floor  # FloorMixin expects a _floor attribute

    super().__init__(desk, chair)

【问题讨论】:

  • 这基本上是Python's super() considered super! 中提出的建议:提取您将处理的参数,然后传递其余的。 (在对fuhm.net/super-harmful 的结论中重申了同样的建议,结果证明这与其说是反驳,不如说是对如何正确使用super 的进一步说明。)
  • @chepner 这是否意味着,Mixins 不应该盲目地调用super().__init__(*args, **kwargs),因为在这种情况下,构造函数可能会被多次调用?
  • 它们不会被多次调用,因为每个类只在方法解析顺序中出现一次。他们应该像任何其他类一样使用super
  • 如果你从一个使用super的类继承,你也应该使用super。如果你从一个使用super的类继承,那么从你那里继承的任何东西都不应该使用super,除非你遵循从PSCS中的非合作类继承的建议。

标签: python mixins


【解决方案1】:

正确使用super 的关键是层次结构中涉及的每个类都应该假设其他所有人也会调用super。对于除object 之外的每个类都是如此,object 始终是任何继承层次结构中的根类。

你的例子

class A(B, FloorMixin):
  def __init__(self, desk, chair, floor):
    B.__init__(self, desk, chair)
    FloorMixin.__init__(floor)

这意味着ABFloorMixin 都应该调用super().__init__,并且在实例化A 时应该使用关键字参数,这样就不会有基于排序的冲突。

class B:
    def __init__(self, desk, chair, **kwargs):
        super().__init__(**kwargs)
        # Do stuff with desk and chair

class FloorMixin:
    def __init__(self, floor, **kwargs):
        super().__init__(**kwargs)
        # Do stuff with floor

class A(B, FloorMixin):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # If there's nothing else to do, you don't
        # even need to define A.__init__

# With keyword arguments, order doesn't matter
# Each __init__ will find the arguments it needs
# and pass on the rest
a = A(floor="...", chair="...", desk="...")

A 的方法解析顺序是[A, B, FloorMixin, object],因此每次对super().__init__ 的调用都会从行中的下一个类调用__init__A.__init__ 调用B.__init__,后者调用FloorMixin.__init__,后者调用object.__init__。重要的是,请注意,这意味着在运行时B.__init__ 调用了B 的作者甚至可能不知道的类的__init__。这就是为什么接受意料之外的关键字参数并传递它们很重要的原因。

【讨论】:

  • 这是否妨碍我写a = A(floor_obj, chair_obj, desk_obj),我。 e.强迫我用 kwargs 显式调用函数?
  • 如所写,是的。您可以使用def __init__(self, floor, chair, desk, **kwargs)(或您喜欢的任何顺序)定义A.__init__,然后调用super().__init__(floor=floor, chair=chair, desk=desk, **kwargs)。不过,IMO 仅对类引入的参数使用名称的约定更简洁一些。
猜你喜欢
  • 2021-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-05
  • 1970-01-01
相关资源
最近更新 更多