在 Python 中实现装饰器模式

     2023-03-31     202

关键词:

【中文标题】在 Python 中实现装饰器模式【英文标题】:Implementing the decorator pattern in Python 【发布时间】:2011-03-08 07:55:46 【问题描述】:

我想在 Python 中实现decorator pattern,我想知道是否有一种方法可以编写一个只实现它想要修改的函数的装饰器,而无需为刚刚转发到的所有函数编写样板文件被装饰的对象。像这样:

class foo(object):
    def f1(self):
        print "original f1"
    def f2(self):
        print "original f2"

class foo_decorator(object):
    def __init__(self, decoratee):
        self._decoratee = decoratee
    def f1(self):
        print "decorated f1"
        self._decoratee.f1()
    def f2(self):              # I would like to leave that part out
        self._decoratee.f2()

我希望自动将呼叫foo_decorator.f2 转接到decoratee.f2。有没有办法编写一个通用方法,将所有未实现的函数调用转发到decoratee

【问题讨论】:

你能举一个简单的子类化不起作用的例子吗?您也可以动态子类化 - 这种模式似乎是一种解决方法,适用于无法做到这一点或不支持多重继承的语言。 我想在运行时装饰对象。我想将不同的装饰器应用于一个对象并能够再次删除它们。子类化不能在创建实例后更改它,或者可以吗? 如果你有 A 类并更改 A,即添加一个新方法 A.foo = lambda self: self 这将反映在 A .. 的所有实例上,因为 everything 是在运行时确定。生成绝对不可维护的代码的好方法。 @THC4K:装饰器模式(与 python 装饰器相反)用于在运行时向 object 添加行为。如果正确完成,这实际上是非常可维护的,这就是我发布这个问题的原因。我想在 Python 中找到解决方法。 【参考方案1】:

你可以使用__getattr__:

class foo(object):
    def f1(self):
        print "original f1"
    def f2(self):
        print "original f2"

class foo_decorator(object):
    def __init__(self, decoratee):
        self._decoratee = decoratee
    def f1(self):
        print "decorated f1"
        self._decoratee.f1()
    def __getattr__(self, name):
        return getattr(self._decoratee, name)

u = foo()
v = foo_decorator(u)
v.f1()
v.f2()

【讨论】:

这是个好主意。但是需要注意的是,如果你使用抽象基类(即 import abc 并使用 metaclass = abc.ABCMeta 来定义抽象方法和属性),那么它将不起作用。 这不适用于像 str 这样的函数。那里发生了什么? @JohnKitchin:重定向不适用于双下划线方法,因为这些必须(出于性能原因)在类而不是实例上定义。 (more info) 我相信唯一的解决方法是显式定义和重定向方法,如 __str__ 这真的是设置装饰器模式的“公认”方式吗?覆盖“双下划线”方法感觉很hacky。【参考方案2】:

作为 Philipp 答案的补充;如果您不仅需要装饰,还需要保留对象的 type,Python 允许您在运行时对实例进行子类化:

class foo(object):
    def f1(self):
        print "original f1"

    def f2(self):
        print "original f2"


class foo_decorator(object):
    def __new__(cls, decoratee):
        cls = type('decorated',
                   (foo_decorator, decoratee.__class__),
                   decoratee.__dict__)
        return object.__new__(cls)

    def f1(self):
        print "decorated f1"
        super(foo_decorator, self).f1()


u = foo()
v = foo_decorator(u)
v.f1()
v.f2()
print 'isinstance(v, foo) ==', isinstance(v, foo)

对于您的示例来说,这比绝对必要的要复杂一些,因为您事先知道要装饰的类。

可能就足够了:

class foo_decorator(foo):
    def __init__(self, decoratee):
        self.__dict__.update(decoratee.__dict__)

    def f1(self):
        print "decorated f1"
        super(foo_decorator, self).f1()

【讨论】:

这似乎再次执行了被装饰者的 __init__(),传递给它decoratee,这可能是不希望的。【参考方案3】:

这可能不是最佳实践,但您可以向实例添加功能,正如我所做的那样,帮助将我的代码从 Django 的 ORM 转换为 SQLAlachemy,如下所示:

def _save(self):
    session.add(self)
    session.commit()
setattr(Base,'save',_save)

【讨论】:

我也考虑过这个,但我觉得很不对劲。也许是因为我来自 C++? 感觉不对是公平的。它可以被认为是猴子补丁。然而,它适用于很少的代码,这是一个非常不同的世界,当一切都是动态的并且通过引用时,它与 C 相比有更多的可能性。 我喜欢它。对于这个例子,其他任何事情都会增加 IMO 的复杂性。【参考方案4】:

