装饰与继承

问题描述 投票:17回答:5

[如果有可能,您如何决定使用装饰器和继承?

例如,this problem有两个解决方案。

我对Python特别感兴趣。

python inheritance decorator mixins
5个回答
20
投票

装饰者...:

  • ...如果要尝试做的是“包装”,则应使用。包装包括获取,修改(或向其中注册)和/或返回行为与原始对象“几乎完全相同”的代理对象。
  • ...只要不创建大量的代理对象,就可以应用类似于mixin的行为。
  • ...具有隐含的“堆栈”抽象:

例如

@decoA
@decoB
@decoC
def myFunc(...): ...
    ...

相当于:

def myFunc(...): ...
    ...
myFunc = decoA(decoB(decoC(myFunc)))  #note the *ordering*

多重继承...:

  • ...最适合将方法添加到类中;您不能使用它来轻松装饰功能。在这种情况下,如果您需要的只是一组“鸭式样式”额外方法,则可以用来实现类mixin行为。
  • ...如果您的问题不能很好地解决问题,并且存在超类构造函数等问题,可能会有点笨拙。例如,除非显式调用子类__init__方法(通过方法-分辨率-顺序协议)!

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

您经常会发现装饰器(例如备忘录)也是不错的选择,但如果它们返回代理对象,则应谨慎使用;它们的应用顺序很重要。并且彼此之间有太多的装饰器正在以不希望使用的方式使用它们。

如果这是一个“经典继承问题”,或者我需要进行mixin行为的只是方法,我会考虑使用继承。一个经典的继承问题是您可以在任何可以使用父级的地方使用子级的问题。

通常,我尝试在不需要增强任意内容的地方编写代码。


3
投票

您参考的问题不是在装饰器和类之间决定。它使用装饰器is,但是您可以选择使用以下任一种:

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

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

[在他们之间做出决定时,完全是个人喜好。您可以做到彼此之间可以做的所有事情。

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

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

在两种情况下最好使用一个装饰器,该装饰器只返回经过某种修改的原始图形。

edit:在更好地理解了您的问题后,我在Python functools.wraps equivalent for classes处发布了另一种解决方案


1
投票

如果两者都相等,我会更喜欢装饰器,因为您可以对多个类使用相同的装饰器,而继承仅适用于一个特定的类。


1
投票

就个人而言,我会考虑代码重用。装饰器有时比继承更灵活。

让我们以缓存为例。如果要向系统中的两个类添加缓存工具:A和B,具有继承性,则可能最终会拥有ACached和BCached。通过覆盖这些类中的某些方法,您可能会为相同的缓存逻辑复制很多代码。但是,如果在这种情况下使用装饰器,则只需定义一个装饰器即可装饰两个类。

因此,在决定使用哪个扩展功能时,您可能首先要检查扩展功能是否仅适用于此类,或者是否可以在系统的其他部分重用相同的扩展功能。如果它不能被重用,那么继承就可以完成这项工作。否则,您可以考虑使用装饰器。


0
投票

其他答案都很好,但是我想给出一个简短的利弊清单。

mixins的主要优点是可以在运行时使用isinstance来检查类型,并且可以使用MyPy之类的linter来检查它。像所有继承一样,当您具有is-a关系时,应使用它。例如,为了公开特定于数据类的自省变量(例如数据类字段的列表),dataclass应该应该是一个混合对象。

当您没有

is-a关系时,装饰者应该是首选。例如,一个装饰器,用于传播另一个类的文档或在某个集合中注册一个类。

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

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

class B(A):
    ...  # Not affected by the decorator in most cases.

现在,Python具有__init_subclass__,装饰器可以做的所有事情都可以用mixins完成,它们通常确实会影响子子类:

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

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

总而言之,在决定混搭和装饰时应该问的问题是:

  • 是否有is-a模式?您会打电话给isinstance吗?您会在类型注释中使用mixin吗?
  • 您是否希望该行为影响子类?
© www.soinside.com 2019 - 2024. All rights reserved.