python给类的所有方法加上装饰器

概述

今天遇到一个需求,需要对类的所有方法进行监听其返回值,本来想的是用装饰器,但是方法太多了一个一个写装饰器会爆炸。
所以这里发现一个有用的东西
参考:https://www.cnblogs.com/nkwy2012/p/6264031.html

__getattribute__

所有类进行方法或者属性获取的时候,实际上都是如下:

class A():
	def name():
		return "a"
x=A()
x.name()#其实是x.\_\_getattribute\_\_(‘name’)()

这里实际上就有一个很好的方法对所有的属性都进行监听了。

序号目的所编写代码Python 实际调用
获取一个计算属性(无条件的)x.my_propertyx.__getattribute__(‘my_property’)
获取一个计算属性(后备)x.my_propertyx.__getattr__(‘my_property’)
设置某属性x.my_property = valuex.__setattr__(‘my_property’,value)
删除某属性del x.my_propertyx.__delattr__(‘my_property’)
列出所有属性和方法dir(x)x.__dir__()

这里需要注意的是,如果该函数没有找到,就会进入__get__attr__里面去找,如果找到了或者没找到都会直接返回。

  1. 如果某个类定义了__getattribute__() 方法,在 每次引用属性或方法名称时 Python 都调用它(特殊方法名称除外,因为那样将会导致讨厌的无限循环)。
  2. 如果某个类定义了 __getattr__() 方法,Python 将只在正常的位置查询属性时才会调用它。如果实例 x 定义了属性color, x.color 将 不会 调用x.__getattr__(‘color’);而只会返回x.color 已定义好的值。
  3. 无论何时给属性赋值,都会调用 __setattr__() 方法。
  4. 无论何时删除一个属性,都将调用 __delattr__() 方法。
  5. 如果定义了 __getattr__() 或 __getattribute__() 方法, __dir__() 方法将非常有用。通常,调用 dir(x) 将只显示正常的属性和方法。如果__getattr()__方法动态处理color 属性, dir(x) 将不会将 color 列为可用属性。可通过覆盖 __dir__() 方法允许将 color 列为可用属性,对于想使用你的类但却不想深入其内部的人来说,该方法非常有益。

一般情况下使用__getattribute__()即可
就目前自己测试来看,如果__getattribute__()找不到,那么getattr基本也找不到。
基本的调用流程:先调用__getattribute__(),如果找不到会调用父类的相关方法,如果都找不到,父类会调用__getattr__进行找,找到了就找到了返回了值,如果没找到那就直接报错。示例如下:

import time
from typing import Iterable
class A():
    def name(self):
        print("a")
class B(A):
    def __getattr__(self, item):
        # print(self.__dir__())
        print("getattr:", item)
        return "None111111111111111111"
    # x=10
    @property
    def name1(self):
        return 213
    def name(self):
        return "sfag"
    def __getattribute__(self, item):
        print("use getattribute")
        x = super().__getattribute__(item)#重点:如果父类调用找不到会直接在父类那里面调用__getattr__,如果还没找到就会直接返回了,这一行下面的代码不会执行。这里有点扯,我觉得是python的bug。。。
        print(isinstance(x,classmethod))
        print(type(x))
        print(str(type(x))=="<class 'method'>")#判断是否为类的方法属性或函数,具体分析请看下面
        print(item + ":" + str(x))
        return x

    def __dir__(self) -> Iterable[str]:
        print("dir")#本来以为上面两个方法会调用,但是实际上没有调用
        return super().__dir__()

b = B()
print(0)
print(hasattr(b,'nnnn'))
print("1")
print(b.x)
print(2)
print(b.name())

有部分很重要的代码:

def __getattribute__(self, item):
        print("use getattribute")
        x = super().__getattribute__(item)#重点:如果父类调用找不到会直接在父类那里面调用__getattr__,如果还没找到就会直接返回了,这一行下面的代码不会执行。这里有点扯,我觉得是python的bug。。。
        print(isinstance(x,classmethod))
        print(type(x))
        print(str(type(x))=="<class 'method'>")#判断是否为类的方法属性或函数,如果为类或实例的方法则为method,如果为类或实例的变量则为对应的变量属性,如果为静态方法则为function。抽象类那一块没测试过,需要使用的自己使用
        print(item + ":" + str(x))
        return x

所以如果要对类的所有方法加上装饰器,只需要重写该类的__getattribute__方法即可。这里我建议重写的时候还是对输入参数item进行一下判断,以免误伤把一些不想做装饰的都进行了装饰。
这里我给一个简单的例子,