链接的 Wikipedia 文章中的 UML 图是错误的,您的代码也是如此。

如果您遵循“装饰器模式”,则装饰器类派生自基装饰类。 (在 UML 图中,缺少从 WindowDecorator 到 Window 的继承箭头)。

class foo_decorator(foo):

您不需要实现未修饰的方法。

顺便说一句:在强类型语言中还有一个原因,为什么装饰器必须从装饰类派生:否则你将无法链接装饰器。

【讨论】:

UML 图显示了基类的继承和聚合(装饰器模式的两个重要部分)。你从基类继承,所以你看起来像原来的,你持有对基类实例的引用,你可以隔离对它的访问。***文章在第 1 步和第 2 步中这样说:“(1) 将原始组件子类化,(2) 添加一个组件指针作为字段”。所以最初的问题实际上是关于 Python 鸭子类型,而不是关于装饰器模式,也不是关于 Python 装饰器!【参考方案5】:

在我的一个项目中,我还需要做一件事,那就是即使是底层对象也应该实际执行在装饰器中重新实现的方法。如果您知道目标位置,实际上很容易做到。

用例是:

我有一个带有方法 A 和 B 的对象 X。 我创建了一个覆盖 A 的装饰器类 Y。 如果我实例化 Y(X) 并调用 A,它将按预期使用修饰后的 A。 如果 B 调用 A,那么如果我实例化 Y(X) 并在装饰器上调用 B,则从 B 内部的调用会转到原始对象上的旧 A,这是不希望的。我希望旧 B 也调用新 A。

有可能达到这样的行为:

import inspect
import six      # for handling 2-3 compatibility

class MyBaseDecorator(object):
    def __init__(self, decorated):
        self.decorated = decorated

    def __getattr__(self, attr):
       value = getattr(self.decorated, attr)
       if inspect.ismethod(value):
           function = six.get_method_function(value)
           value = function.__get__(self, type(self))
       return value

class SomeObject(object):
    def a(self):
        pass

    def b(self):
        pass

class MyDecorator(MyBaseDecorator):
    def a(self):
        pass

decorated = MyDecorator(SomeObject())

这可能无法开箱即用,因为我从头顶输入了除了 getattr 方法之外的所有其他内容。

代码在装饰对象中查找请求的属性,如果它是一个方法(现在不适用于属性,但支持它们的更改应该不会太难),然后代码拉取实际函数在方法之外并使用描述符接口调用它将函数“重新绑定”为方法,但在装饰器上。然后它被返回并很可能被执行。

这样做的效果是,如果b 曾经在原始对象上调用a,那么当你装饰了对象并且有来自装饰器的任何方法调用时,装饰器确保所有访问的方法都是而是绑定到装饰器,因此使用装饰器而不是原始对象查找事物,因此装饰器中指定的方法优先。

P.S.:是的,我知道它看起来很像继承,但这是在多个对象的组合意义上完成的。

【讨论】:

【参考方案6】:

补充@Alec Thomas 的回复。我修改了他的答案以遵循装饰者模式。这样你就不需要提前知道你要装饰的班级。

class Decorator(object):
    def __new__(cls, decoratee):
        cls = type('decorated',
                   (cls, decoratee.__class__),
                   decoratee.__dict__)
        return object.__new__(cls)

然后,您可以将其用作:

class SpecificDecorator(Decorator):
    def f1(self):
        print "decorated f1"
        super(foo_decorator, self).f1()

class Decorated(object):
    def f1(self):
        print "original f1"


d = SpecificDecorator(Decorated())
d.f1()

【讨论】:

【参考方案7】:

在 Python 3 中,Philipp 接受的答案提出了RuntimeError: maximum recursion depth exceeded

对我有用的方式:

class Foo(object):
    def f1(self):
        print("original f1")

    def f2(self):
        print("original f2")

class FooDecorator(object):
    def __init__(self, decoratee):
        self._decoratee = decoratee

    def f1(self):
        print("decorated f1")
        return self._decoratee.f1()

    def __getattr__(self, name):
        if name in ['f1', '_decoratee']:
            raise AttributeError()
        return getattr(self._decoratee, name)

f = FooDecorator(Foo())
f.f1()
# decorated f1
# original f1
f.f2()
# original f2

解决方法的灵感来自Ned Batchelder's blog

【讨论】:

如何在 PHP 中实现装饰器?

】如何在PHP中实现装饰器?【英文标题】:HowtoimplementadecoratorinPHP?【发布时间】:2010-10-3105:04:27【问题描述】:假设有一个名为“Class_A”的类,它有一个名为“func”的成员函数。我希望“func”通过将Class_A包装在装饰器类中来... 查看详情

