python面向对象高级:反射、魔法方法、元类

自省/反射
什么是反射?
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。
并且在python里,反射可以使得程序运行时对象拥有增删改查它本身属性或行为的一种能力
如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__
反射的使用场景?
  即插即用,即可以事先定义好接口,接口只有在被完成后才会真正执行
比如:如果和别人共同合作开发项目,但是需要用到对方的类的方法,对方还没完成

f1=FtpClient(‘192.168.1.1‘)
if hasattr(f1,‘get‘):
    func_get=getattr(f1,‘get‘)
    func_get()
else:
    print(‘---->不存在此方法‘)
    print(‘处理其他的逻辑‘)

四个可以实现自省的函数
python通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

getattr(x, ‘y‘)     #x.y
setattr(x, ‘y‘, v) #x.y = v可以设置属性setattr(b1,‘show_name‘,lambda self:self.name+‘sb‘)
delattr(x, ‘y‘)     #del x.y
hasattr(b1,‘name‘) 

魔法方法

__setattr__,__delattr__,__getattr__

必知
1)覆写以上函数非常容易出现无限递归,因为无论是操纵全局自省函数,还是对象.属性都是去调用对应的魔法方法。所以只能操纵魔法字典这个属性

def __getattr__(self, item):
    return self.__dict__[item]

2)覆写以上函数的格式和对应的全局自省函数参数相同,因为全局自省函数就是调用对应的它们

__getattribute__

__getattr__对比__getattribute__有什么区别?
1)当使用 对象.属性 找不到的时候,会调用getattr,返回一个值或AttributeError异常,但是若属性存在,则不调用。
  但是__getattribute__无论属性存在与否都会调用
2)当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError

描述符协议

__get__,__set__,__delete__   # 描述符

什么是描述符?
描述符的功能就是描述其他类的类属性
描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发

描述符的原理是将描述符定义为指定类的类属性,产生指定操作时,便去属性字典里面找,如果是描述符对象,便执行

class Int:
def __get__(self, instance, owner): #owner是instance的class
    print(‘Int调用‘)
def __set__(self, instance, value):
    print(‘Int设置...‘)
def __delete__(self, instance):
    print(‘Int删除...‘)

class People:
    age=Int()
    def __init__(self,name,age):
        self.age=age #执行Int.__get__

描述符的分类
描述符分两种,数据描述符(至少实现类__get__()和__set__()), 非数据描述符(没有实现__set__()),
考虑要定义哪一种,主要是考虑需不需要给实例定义自己相同的属性(该属性名与类描述符名相同)
并且数据描述符的优先级只有类属性可以覆盖。

4.__set__设置类.描述符时,新设的类属性值会覆盖描述符,但是通过实例.描述符却不会覆盖。在类的__dict__可以观察到。
5.描述符只能通过 类.描述符名=XX 改变,实例是无法改变,并且实例可以设置描述符同名的属性(所以数据描述符,和非数据描述符就是有区别,区别就是有__set__方法,就应该去执行,没有的话就直接绑定了)

描述符的用处
  干预被描述的属性的操作
  当操纵指定的类属性时,会跳到相应的描述符函数并执行返回。我们可以在描述符函数通过操作指定对象的__dict__模仿正常的属性操作。所以说这中间多了个干预工程,常用于制作类构造函数的类型限制。

给类加上类属性的三种方法:(注意,函数也属于类属性)
1)直接在类中声明,例如a=Des()
2)定义一个函数’装饰器‘,装饰于类中。在函数里面为类添加属性
3 ) 定义一个类’装饰器‘,然后装饰于另外一个类的方法里,
然后被装饰器的函数名就指向这个类’装饰器‘产生的对象(obj=cls(func)),若此时类装饰器又实现了__get__方法,
换句话说,函数是类属性,类属性指向描述符对象。专门应用于函数。
扩展:类装饰器
  类装饰器,原理和函数装饰器一样,前者将函数当作对象传入,后者将类当作对象传入,通过类比可知。
当构造函数的参数过多时,可以额外写一个函数作装饰器(参数为 属性名=期望类型..的可变参数**kwargs,函数里面,为类加入描述符属性(类.属性名=描述符)

例如:自制proprety就是类装饰器配合描述符的好例子

class Pro():
    def __init__(self,ab):
        self.ab=ab

    def __get__(self,instance,owner):
        return self.ab(instance)

class Apple():
    def __init__(self,a,b):
        self.a=a
        self.b=b

    @Pro
    def ab(self):
        return self.a*self.b

    def __str__(self):
        return ("是我a=%s,b=%s"%(self.a,self.b))

if __name__==‘__main__‘:
    aa=Apple(2,3)
    print(aa.ab)
__setitem__,__getitem,__delitem__  #等于c++的运算符[]重载
__str__,__repr__,__format__

1)改变对象的字符串显示__str__,__repr__,自定制格式化字符串__format__
2)在类中__str__和__repr__没什么区别。如果__str__没有被定义,那么就会使用__repr__来代替输出
注意:这俩方法的返回值必须是字符串,否则抛出异常

__slots__ #内存优化工具,顺便可以限制属性

用法:
可以选择性定义的一个类属性,类型是列表,列表元素是定义的实例属性名(字符串)
作用:
1)__slots__机制借由数组存储属性来节约内存。因为类的字典是共享的,而每个实例的是独立的,字典占用内存很大,
2)在类中定义__slots__属性列表后,实例通过一个很小的固定大小的数组来构建,在__slots__中列出的属 性名在内部被映射到这个数组的指定小标上。而不是为每个实例定义一个字典,节约内存。
3)使用__slots__的代价就是不能再给实例添加新的属性,只能使用在__slots__中定义的那些属性名