class ChiseAipFace(AipFace):#AipFace为百度的人脸识别http封装的类。
    def __getattribute__(self, item):
    """建议对item进行一下判断,不要全局增加"""
        ret = super().__getattribute__(item)
        if type(
                ret) == "<class 'method'>":  # 类里面的成员分为三种,method(类方法和实例方法),function(实例方法),int,str...(变量成员),具体需要的时候还是通过type进行判断或者直接通过item来判断
            def res(*args, **kwargs):
                retu = None
                t = 0
                for i in range(10):
                    if i > 0:
                        t = time.time()
                    retu = ret(*args, **kwargs)
                    if retu['error_code'] == 18:#主要解决瞬时并发超限的问题,通过随机值将超限的并发随机在之后的一段时间里面进行接口访问。
                        time.sleep(random.random() * i * 5)
                    else:
                        if t:
                            logger.warning("接口访问延时18:" + str(time.time() - t) + ",name:" + item)
                        return retu
                logger.error("接口失败18:" + item)
                return retu
            return res
        else:
            return ret

一些其他有意义的东西

本部分来自链接:https://www.cnblogs.com/nkwy2012/p/6264031.html
真正神奇的东西

如果知道自己在干什么,你几乎可以完全控制类是如何比较的、属性如何定义,以及类的子类是何种类型。

序号目的所编写代码Python 实际调用
类构造器x = MyClass()x.__new__()
*类析构器del xx.__del__()
只定义特定集合的某些属性x.__slots__()
自定义散列值hash(x)x.__hash__()
获取某个属性的值x.colortype(x).__dict__[‘color’].__get__(x, type(x))
设置某个属性的值x.color = ‘PapayaWhip’type(x).__dict__[‘color’].__set__(x, ‘PapayaWhip’)
删除某个属性del x.colortype(x).__dict__[‘color’].__del__(x)
控制某个对象是否是该对象的实例 your class isinstance(x, MyClass)MyClass.__instancecheck__(x)
控制某个类是否是该类的子类issubclass(C, MyClass)MyClass.__subclasscheck__©
控制某个类是否是该抽象基类的子类issubclass(C, MyABC)MyABC.__subclasshook__©

python中以双下划线的是一些系统定义得名称,让python以更优雅得语法实行一些操作,本质上还是一些函数和变量,与其他函数和变量无二。
比如x.__add__(y) 等价于 x+y
有一些很常见,有一些可能比较偏,在这里罗列一下,做个笔记,备忘。

  • x.__contains__(y) 等价于 y in x, 在list,str, dict,set等容器中有这个函数
    __base__, __bases__, __mro__, 关于类继承和函数查找路径的。
    class.__subclasses__(), 返回子类列表 x.__call__(…) == x(…) x.__cmp__(y) == cmp(x,y)
    2. x.__getattribute__(‘name’) == x.name == getattr(x, ‘name’),
    比__getattr__更早调用 x.__hash__() == hash(x) x.__sizeof__(), x在内存中的字节数,
    x为class得话, 就应该是x.__basicsize__ x.__delattr__(‘name’) == del x.name

  • __dictoffset__ attribute tells you the offset to where you find the pointer to the __dict__ object in any instance object that has one.
    It is in bytes.

  • __flags__, 返回一串数字,用来判断该类型能否被序列化(if it’s a heap type), __flags__ & 512 S.__format__, 有些类有用 x.__getitem__(y) == x[y], 相应还有__setitem__,
    某些不可修改类型如set,str没有__setitem__ x.__getslice__(i, j) == x[i:j],
    有个疑问,x=‘123456789’, x[::2],是咋实现得

  • __subclasscheck__(), check if a class is subclass

  • __instancecheck__(), check if an object is an instance

  • __itemsize__, These fields allow calculating the size in bytes of instances of the type. 0是可变长度, 非0则是固定长度 x.__mod__(y) == x%y,

  • x.__rmod__(y) == y%x x.__module__ , x所属模块 x.__mul__(y) == x*y,

  • x.__rmul__(y) == y*x

  • __reduce__, __reduce_ex__ , for pickle

  • __slots__ 使用之后类变成静态一样,没有了__dict__, 实例也不可新添加属性

  • __getattr__ 在一般的查找属性查找不到之后会调用此函数

  • __setattr__ 取代一般的赋值操作,如果有此函数会调用此函数, 如想调用正常赋值途径用 object.__setattr__(self, name, value)

  • __delattr__ 同__setattr__, 在del obj.name有意义时会调用

