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有意义时会调用

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python装饰器是一种特殊的装饰器形式,采用语法糖的形式来实现装饰器返回一个增加了新功能的函数对象,但这个函数对象是一个的实例对象。为了让装饰器变成可调用对象,必须在实现`__call__`方法。通过这种方式,生成的装饰器实例对象可以像函数一样被调用。 装饰器可以用于装饰函数或方法,从而改变被装饰函数或方法的功能和性质。在装饰器,可以定义一些额外的功能和逻辑,并在被装饰的函数或方法执行前后进行处理。 举个例子,我们可以定义一个延时执行的装饰器。在这个装饰器,可以设置一个延时时间,在被装饰的函数执行之前,先进行延时操作。这样可以实现一些需要延时执行的功能。 下面是一个不带参数的装饰器的例子: ```python import time class Decorator: def __init__(self, func): self.func = func def defer_time(self): time.sleep(5) print("延时结束了") def __call__(self, *args, **kwargs): self.defer_time() self.func() @Decorator def f1(): print("延时之后我才开始执行") f1() ``` 在这个例子,`Decorator`实现了一个延时执行的装饰器。通过在函数定义前加上`@Decorator`,就可以应用这个装饰器。在调用被装饰的函数`f1()`时,会先进行5秒钟的延时操作,然后再执行函数体。 使用装饰器时也可以传递参数。下面是一个带参数的装饰器的例子: ```python import time class Decorator: def __init__(self, func): self.func = func def defer_time(self, time_sec): time.sleep(time_sec) print(f"{time_sec}s延时结束了") def __call__(self, time): self.defer_time(time) self.func() @Decorator def f1(): print("延时之后我才开始执行") f1(5) ``` 在这个例子,`Decorator`实现了一个可以传递延时时间的装饰器。在调用被装饰的函数`f1(5)`时,会先进行5秒钟的延时操作,然后再执行函数体。 总结来说,Python装饰器是一种特殊的装饰器形式,通过定义一个实现`__call__`方法实现装饰器可以用于装饰函数或方法,改变被装饰函数或方法的功能和性质。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [python装饰器详解](https://blog.csdn.net/weixin_44992737/article/details/125868592)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值