__next__和__iter__  #实现迭代器协议
__doc__ #类中最开头处的字符串

class Foo:
‘我是描述信息‘
  pass
print(Foo.__doc__)
__module__   #表示当前操作的对象在那个模块
__class__     #表示当前操作的对象的类是什么
__del__  #析构方法,当对象在内存中被释放时,自动触发执行。

1)创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中
当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源
2)f=open(‘a.txt‘) #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件
del f #只回收用户空间的f,操作系统的文件还处于打开状态
#所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源。

__call__    #变为函数对象
__enter__和__exit__    #上下文管理器 

注意的是:__exit__的函数参数有点多。还有一般这上下文管理器中的异常会有点多。
上下文中间的代码发生异常时,若__exit__()返回值为True,那么异常就会被吞掉,就好像啥都没发生一样,
例如:

class Open:
    def __init__(self,name):
        self.name=name

    def __enter__(self):
        print(‘出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量‘)

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(‘with中代码块执行完毕时执行我啊‘)
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        #return True

with Open(‘a.txt‘) as f:
    print(‘=====>执行代码块‘)
    raise AttributeError(‘***着火啦,救火啊***‘)
print(‘0‘*100) #------------------------------->不会执行                    

元类

type()函数产生类
Foo=type(class_name,class_bases,class_dic)
第 1 个参数是字符串 ‘Foo’,表示类名
第 2 个参数是元组 (object, ),表示所有的父类
第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

使用自己定义的元类(class A(metaclass=MyType))

当定义自己的元类时,首先要继承type(注意一下,元类中self意味着实例化的cls)
1)如果想干涉‘元类-->类’,就改写__new__。(def__new__(cls,class_name,class_bases,class_dict))
因为类创建对象的流程是__new__创建对象后将对象地址传给__init__,__init__加工上自己所需的属性,
换句话说,前者负责创建返回,后者负责加工。
2)如果想干涉’对象的创建‘,就改写元类的__call__,其实元类的__call__才是大佬,
由它决定要不要调用类的__new__函数和__init__函数。默认会调

原文地址:https://www.cnblogs.com/ziyide/p/9098678.html

时间: 2024-10-24 16:23:43

python面向对象高级:反射、魔法方法、元类的相关文章

反射 动态导入 元类

isinstance(obj cls) 检查obj是否是cls的对象 issubclass(sub, super) 检测sub是否是super的派生类/子类 class Foo(object): pass obj = Foo() print(isinstance(obj, Foo)) # True class Fo(Foo): pass print(issubclass(Fo, Foo)) #True 反射 reflect python中 反射是指 通过字符串的形式来操作对象的相关属性 geta

python中使用反射的方法的代码

开发之余,把开发过程中常用的一些内容段做个珍藏,下面的内容是关于python中使用反射的方法的内容,应该是对码农们有一些用途. import sys, types,new def _get_mod(modulePath): try: aMod = sys.modules[modulePath] if not isinstance(aMod, types.ModuleType): raise KeyError except KeyError: # The last [''] is very imp

Python的__init__, __new__魔法方法以及在__metaclass__元类中的使用

Python中类的实例化是由Python解释器先后调用__new__,__init__这两个魔法方法来完成的,前者用来完成实例化后的对象的"骨架"(比如,解释器会为对象分配地址,并返回一个指向该对象的引用值,该引用值会被紧接着传递给__init__函数),后者用" self.属性名 = 属性值 "这样的方式对实例化的对象进行"填充". 1.__new__ 在一个类(假设为类A)实例化出一个对象的过程中,__new__()方法先于__init__(

Python 面向对象高级编程——使用枚举和元类

1.1   使用枚举 基于Enum类实现的枚举 >>> fromenum import Enum >>> Month = Enum('Month', ('Jan','Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')) >>> for name, member inMonth.__members__.items(): ...    print(name,

Python面向对象之反射,双下方法

一. 反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究.它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩. python面向对象中的反射:通过字符串的形式操作对象相关的属性.python中的一切事物都是对象(都可以使用反射) 四个可以实现自省的函数 下列方法适用于类和对象(一切皆对象,类本身也是一个对象) class Foo:    f = '

第三十四篇 Python面向对象之 反射(自省)

什么是反射? 反射的概念是由Smith在1982年提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究.它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成就. 四个可以实现自省的函数,是Python的内置函数 下列方法适用于类和对象 先看这四个方法对实例(b1)的使用 # 演示代码 class BlackMedium: feature = 'Ugly' def __init__(self, nam

Python之路【第十二篇】:Python面向对象高级

一.反射 1 什么是反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究.它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩. 2 python面向对象中的反射:通过字符串的形式操作对象相关的属性.python中的一切事物都是对象(都可以使用反射) 四个可以实现自省的函数 下列方法适用于类和对象(一切皆对象,类本身也是一个对象) 导入其他模块,利用反

python面向对象高级编程

python中属性和方法的动态绑定  class Student(object):     pass   # 实例化一个对象     s = Student() # 给这个对象绑定一个属性name s.name = 'John'   print(s.name) John   # 定义一个方法 def set_age(self, age):     self.age = age   # 导入模块     from types import MethodType   #给s这个对象绑定一个set_a

python\面向对象高级

一.__slots__ 1.__slots__的概念:是一个变量,变量值可以是列表,元组,或者可迭代对象,也可以是一个字符串. 2.使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例是独立的) 3.为什么要用:节省内存,不会产生新的名称空间. 定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示.实例通过一个很小的固定大小的数组来构建,而不是每个实例定义一个字典:在__slots__中列出的属性名在内部被映射到这个数组的指