如何在 Java 中实现包装装饰器?

】如何在Java中实现包装装饰器?【英文标题】:HowtoimplementawrapperdecoratorinJava?【发布时间】:2016-04-0807:20:35【问题描述】:问题是创建现有对象的动态增强版本。我无法修改对象的Class。相反,我必须:子类化它将现有对象包装... 查看详情

设计模式——装饰器模式

...是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理             未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。ConcreteComponent... 查看详情

在 PHP 中构造装饰器

...发人员,试图扩展我的工具箱并学习更多技巧。我最近在Python中遇到了一种称为“装饰”的模式,我想知道是否/如何在PHP中实现它,因为我有一个现有的PHP代码库。这是我的意思的一个简短示例:importtimedeflog_calls(func):defwrapper(*... 查看详情

装饰器模式实现

...据“HeadFirstDesignPatterns”一书(用Java编写)中的代码在C#中实现装饰器模式。我刚开始使用C#,因此对语法还是很陌生,所以我不确定为什么我不能让下面注释的代码行工作。这是装饰器模式中的第一个抽象基类及其派生类:using... 查看详情

是否有用于在 Django 中实现视图模型装饰器 ala Draper 的库?

】是否有用于在Django中实现视图模型装饰器alaDraper的库?【英文标题】:IstherealibraryforimplementingviewmodeldecoratorsalaDraperinDjango?【发布时间】:2015-07-0219:24:14【问题描述】:我想要“视图模型装饰器”,例如Draper在Django中为Rails实现... 查看详情

如何在 Flask 中实现登录所需的装饰器

】如何在Flask中实现登录所需的装饰器【英文标题】:HowtoimplementloginrequireddecoratorinFlask【发布时间】:2016-04-0210:15:31【问题描述】:我有2个可以协同工作的Flask应用程序(不同的项目)。一个实现了一些使用令牌进行身份验证的... 查看详情

CQRS 命令/查询装饰器

...我正在尝试使用Autofac和MediatR库在简单的控制台应用程序中实现CQRS模式。该项目具有以下结构:我想以这种方式处理命令和查询:请求处理程序装饰器应用命令/查询处理程序装饰器应用命令/ 查看详情

装饰器实现单例模式

我们知道,python中装饰器无非是对对象的重新包装,这个对象可以是函数,也可以是一个类@decoratedeftest():相当于test=decorate(test)defdecorate(func): defwrap(*args,**kwargs): ***** return returnwrap不难发现,test其实就是wrap,我们可以在wrap中实... 查看详情

设计模式之装饰模式(代码片段)

...是父类,建议接口,面向接口编程),声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。ConcreteComponent(具体构件):它是抽象构件类... 查看详情

设计模式—装饰器模式

装饰器模式(DecoratorPattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法... 查看详情

java设计模式之装饰器模式

适AT java设计模式之装饰器模式装饰器模式装饰器模式(DecoratorPattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个... 查看详情

python入门到精通python装饰器的应用详解(代码片段)

...1f680;欢迎小伙伴们点赞👍、收藏⭐、留言💬目录Python中的装饰器及其应用什么是装饰器模式在不修改原有类的情况下去添加一个新的功能从实际例子来看装饰器这个时候,如果不想修改原有的函数,又想增加一... 查看详情

修饰器模式

装饰器模式装饰器模式(DecoratorPattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在... 查看详情

设计模式之装饰器模式(代码片段)

...。(1).比如说我现在有一个接口为Noodles可以做一碗面,自类中实现为刀削面,方便面。但是我们现在想加个配料(比如鸡蛋,火腿啥的)(2).Noodles接口publicinterfaceNoodles//可以制作一碗面publicvoidcreateABlowOfNoodles();(3)DaoXiaoNoodlespublicclassDaoX... 查看详情

在python中实现可选的上下文管理器(代码片段)

我们有一个具有以下使用模式的代码库:factory=DataFactory(args)dataset=factory.download_and_cache_big_dataset(key)metadata=dataset.get_some_metadata()目前,download_and_cache_big_dataset从S3获取一个非常大的文件并将其放在某处。除此之外,确实如此filenam 查看详情

spring十三种模式之--装饰器模式

解释:装饰器模式(DecoratorPattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持... 查看详情

装饰器模式(代码片段)

1、引言最近在看Openstack源码,发现里面大量使用了装饰器模式,以前也接触过装饰器模式,只了解它是在原有对象上加了一层封装,保持原有逻辑不变。今天仔细想想,完全可以再重新写一个类,把原来的对象包进去,将原来... 查看详情