如何装饰一个班级?

2025-03-05 09:14:00
admin
原创
108
摘要:问题描述:如何创建适用于类的装饰器?具体来说,我想使用装饰器向类addID添加成员,并更改构造函数以接受该成员的参数。__id`__init__`iddef getId(self): return self.__id classdecorator addID(cls): def __init__(s...

问题描述:

如何创建适用于类的装饰器?

具体来说,我想使用装饰器向类addID添加成员,并更改构造函数以接受该成员的参数。__id`__init__`id

def getId(self): return self.__id

classdecorator addID(cls):
    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        cls.__init__(self, *args, **kws)

@addID
class Foo:
    def __init__(self, value1):
        self.value1 = value1

以上应该相当于:

class Foo:
    def __init__(self, id, value1):
        self.__id = id
        self.value1 = value1

    def getId(self): return self.__id

解决方案 1:

除了类装饰器是否是解决你的问题的正确方法这个问题之外:

在 Python 2.6 及更高版本中,有带有 @ 语法的类装饰器,因此你可以编写:

@addID
class Foo:
    pass

在旧版本中,您可以通过另一种方式执行此操作:

class Foo:
    pass

Foo = addID(Foo)

但请注意,这与函数装饰器的工作方式相同,并且装饰器应返回新的(或修改后的原始)类,而这不是您在示例中所做的。 addID 装饰器将如下所示:

def addID(original_class):
    orig_init = original_class.__init__
    # Make copy of original __init__, so we can call it without recursion

    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        orig_init(self, *args, **kws) # Call the original __init__

    original_class.__init__ = __init__ # Set the class' __init__ to the new one
    return original_class

然后,您可以按照上面所述使用适合您的 Python 版本的语法。

但是我同意其他人的观点,如果您想覆盖,继承更适合__init__

解决方案 2:

我赞同你的观点,你可能希望考虑子类而不是你概述的方法。但是,不知道你的具体情况,YMMV :-)

您所考虑的是元类。__new__元类中的函数会传递该类的完整建议定义,然后可以在创建类之前重写该定义。那时,您可以将构造函数替换为新构造函数。

例子:

def substitute_init(self, id, *args, **kwargs):
    pass

class FooMeta(type):

    def __new__(cls, name, bases, attrs):
        attrs['__init__'] = substitute_init
        return super(FooMeta, cls).__new__(cls, name, bases, attrs)

class Foo(object):

    __metaclass__ = FooMeta

    def __init__(self, value1):
        pass

替换构造函数可能有点戏剧性,但语言确实为这种深度自省和动态修改提供了支持。

解决方案 3:

没有人解释过你可以动态定义类。因此,你可以有一个定义(并返回)子类的装饰器:

def addId(cls):

    class AddId(cls):

        def __init__(self, id, *args, **kargs):
            super(AddId, self).__init__(*args, **kargs)
            self.__id = id

        def getId(self):
            return self.__id

    return AddId

它可以在 Python 2 中使用(Blckknght 的评论解释了为什么应该在 2.6+ 中继续这样做),如下所示:

class Foo:
    pass

FooId = addId(Foo)

super()在 Python 3 中是这样的(但在课堂上使用时要小心):

@addId
class Foo:
    pass

所以你可以鱼熊掌兼得——继承装饰器!

解决方案 4:

我同意继承更适合解决所提出的问题。

我发现这个问题在装饰课程方面确实很方便,谢谢大家。

以下是根据其他答案提供的另外几个示例,包括继承如何影响 Python 2.7 中的内容(以及@wraps,它维护原始函数的文档字符串等):

def dec(klass):
    old_foo = klass.foo
    @wraps(klass.foo)
    def decorated_foo(self, *args ,**kwargs):
        print('@decorator pre %s' % msg)
        old_foo(self, *args, **kwargs)
        print('@decorator post %s' % msg)
    klass.foo = decorated_foo
    return klass

@dec  # No parentheses
class Foo...

通常你需要向装饰器添加参数:

from functools import wraps

def dec(msg='default'):
    def decorator(klass):
        old_foo = klass.foo
        @wraps(klass.foo)
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass
    return decorator

@dec('foo decorator')  # You must add parentheses now, even if they're empty
class Foo(object):
    def foo(self, *args, **kwargs):
        print('foo.foo()')