<think>好的,我现在需要帮助用户解决如何在Python中使用装饰器修饰方法的问题。首先,我得回忆一下装饰器的基本概念。装饰器本质上是一个函数,它可以接受一个函数或方法作为参数,并返回一个新的函数或方法,以增强原有功能。用户的问题是关于方法装饰器应用,所以我需要重点放在方法上。根据之前的引用内容,尤其是引用[1]提到@classmethod,引用[5]中的例子是在内部定义装饰器来修改属性。可能用户需要了解如何在内部或外部创建装饰器来修饰方法。 首先,我应该解释基本的装饰器用法,比如在方法上使用装饰器的语法。例如,在方法定义前加上@decorator_name。然后,可能需要区分装饰器是定义在内部还是外部,因为引用[5]中的例子是在内部定义装饰器,而引用[2]和[3]则是外部装饰器修饰。 接下来,我需要给出具体的例子。比如,一个简单的装饰器,用于记录方法的执行时间或者打印日志。例如,定义一个外部的装饰器函数,接受方法作为参数,并返回一个包装后的函数。在包装函数中执行额外的操作,比如打印信息,然后调用原方法。另外,内部定义的装饰器可能需要注意参数传递。例如,在内部定义的装饰器需要接收self或cls作为第一个参数,这样才能访问的实例或属性。引用[5]中的例子展示了如何在内部使用装饰器,并修改实例属性。这时候装饰器函数需要正确处理self参数。 还需要提到内置的装饰器,比如@classmethod、@staticmethod和@property,这些是Python自带的,用来改变方法的行为。比如,@classmethod会将方法转换为方法,第一个参数是cls而不是self。这可能和用户的问题相关,但用户更关注的是自定义装饰器,而不是内置的。另外,需要注意装饰器方法上的应用时,参数的传递。装饰器函数需要正确处理实例方法的self参数,或者在方法中的cls参数。例如,在装饰器内部定义的包装函数,需要接收self作为第一个参数,然后在调用原方法时传递正确的参数。可能还需要解释装饰器的嵌套和顺序,比如多个装饰器时的执行顺序,是从下往上还是从上往下。不过这可能稍微复杂,用户可能更需要基础的应用方法。 还要考虑错误处理,比如如果装饰器没有正确保留原方法的元信息,可能会导致一些问题,比如方法名变成装饰器的函数名。这时候可以使用functools.wraps来保留元数据,但可能在方法中需要注意这一点。总结一下,我的回答结构应该是: 1.装饰器基本概念和语法。2.示例一:外部装饰器修饰方法。3.示例二:内部定义装饰器修饰方法。 4.内置装饰器的简要提及。 5.注意事项,比如参数传递、装饰器顺序等。需要确保示例代码正确,并且符合用户的需求。例如,外部装饰器的例子可以是一个计时装饰器,内部装饰器的例子可以像引用[5]那样修改属性。 同时,根据用户提供的引用,特别是引用[5],用户可能对在内部使用装饰器来修改实例属性感兴趣,所以需要包含这样的例子。此外,引用[3]中的例子展示了如何用装饰器修饰本身,但用户的问题是关于方法的,所以可能需要调整示例。最后,生成相关问题,比如如何传递参数给装饰器,或者如何处理多个装饰器的情况,这样可以引导用户进一步学习。</think>### 如何在Python中对方法应用装饰器? 在Python中,使用装饰器修饰方法的核心步骤是**定义装饰器函数**并**正确传递实例或对象**。以下是具体实现方法: --- #### 一、基础用法:外部装饰器修饰方法 定义一个外部装饰器函数,接收方法作为参数,并在内部包装方法逻辑: ```python # 定义装饰器 def log_method(func): def wrapper(self, *args, **kwargs): print(f"执行方法: {func.__name__}") return func(self, *args, **kwargs) return wrapper # 应用装饰器方法 class MyClass: @log_method def my_method(self): print("方法被调用") obj = MyClass() obj.my_method() # 输出: 执行方法: my_method → 方法被调用 ``` - **关键点**:装饰器的`wrapper`函数需包含`self`参数以接收实例[^5]。 --- #### 二、内部定义装饰器修饰方法内部定义装饰器时,需通过`self`或`cls`访问属性或实例属性: ```python class ShoppingCart: def __init__(self): self.items = [] # 内定义装饰器 def check_empty(func): def wrapper(self, *args, **kwargs): if not self.items: print("购物车为空") return return func(self, *args, **kwargs) return wrapper @check_empty def checkout(self): print("结算购物车") cart = ShoppingCart() cart.checkout() # 输出: 购物车为空 ``` - **关键点**:装饰器需定义为嵌套函数,并通过`self`操作实例状态[^5]。 --- #### 三、内置装饰器的应用 Python内置装饰器如`@classmethod`和`@staticmethod`可直接修饰方法: ```python class MathUtils: @classmethod def add(cls, a, b): return a + b @staticmethod def multiply(a, b): return a * b print(MathUtils.add(2, 3)) # 输出: 5 print(MathUtils.multiply(2, 3)) # 输出: 6 ``` - **区别**:`@classmethod`第一个参数是对象`cls`,`@staticmethod`无需隐式参数[^1]。 --- #### 四、高级用法:带参数的装饰器 若需向装饰器传递额外参数,需嵌套三层函数: ```python def repeat(n): def decorator(func): def wrapper(self, *args, **kwargs): for _ in range(n): func(self, *args, **kwargs) return wrapper return decorator class Printer: @repeat(3) def print_msg(self): print("Hello") printer = Printer() printer.print_msg() # 输出三次 "Hello" ``` --- #### 注意事项 1. **保留元信息**:使用`@functools.wraps(func)`避免方法名被装饰器覆盖。 2. **装饰器顺序**:多个装饰器按从下到上的顺序执行,例如: ```python @decorator2 @decorator1 def my_method(): ... ``` 等效于 `decorator2(decorator1(my_method))`。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值