装饰者与继承

Nei*_*l G 16 python inheritance decorator

当两者都可能时,您如何决定使用装饰器和继承?

例如,这个问题有两个解决方案.

我对Python特别感兴趣.

nin*_*cko 20

装饰...:

  • ......如果你要做的就是"包装",应该使用... 包装包括取东西,修改(或用某些东西注册),和/或返回一个行为"几乎完全"的代理对象.
  • ...只要您不创建大量代理对象,就可以应用类似mixin的行为.
  • ...有一个隐含的"堆栈"抽象:

例如

@decoA
@decoB
@decoC
def myFunc(...): ...
    ...
Run Code Online (Sandbox Code Playgroud)

相当于:

def myFunc(...): ...
    ...
myFunc = decoA(decoB(decoC(myFunc)))  #note the *ordering*
Run Code Online (Sandbox Code Playgroud)

多重继承...:

  • ...最适合在类中添加方法; 你不能用它来轻松地装饰功能.在这种情况下,如果您需要的是一组"鸭子式"额外方法,它可用于实现类似mixin的行为.
  • ...如果你的问题与它不匹配,超类构造函数等问题可能有点笨拙.例如,__init__除非明确调用子类方法(通过方法解析顺序),否则不会调用子类方法协议)!

总而言之,如果他们没有返回代理对象,我会使用装饰器进行类似mixin的行为.一些示例将包括任何返回原始函数的装饰器,稍微修改(或在某处注册或将其添加到某个集合之后).

你经常会找到装饰器的东西(比如memoization)也是很好的候选者,但如果他们返回代理对象,应该适度使用; 它们的应用顺序很重要.并且太多的装饰器在彼此之上以他们不打算使用的方式使用它们.

如果它是一个"经典继承问题",或者如果我对mixin行为所需要的只是方法,我会考虑使用继承.经典的继承问题是您可以在任何可以使用父级的地方使用子级的问题.

一般来说,我尝试编写代码,而不必增强任意内容.

  • 考虑到这一点,我认为关键点是`issubclass`和`isinstance`,它们可以通过继承获得.如果你想要那些,你可能想要继承.这证明了Collections.*,是使用继承而不是装饰器实现的. (3认同)
  • 我还想指出,因为在你的回答中并不明显Python中的多重继承(不像C++)由于合作继承而具有堆栈抽象.每个连续的基类的`__init__`都会看到一个修改过的对象. (2认同)

Nei*_*l G 7

其他答案都很好,但我想给出一个简洁的优缺点列表。

mixins 的主要优点是可以在运行时使用检查类型isinstance,并且可以使用 MyPy 等 linter 进行检查。与所有继承一样,当存在is-a关系时应该使用它。例如,dataclass可能应该是一个 mixin,以便公开特定于数据类的内省变量,例如数据类字段列表。

当您没有is-a关系时,应该首选装饰器。例如,从另一个类传播文档或在某个集合中注册类的装饰器。

装饰通常只影响它所装饰的类,而不影响从基类继承的类:

@decorator
class A:
    ...  # Can be affected by the decorator.

class B(A):
    ...  # Not affected by the decorator in most cases.
Run Code Online (Sandbox Code Playgroud)

既然 Python 有了__init_subclass__,装饰器能做的一切都可以通过 mixins 来完成,而且它们通常会影响子子类:

class A(Mixin):
    ... # Is affected by Mixin.__init_subclass__.

class B(A):
    ... # Is affected by Mixin.__init_subclass__.
Run Code Online (Sandbox Code Playgroud)

Mixins还有另一个优点,那就是它们可以提供空的基类方法。子类可以用一些“增强”行为重写这些方法,然后调用 super. 装饰器不能轻易提供这样的基类方法。这是 mixin 更加灵活的另一种方式。

总之,在决定 mixin 和装饰时应该问的问题是:

  • 是否存在is-a模式?
    • 你会打电话吗isinstance
    • 您会在类型注释中使用 mixin 吗?
  • 您希望这种行为影响孩子们的班级吗?
  • 您需要增强方法吗?

一般来说,倾向于继承。


nin*_*cko 5

您引用的问题不是在装饰器和类之间做出决定。它使用装饰器,但您可以选择使用:

  • 一个装饰器,返回一个类
  • 装饰器,返回一个函数

装饰器只是“包装”模式的一个奇特名称,即用其他东西替换某些东西。实现取决于您(类或函数)。

在它们之间做出决定时,这完全取决于个人喜好。你可以与另一个人一起做你能做的一切。

  • 如果装饰一个函数,您可能更喜欢返回代理函数的装饰器
  • 如果装饰一个类,您可能更喜欢返回代理类的装饰器

(为什么这是一个好主意?可能会假设修饰函数仍然是函数,修饰类仍然是类。)

在这两种情况下,更好的是使用一个装饰器,它只返回原始的,并以某种方式修改。

编辑:在更好地理解你的问题之后,我在 Python functools.wraps 上发布了另一个解决方案,相当于类