python3基础笔记(九)类内置方法
自省(反射)
四个可以实现自省(反射)的函数
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
hasattr(obj, '属性') # obj.属性是否存在 getattr(obj, '属性') # 获取obj.属性。 不存在则报错 getattr(obj, '属性', '默认值') # 获取obj.属性。 不存在不会报错返回默认值 setattr(obj, '属性', '属性的值') # obj.属性 = 属性的值 delattr(obj, '属性') # del obj.属性
__hasattr__(self,name)
判断object中有没有一个name字符串对应的方法或属性
__getattr__(self, name)
实例instance
通过instance.name
访问属性name
,只有当属性name
没有在实例的__dict__
或它构造类的__dict__
或基类的__dict__
中没有找到,才会调用__getattr__
。
当属性name
可以通过正常机制追溯到时,__getattr__
是不会被调用的。如果在__getattr__(self, attr)
存在通过self.attr
访问属性,会出现无限递归错误。
class ClassA(object): def __init__(self, classname): self.classname = classname def __getattr__(self, attr): return('invoke __getattr__', attr) insA = ClassA('ClassA') print(insA.__dict__) # 实例insA已经有classname属性了 # {'classname': 'ClassA'} print(insA.classname) # 不会调用__getattr__ # ClassA print(insA.grade) # grade属性没有找到,调用__getattr__ # ('invoke __getattr__', 'grade')
__setattr__(self, name,value)
如果类自定义了__setattr__
方法,当通过实例获取属性尝试赋值时,就会调用__setattr__
。常规的对实例属性赋值,被赋值的属性和值会存入实例属性字典__dict__
中。
class ClassA(object): def __init__(self, classname): self.classname = classname insA = ClassA('ClassA') print(insA.__dict__) # {'classname': 'ClassA'} insA.tag = 'insA' print(insA.__dict__) # {'tag': 'insA', 'classname': 'ClassA'}
如下类自定义了__setattr__
,对实例属性的赋值就会调用它。类定义中的self.attr
也同样,所以在__setattr__
下还有self.attr
的赋值操作就会出现无线递归的调用__setattr__
的情况。自己实现__setattr__
有很大风险,一般情况都还是继承object
类的__setattr__
方法。
class ClassA(object): def __init__(self, classname): self.classname = classname def __setattr__(self, name, value): # self.name = value # 如果还这样调用会出现无限递归的情况 print('invoke __setattr__') insA = ClassA('ClassA') # __init__中的self.classname调用__setattr__。 # invoke __setattr__ print(insA.__dict__) # {} insA.tag = 'insA' # invoke __setattr__ print(insA.__dict__) # {}
__delattr__(self, name)
与 del 实例名.name 等价
def delattr(x, y): # real signature unknown; restored from __doc__ """ Deletes the named attribute from the given object. delattr(x, 'y') is equivalent to ``del x.y'' """ pass
set,get,del三者的用法演示:
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): print('----> from getattr:你找的属性不存在') def __setattr__(self, key, value): print('----> from setattr') # self.key=value #这就无限递归了,你好好想想 # self.__dict__[key]=value #应该使用它 def __delattr__(self, item): print('----> from delattr') # del self.item #无限递归了 self.__dict__.pop(item) #__setattr__添加/修改属性会触发它的执行 f1=Foo(10) print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值 f1.z=3 print(f1.__dict__) #__delattr__删除属性的时候会触发 f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作 del f1.a print(f1.__dict__) #__getattr__只有在使用点调用属性且属性不存在的时候才会触发 f1.xxxxxx
__getattribute__(self, name)
实例instance
通过instance.name
访问属性name
,__getattribute__
方法一直会被调用,无论属性name
是否追溯到。如果类还定义了__getattr__
方法,除非通过__getattribute__
显式的调用它,或者__getattribute__
方法出现AttributeError
错误,否则__getattr__
方法不会被调用了。如果在__getattribute__(self, attr)
方法下存在通过self.attr
访问属性,会出现无限递归错误。
如下所示,ClassA
中定义了__getattribute__
方法,实例insA
获取属性时,都会调用__getattribute__
返回结果,即使是访问__dict__
属性。
class ClassA(object): def __init__(self, classname): self.classname = classname def __getattr__(self, attr): return('invoke __getattr__', attr) def __getattribute__(self, attr): return('invoke __getattribute__', attr) insA = ClassA('ClassA') print(insA.__dict__) # ('invoke __getattribute__', '__dict__') print(insA.classname) # ('invoke __getattribute__', 'classname') print(insA.grade) # ('invoke __getattribute__', 'grade')
item系列:set、get、del
class Test: def __setitem__(self, key, value): print('setitem') self.__dict__[key] = value def __getitem__(self, item): print('getitem') return self.__dict__[item] def __delitem__(self, key): print('del') self.__dict__.pop(key) f = Test() f.a = 123 #使用 . 的方式不会调用 __setitem__() 方法 f['a'] = 123 #使用 [] 的方式才会调用 __setitem__() 方法 print(f.a) # 使用 . 的方式不会调用 __getitem__() 方法 print(f['a']) # 使用 [] 的方式才会调用 __getitem__() 方法 # del f.a # 使用 . 的方式不会调用 __delitem__() 方法 del f['a'] # 使用 [] 的方式才会调用 __delitem__() 方法 # 使用 . 的方式 与 attr 系列方法有关。 使用 [] 的方式与 item 系列方法有关
__str__(self)
print方法会调用str()方法。str方法会调用此__str__()方法 返回的必须是字符串,否则会异常、
不修改__str__()方法:
class Test: pass f = Test() print(f)
输出:
<__main__.Test object at 0x00000000011238D0>
重写__str__()方法:
class Test: def __str__(self): return "这是Test类" f = Test() print(f)
输出:
这是Test类
__repr__(self)
与 __str__()类似 但是 与其不同的是__repr__()在解释器中触发,__str__()在print()中触发。 返回的必须是字符串,否则会异常、
print()函数只有当__str__()未定义。才会去调用__repr__()
__format__(self)
必须返回一个字符串。
class Date: def __init__(self, year, mon, day): self.y = year self.m = mon self.d = day def __format__(self, format_spec): if not format_spec or format_spec not in format_dict: # 如果没有指定format_spec 或者不存在的格式就默认给他ymd格式 format_spec='ymd' fmt = format_dict[format_spec] return fmt.format(self) d = Date("2018","4","9") print(format(d)) print(format(d,'y-m-d')) print(format(d,'m-d-y'))
__slots__(self)
1 1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 2 2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的) 3 3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 4 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 5 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 6 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。 7 4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 8 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 9 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。
用于限制class的属性 省内存。
使Test类只能设置__slots__()提供的两个属性。name和age
使用__slots__
要注意,__slots__
定义的属性仅对当前类起作用,对继承的子类是不起作用的,除非在子类中也定义__slots__
,这样,子类允许定义的属性就是自身的__slots__
加上父类的__slots__
。
class Test: __slots__ = ['name','age'] # 类似于定制属性字典{'name':None, 'age':None} t = Test() t.name = 'zjs' print(t.name) #t.xxxxxxxxxxxxx = 'test' # 会调用setattr然后通过t.__dict__['xxxxxxxxx'] = 'test' 由于__dict__不存在所以不能设置 # t.__dict__ # 没有这个属性。
__dir__(self)
dir()作用在一个实例对象上时,__dir__
会被调用。返回值必须是序列。dir()将返回的序列转换成列表并排序。
__doc__(self)
该属性不能被继承。
class Test: '我是描述信息' pass print(Test.__doc__)
__module__()
表示当前操作的对象在那个模块
__class__()
表示当前操作的对象的类是什么
from lib.aa import C obj = C() print obj.__module__ # 输出 lib.aa,即:输出模块 print obj.__class__ # 输出 lib.aa.C,即:输出类
__del__(self)
一般不需要定义。析构方法,当对象在内存中被释放时,自动触发执行。
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__
f=open('a.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件 del f #只回收用户空间的f,操作系统的文件还处于打开状态 #所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是 f=open('a.txt') 读写... f.close() 很多情况下大家都容易忽略f.close,这就用到了with上下文管理
__call__(self[, args...])
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
Python中有一个有趣的语法,只要定义类型的时候,实现__call__
函数,这个类型就成为可调用的。换句话说,我们可以把这个类的对象当作函数来使用,相当于重载了括号运算符。
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name) s = Student('Michael') s() # My name is Michael.
实现迭代器协议。
__next__(self)与__iter__(self)
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' class Foo: def __init__(self,x): self.x=x def __iter__(self): return self def __next__(self): n=self.x self.x+=1 return self.x f=Foo(3) for i in f: print(i) 简单示范
class Foo: def __init__(self,start,stop): self.num=start self.stop=stop def __iter__(self): return self def __next__(self): if self.num >= self.stop: raise StopIteration n=self.num self.num+=1 return n f=Foo(1,5) from collections import Iterable,Iterator print(isinstance(f,Iterator)) for i in Foo(1,5): print(i)
class Range: def __init__(self,n,stop,step): self.n=n self.stop=stop self.step=step def __next__(self): if self.n >= self.stop: raise StopIteration x=self.n self.n+=self.step return x def __iter__(self): return self for i in Range(1,7,3): # print(i) 练习:简单模拟range,加上步长
class Fib: 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 f1=Fib() print(f1.__next__()) print(next(f1)) print(next(f1)) for i in f1: if i > 100: break print('%s ' %i,end='') 斐波那契数列
上下文管理__enter__() 与 __exit__()
使用 with as 会执行enter与exit
__exit__() 的执行代表了整个 with 语句的执行完毕
用途或者说好处:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
class Test: def __init__(self, name): self.name = name def __enter__(self): # 使用 with 关键字时会执行。返回一个对象赋值给 as 后的对象。 print('执行了enter') return self def __exit__(self, exc_type, exc_val, exc_tb): # 所有代码块内的过程运行完毕或者发生异常时候执行 print('执行了exit') # 当没有异常信息时候 exc_type, exc_val, exc_tb 都为 None # 有异常信息时候会抛出异常。 print(exc_type) # <class 'NameError'> 异常类 print(exc_val) # name 'asdfasdfasdfagdasgerghae' is not defined 异常原因 print(exc_tb) # <traceback object at 0x000000000111C848> 异常的追踪信息 # 如果在__exit__()中返回一个 True ,with,as 代码块中的异常将结束代码块,继续执行代码块外的程序 并不会抛出异常。 return True print('='*50) with Test('a.txt') as f: # ----> f = Test.__enter__() 使用with关键字会执行__enter__()函数,然后由__enter__()返回一个对象赋值给f print(f) print(f.name) print(asdfasdfasdfagdasgerghae) print('当__exit__()return True时候会跳过异常后的语句直接执行代码块外的语句') print('='*50)
类的描述符:__set__() 、__get__() 、__delete__()
描述符是一种具有“捆绑行为”的对象属性。访问(获取、设置和删除)它的属性时,实际是调用特殊的方法(_get_(),_set_(),_delete_())。也就是说,如果一个对象定义了这三种方法的任何一种,它就是一个描述符。
数据描述符:定义了_set_ 和_get_方法的对象
非数据描述符:只定义了_get_方法的对象。
优先级:数据描述符 > 实体属性(存储在实体的_dict_中)> 非数据描述符。
首先,看这个属性是不是一个数据描述符,如果是,就直接执行描述符的_get_,并返回值。
其次,如果这个属性不是数据描述符,那就按常规去从_dict_里面取。
最后,如果_dict_里面还没有,但这是一个非数据描述符,则执行非数据描述符的_get_方法,并返回。
# 描述符: class Type: def __init__(self, key, wanna_type): self.key = key self.wanna_type = wanna_type # key 所期望的类型。 def __get__(self, instance, owner): # instance 就是传入的 People 实例。 return instance.__dict__[self.key] def __set__(self, instance, value): if not isinstance(value, self.wanna_type): # 判断 instance 传入的value 是不是传入的 wanna_type 类型。 raise TypeError('%s 传入的类型不是%s' % (self.key, self.wanna_type)) else: instance.__dict__[self.key] = value # 为 instance 设置属性 def __delete__(self, instance): instance.__dict__.pop(self.key)
class People: name = Type('a', str) age = Type(12, int) def __init__(self, name, age): self.name = name self.age = age p = People('zjs', 12) # 如果 第一个位置传入非字符串会出错, 第二个位置传入非int类型会出错。 print(p.name, p.age)
类的装饰器。
class Type: def __init__(self, key, wanna_type): self.key = key self.wanna_type = wanna_type # key 所期望的类型。 def __get__(self, instance, owner): # instance 就是传入的 People 实例。 return instance.__dict__[self.key] def __set__(self, instance, value): if not isinstance(value, self.wanna_type): # 判断 instance 传入的value 是不是传入的 wanna_type 类型。 raise TypeError('%s 传入的类型不是%s' % (self.key, self.wanna_type)) else: instance.__dict__[self.key] = value # 为 instance 设置属性 def __delete__(self, instance): instance.__dict__.pop(self.key) #类的装饰器: def deco(**kwargs): # kwargs == {'name': str, 'age': int} def wrapper(obj): # obj ----> People for key, val in kwargs.items(): value = Type(key, val) setattr(obj, key, value) # 设置 obj.__dict__[key] = Type return obj # 将设置完成的结果返回。 return wrapper @deco(name = str, age = int) # 相当于People = wrapper(People) 然后指定 name 为 str 类型, age 为 int 类型 class People: name = Type('a', str) age = Type(12, int) def __init__(self, name, age): self.name = name self.age = age p = People('zjs', 12) # 如果 第一个位置传入非字符串会出错, 第二个位置传入非int类型会出错。 print(p.name, p.age)
property的setter getter deleter
class Goods: def __init__(self): # 原价 self.original_price = 100 # 折扣 self.discount = 0.8 @property def price(self): # 实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price @price.setter def price(self, value): self.original_price = value @price.deleter def price(self): del self.original_price obj = Goods() obj.price # 获取商品价格 obj.price = 200 # 修改商品原价 print(obj.price) del obj.price # 删除商品原价
使用继承的二次加工(包装):
class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid def append(self, p_object): ' 派生自己的append:加上类型检查' if not isinstance(p_object,int): raise TypeError('must be int') super().append(p_object) @property def mid(self): '新增自己的属性' index=len(self)//2 return self[index] l=List([1,2,3,4]) print(l) l.append(5) print(l) # l.append('1111111') #报错,必须为int类型 print(l.mid) #其余的方法都继承list的 l.insert(0,-123) print(l) l.clear() print(l)
使用授权的二次加工(包装):
class List(list): def __init__(self,item,tag=False): super().__init__(item) self.tag=tag def append(self, p_object): if not isinstance(p_object,str): raise TypeError super().append(p_object) def clear(self): if not self.tag: raise PermissionError super().clear() l=List([1,2,3],False) print(l) print(l.tag) l.append('saf') print(l) # l.clear() #异常 l.tag=True l.clear()
2.通过使用__setattr__
, __getattr__
, __delattr__
可以重写dict,使之通过“.”调用键值。
class Dict(dict): ''' 通过使用__setattr__,__getattr__,__delattr__ 可以重写dict,使之通过“.”调用 ''' def __setattr__(self, key, value): print("In '__setattr__") self[key] = value def __getattr__(self, key): try: print("In '__getattr__") return self[key] except KeyError as k: return None def __delattr__(self, key): try: del self[key] except KeyError as k: return None # __call__方法用于实例自身的调用,达到()调用的效果 def __call__(self, key): # 带参数key的__call__方法 try: print("In '__call__'") return self[key] except KeyError as k: return "In '__call__' error" s = Dict() print(s.__dict__) # {} s.name = "hello" # 调用__setattr__ # In '__setattr__ print(s.__dict__) # 由于调用的'__setattr__', name属性没有加入实例属性字典中。 # {} print(s("name")) # 调用__call__ # In '__call__' # hello print(s["name"]) # dict默认行为 # hello # print(s) print(s.name) # 调用__getattr__ # In '__getattr__ # hello del s.name # 调用__delattr__ print(s("name")) # 调用__call__