@dec('subfoo decorator')
class SubFoo(Foo):
    def foo(self, *args, **kwargs):
        print('subfoo.foo() pre')
        super(SubFoo, self).foo(*args, **kwargs)
        print('subfoo.foo() post')

@dec('subsubfoo decorator')
class SubSubFoo(SubFoo):
    def foo(self, *args, **kwargs):
        print('subsubfoo.foo() pre')
        super(SubSubFoo, self).foo(*args, **kwargs)
        print('subsubfoo.foo() post')

SubSubFoo().foo()

输出:

@decorator pre subsubfoo decorator
subsubfoo.foo() pre
@decorator pre subfoo decorator
subfoo.foo() pre
@decorator pre foo decorator
foo.foo()
@decorator post foo decorator
subfoo.foo() post
@decorator post subfoo decorator
subsubfoo.foo() post
@decorator post subsubfoo decorator

我使用了函数装饰器,因为我发现它们更简洁。这是一个用于装饰类的类:

class Dec(object):

    def __init__(self, msg):
        self.msg = msg

    def __call__(self, klass):
        old_foo = klass.foo
        msg = self.msg
        def decorated_foo(self, *args, **kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass

一个更强大的版本,可以检查这些括号,并且如果装饰类中不存在这些方法,它就可以工作:

from inspect import isclass

def decorate_if(condition, decorator):
    return decorator if condition else lambda x: x

def dec(msg):
    # Only use if your decorator's first parameter is never a class
    assert not isclass(msg)

    def decorator(klass):
        old_foo = getattr(klass, 'foo', None)

        @decorate_if(old_foo, wraps(klass.foo))
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            if callable(old_foo):
                old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)

        klass.foo = decorated_foo
        return klass

    return decorator

检查assert装饰器是否未在括号内使用。如果使用过,则将被装饰的类传递给msg装饰器的参数,从而引发AssertionError

@decorate_if仅适用于decorator如果condition评估为True

使用getattrcallable测试和,@decorate_if以便当foo()被装饰的类中不存在该方法时,装饰器不会中断。

解决方案 5:

这不是一个好的做法,因此也没有机制可以做到这一点。实现你想要的正确方法是继承。

查看课程文档。

举个小例子:

class Employee(object):

    def __init__(self, age, sex, siblings=0):
        self.age = age
        self.sex = sex    
        self.siblings = siblings

    def born_on(self):    
        today = datetime.date.today()

        return today - datetime.timedelta(days=self.age*365)


class Boss(Employee):    
    def __init__(self, age, sex, siblings=0, bonus=0):
        self.bonus = bonus
        Employee.__init__(self, age, sex, siblings)

这样Boss就拥有了一切Employee,还有他自己的__init__方法和自己的成员。

解决方案 6:

这里实际上有一个相当不错的类装饰器的实现:

https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py

我实际上认为这是一个非常有趣的实现。因为它将它修饰的类子类化,所以它在检查等方面的行为与该类完全相同isinstance

__init__它还有一个额外的好处:自定义 django 表单中的语句进行修改或添加并不罕见,self.fields因此最好在相关类的全部运行之后self.fields进行更改。__init__

非常聪明。

然而,在您的类中,您实际上希望装饰器能够改变构造函数,我认为这不是类装饰器的一个好用例。

解决方案 7:

这是一个回答返回类的参数问题的示例。此外,它仍然遵循继承链,即只返回类本身的参数。该函数get_params作为一个简单的示例添加,但可以通过检查模块添加其他功能。

import inspect 

class Parent:
    @classmethod
    def get_params(my_class):
        return list(inspect.signature(my_class).parameters.keys())

class OtherParent:
    def __init__(self, a, b, c):
        pass

class Child(Parent, OtherParent):
    def __init__(self, x, y, z):
        pass

print(Child.get_params())
>>['x', 'y', 'z']

解决方案 8:

Django 有method_decorator一个装饰器,它可以将任何装饰器变成方法装饰器,你可以看到它是如何实现的django.utils.decorators

https://github.com/django/django/blob/50cf183d219face91822c75fa0a15fe2fe3cb32d/django/utils/decorators.py#L53

https://docs.djangoproject.com/en/3.0/topics/class-based-views/intro/#decorating-the-class

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3983  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2747  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   82  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   90  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   79  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用