在上一节中,我们讲到讲完了面向对象的一些基本属性和它的三大特性,这节我们将来学习一些更高级的东西.
反射:广义上的反射指的是程序可以访问,检测和修改它本身状态或行为的一种能力,而python里面的反射就是通过字符串的形式来操作对象的相关属性,涉及到四个反射的方法是:hasattr,getattr,setattr和delattr,我们直接在案例中来看这四个方法的使用:
class People: country = "中国人" def __init__(self,name,language): self.name = name self.language = language def speek_lang(self): print("【%s】 是一名 【%s】 他的母语是 【%s】"%(self.name,self.country,self.language)) p = People("小王","汉语") # hasattr(object,name):判断某一个对象中是否有某属性,比如 print(hasattr(p,"country")) # getattr(object,name,defualt=None):获取对象中的某一个属性,如果没有该属性,在第三个参数不给的情况下就直接报错,如果我们给上第三个参数的,就会按照第三个参数返回 print(getattr(p,"country")) # 输出结果是: 中国人 # print(getattr(p,"age")) # 报错: AttributeError: 'People' object has no attribute 'age' print(getattr(p,"age","找不到这个属性")) # 输出的结果是:找不到这个属性 # setattr(object,name,value): 设置属性 如果属性不存在就会添加,如果属性存在,就修改 setattr(p,"age",18) setattr(p,"name","老王") print(p.__dict__) # 输出结果是:{'name': '老王', 'language': '汉语', 'age': 18} # delattr(object,name): 删除属性 delattr(p,"age") print(p.__dict__) # 输出结果是: {'name': '老王', 'language': '汉语'}
那么反射的作用什么呢?一般情况下,我们都是团队开发,每个人负责的模块可能不一样,但是有的时候我可能用到别人模块下的某一个方法,这个时候我们不确定有没有没有这个方法,这个时候我们就可以通过反射来完成了,比如:
# A 开发的模块userinfo
class UserInfo: def __init__(self,name,age): self.name = name self.age = age def get_name(self): ''' 在这里,我们假设用登录之后,我们就将用户名保存起来,然后将用户名返回 :return: ''' return self.name
# B 开发模块
from userinfo import UserInfo class ShopCard: def __init__(self,user,goods): self.user = user self.goods = goods def enter_shoping_card(self): ''' 我们在进入购物车之前,要先判断用户是否登录,这个时候我们需要去用户信息哪里获取用户信息 但是我们又不确定那个用户信息有没有记录下来,这个时候我们就要用到反射了 :return: ''' if hasattr(self.user,"get_name"): print("欢迎【%s】 购物车有商品数为【%s】个" % (getattr(self.user,"get_name")(), self.goods)) else: print("你还没登录") user = UserInfo("小王",18) sc = ShopCard(user,"10000") sc.enter_shoping_card()
还有一个作用就是,我们可以动态导入模块,比如__import__("模块"),导入的是最顶层的那个,importlib.import_module("模块")导入的最里面的那个,那么有的时候,我们想自己来控制增删改查呢?这时候机会涉及到底层的是三个函数了,分别是:__setattr__,__getattr__,__delattr__,如:
typed={ "name":str, "age":int, "language":str } class People: country = "中国人" def __init__(self,name,age,language): self.name = name self.age = age self.language = language def __setattr__(self, key, value): ''' 这个方法我们在进行实例化和改变值的时候都会调用 :param key: :param value: :return: ''' print("触发了set方法") # 由于我们重写了__setattr__方法,所以我们需要在这里重新将值设置到字典里面去 # 前面我们说,我们希望在实例化的时候对参数进行一定的限制,比如,我们希望输入的年龄是整型,如果不是整型我们就给用于一个提示,让用户重新输入 t = typed[key] if not isinstance(value,t): print("您输入的【%s】不是【%s】" % (key, typed[key])) return self.__dict__[key] = value def __getattr__(self, item): ''' 这个方法是当我们在获取属性的时候如果这个属性不存在就会触发 :param item: :return: ''' print("触发了get方法") def __delattr__(self, item): ''' 删除属性的时候就会调用 :param item: :return: ''' print("触发了del方法") p = People("小王","18","汉语") print(p.__dict__)
注:__getattribute__属性存在或不存在都会执行,当它与__getattr__同时存在的时候,如果__getattribute__的执行没有抛出异常的话,就只执行__getattribute__,如果通过raise AttributeError 抛出异常的话才会执行__getattr__
item序列,适用字典操作,分别为:__getitem__,__setitem__,__delitem__,通过self.__dict__[key] = value来进行设值
如果我们要改变字符串的显示对象,我们可以通过重写__str__,__repr__,__format__来实现,如:
format_dict={ "format_type_one":"{obj.name}-{obj.age}-{obj.language}", "format_type_two":"{obj.name}:{obj.age}:{obj.language}", "format_type_three":"{obj.name}/{obj.age}/{obj.language}" } class People: country = "中国人" def __init__(self,name,age,language): self.name = name self.age = age self.language = language def __str__(self): ''' 我们在print的时候会调用此属性 :return: ''' return "%s,%s,%s"%(self.name,self.age,self.language) def __repr__(self): ''' 如果没有没有定义__str__就会调用这个方法 :return: ''' return "%s,%s,%s"%(self.name,self.age,self.language) def __format__(self, format_spec): ''' 当用format函数的时候就会执行,在这里,我们可以修改显示格式 :param format_spec: 传入的参数 :return: ''' if not format_spec or format_spec not in format_dict: format_spec = "format_type_one" fmt = format_dict[format_spec] return fmt.format(obj = self) p = People("小王",18,"普通话") print(p) print(format(p,"format_type_one"))
slots属性: __slots__是一个类变量,变量值可以是列表,元组,或者可迭代对象,也可以是一个字符串;引子--在使用点来访问属性本质就是在访问类或者对象的__dict__属性字典;那么我们为什么使用__slots__呢?因为字典会占用大量内存,假设有一个属性很少的类,但是却有很多实例,为了节省内存这个时候我们就可以使用__slots__来取代__dict__,当我们定义了__slots__之后,__slots_就会实例一种更加紧凑的内部表示,实例通过一个很小的固定数组来构建,而不是为每个实例定义字典,和列表或数组很像,在__slots__中列出的属性名在内部被映射到这个数组指定的脚标上,使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用__slots__中定义的属性了,需要注意的是,__slots__的很多特性都依赖于普通的基于字典的实现,定义了__slots__的类不再支持一些普通类的特性,比如多继承,同时也没有__dict__这个属性了
class People: __slots__=["name","age"] p = People() p.name = "小王" p.age = 18 print(p.__slots__) print(p.name)
__doc__:文档信息,这个函数是不能被继承的,比如:
class A: '''省略1000000行,自己去想象''' pass class B(A): pass print(A.__doc__) # 输出结果是:省略1000000行,自己去想象 print(B.__doc__) # 输出结果是:None
__module__:表示当前操作的对象在哪个模块
__class__:表示当前操作的对象的类是什么
__del__:析构方法,当对象在内存中被释放时,自动触发执行,一般我们是不需要去定义的,python解释器会自动进行垃圾回收的
__call__:对象实例化的时候就会触发
迭代器协议:
# 实现斐波那契数列 # 1、1、2、3、5、8、13、21、…… class Series: def __init__(self): self._a = 0 self._b = 1 def __iter__(self): return self def __next__(self): self._a,self._b = self._b,self._a + self._b return self._a s = Series() for i in s: if i > 20: break print(i)
描述符: 本身就是一个新式类,在这个新式类中,至少实现__get__,__set__,__delete__中的的一个,也被称为描述符协议,它的作用就是代理另外一个类的属性(必须把描述符定义成这个类的类属性,不能定义到构造函数中),描述符本身调用时不执行的,定义在另外的类中,然后通过这个类的实例去执行相应的方法的时候才会被调用
__get__:调用一个属性的时候触发
__set__:为一个属性赋值的时候触发
__delete__:采用del删除属性的时候触发
描述符分为两种,一种是数据描述符,就是至少实现了__get__和__set__,另一种是非数据描述符,没有实现__set__方法,数据描述符的优先级大于非数据描述符,既然说到优先级,那么我们就看描述符的优先级:
类属性 > 数据描述符 > 实例属性 > 非数据描述符 > 找不到属性时触发
class Descriptor: def __set__(self, instance, value): print("set") def __get__(self, instance, owner): print("get") def __delete__(self, instance): print("delete") class Student: ''' 通过描述符来代理name属性 ''' name = Descriptor() def __init__(self,name,age): self.name = name self.age = age s = Student("小王",18) # print(s.__dict__) # 输出结果是:{'age': 18} ==>通过描述符的代理之后,name就变成了一个类属性,不再是实例属性了,既然是一个类属性,那么我们就可以直接通过类名来调用 # # print(Student.name) # 执行了get方法,但是我们发现输出是None,我们通过赋值看看情况 # # Student.name = "老王" # print(Student.name) # print(s.name) # 两个的输出结果我们发现 实例的name被覆盖了,而且描述符的get方法也没有执行,这就说明了 类属性优先于描述符 s.name = "小华" # set方法调用了 print(s.name) # get 方法被调用了 print(s.__dict__) # 输出结果是:{'age': 18} 从而说明了数据数据大于实例属性 因为name没有在实例字典中
class Descriptor: ''' 非数据描述符 ''' def __get__(self, instance, owner): print("get") class Student: name = Descriptor() def __init__(self,name,age): self.name = name self.age = age def __getattr__(self, item): print("找不到%s"%item) s = Student("小王",18) print(s.name) print(s.__dict__) # 输出结果是:{'name': '小王', 'age': 18} 同时也没有调用描述符中的get方法 表明了实例属性优先级大于非数据描述符
描述符案例一:
# 通过描述符限制类型 class Descriptor: def __init__(self,type_name,excepted_type): ''' :param type_name: 传入的数据 :param excepted_type: 希望的数据类型 ''' self.type_name = type_name self.excepted_type = excepted_type def __set__(self, instance, value): if not isinstance(value,self.excepted_type): raise TypeError("异常是%s"%str(self.excepted_type)) instance.__dict__[self.type_name] = value def __get__(self, instance, owner): if instance is None: return self return instance.__dict__[self.type_name] class Employee: name = Descriptor("name",str) salary = Descriptor("salary",float) def __init__(self,name,salary): ''' 我们期望的是通过描述符限制我们的数据类型 比如name为str 而salary则为 float类型 :param name: :param salary: ''' self.name = name self.salary = salary def __str__(self): return "%s,%s"%(self.name,self.salary) e = Employee("小王",2222) print(e)
描述符案例二:
# 通过上面的案例,我们发现,当我们需要限制很多属性的时候,我们就要定义很多个Descriptor实例,这样一来我们的代码就会看的很臃肿,接下来我们直接通过描述器来对类的一种装饰 class Descriptor: def __init__(self,key,excepted_type): self.key = key self.excepted_type = excepted_type def __set__(self, instance, value): if not isinstance(value,self.excepted_type): raise TypeError("异常是%s"%str(self.excepted_type)) instance.__dict__[self.key] = value def __get__(self, instance, owner): if instance is None: return self return instance.__dict__[self.key] def typed(**kwargs): ''' 定义一个装饰器 :param kwargs: 传进来的数据 我们以字典的形式 :return: ''' def wrapper(cls): ''' 参数是一个类命名 我们在调用setatttr的时候需要三个参数 第一个参数是:实例 第二个参数:key 第三个参数是:value :param cls: :return: ''' for key,excepted_type in kwargs.items(): ''' 在设置值的时候,我们先通过装饰器来做一个判断 ''' setattr(cls,key,Descriptor(key,excepted_type)) return cls return wrapper @typed(name=str,salary=float) class Employee: def __init__(self,name,salary): self.name = name self.salary = salary def __str__(self): return "%s,%s"%(self.name,self.salary) e = Employee("小王",2222.2) print(e) e = Employee("老王",22222) print(e)
描述符案例三:
# 自定制一个property,来实现缓存计算功能 class LazyProperty: ''' 我们定义一个非数据型描述符,因为实例属性优先级大于它,我们在第一次执行的 就将它存到字典里面 等到下次用的时候就直接从字典里面获取 ''' def __init__(self,func): self.func = func def __get__(self, instance, owner): if instance is None: return self # 这里相当于执行了函数func value = self.func(instance) # 将value保存到自己的属性字典中 setattr(instance,self.func.__name__,value) return value class Room: def __init__(self,width,length): self.width = width self.length = length @LazyProperty # 相当于area = LazyProperty(area) def area(self): return self.width * self.length r = Room(10,10) print(r.area) # 先从自己的属性字典中,没有的话再去类中找,然后触发area的__get__方法 这样就不用每执行一次都要去计算一次 print(r.area)
上下文管理协议: with ... as ...它的用处就是把代码块放在with中执行,在with执行结束之后,自动清理工作,无序手动干预;有的时候,我们需要管理一些资源,比如文件,网络连接和锁编程的环境中,可以在__exit__中定制自动释放资源的机制
__enter__:with开始触发时,如果with里面有异常,将直接跳到exit()进行操作,如果这个时候__exit__返回的是True,这个时候with代码块里面的异常部分之后的代码将不会执行,直接跳出with,执行其他代码,如果没有返回True,整个程序这个时候就会报出异常,程序执行结束
__exit__:正常情况下,with里面的代码执行完之后,触发
元类:简单的说就是类的类,是类的模板,是用来控制如何创建类的,元类的实例是类,type是python内建的一个元类,用来控制生成类,通过type来生成类的话,里面有三个参数,第一个参数类名,第二个参数是父类,第三个参数是属性(必须是一个字典),比如:
A = type("A",(object,),{'x':1}) print(A.__dict__)
如果我们要自定义一个元类的话,我们可以:
一:声明为元类
class A(metaclass=type):
二:继承来实现:
class A(type):
浙公网安备 33010602011771号