面向对象高级
附上面向对象所以知识的思维图:
特殊属性 | 含义 |
---|---|
_name_ | 类,函数,方法等的名字 |
_class_ | 对象的类型 type (type(ClassName)) 相当于 ClassName._type_ |
__dict__★★★★ | 对象属性的字典 |
_qualname_ | 类的限定名 |
_bases_ | 类的基类的元组,顺序为他们在基类列表中出现的顺序 |
_doc_ | 类,函数的文档字符串,如果没有定义为None |
_mro_ | 类的mro ,class.mro()返回的结果的保存在__mro__中 |
_dict_ | 类的实例的属性,可写的字典 |
_module_ | 类定义所在的模块名 |
查看属性
dir(obj)
定义 | 含义 |
---|---|
_dir_ | 返回类或者对象的所有成员名称列表 dir()函数操作实例就是调用_dir_() |
如果dir[(obj)]参数obj包含方法__dir__()
方法,该方法将被调用,如果参数obj不包含__dir__()
则将最大限度的收集属性信息
dir(obj)对于不同类型的对象obj 具有不同的行为:
-
如果对象是模块对象,返回列表包含模块的属性名和变量名
-
如果对象是类型或者是类对象,返回的列表包含类的属性名,及它的祖先类的属性名
-
如果是类的实例
- 有__dir__方法,返回模块的属性和变量名,返回值必须是可迭代对象,且对应的是’str’(字典的key)
- 没有__dir__方法,则尽可能收集实例的属性名,类的属性和祖先的属性名
- 有__dir__方法,返回模块的属性和变量名,返回值必须是可迭代对象,且对应的是’str’(字典的key)
-
如果obj 不写,返回列表包括内容不同
- 在模块中,返回模块的属性和变量名
- 在函数中,返回本地作用域的变量名
- 在方法中,返回本地作用域和变量名
举例:
import t1 #导入 t1.py文件定义的类
class Animal:
X= 10
def __init__(self):
pass
from t1 import Animal
class Cat(Animal):
x = 'Cat'
y = 'dog'
class Dog(Animal):
def __dir__(self):
return ['dog']
print('~~~~~~')
print(dir()) #当前的变量名和属性
print(dir(t1))# t1文件的变量名和属性
print(1,"object's __dict__ = {}".format(sorted(object.__dict__.keys())))
print(2,"Animal's dir() = {}".format(dir(Animal)))
print(3,"Cat's dir() = {}".format(dir(Cat)))
print('~~~~~~~~~')
tom = Cat('tom')
print(4,sorted(dir(tom)))
print(5,sorted(tom.__dir__()))
print(6,sorted(set(tom.__dict__.keys()) | set(Cat.__dict__.keys()) | set(object.__dict__.keys())))
print(7,"Dog's dir = {}".format(dir(Dog)))
dog = Dog('snoppy')
print(8,dir(dog))
print(9,dog.__dict__)
dir()另外记住使用
举例:
class Person:
def show(self): # 方法中
a = 100
t = int(a)
print(1,dir()) # 当前函数的变量
print(2,locals()) # 当前locals local 是返回一一对应的键值
def test(a=50, b=100): # 函数中
c = 150
print(3,dir())
print(4,locals())
Person().show()
test()
print(5,dir()) # 当前模块的变量
print(6,sorted(locals().keys()))
print(7,sorted(globals().keys()))
>>>
1 ['a', 'self', 't']
2 {'t': 100, 'a': 100, 'self': <__main__.Person object at 0x0000000005B29128>}
3 ['a', 'b', 'c']
4 {'c': 150, 'b': 100, 'a': 50}
魔术方法:
分类
- 创建,初始化与销毁
- _new_
__init__
与__del___
- 可视化
- hash
- bool
- 运算符重载
- 容器和大小
- 可调用对象
- 上下文管理
- 反射
- 描述器
- 其他
实例化
方法 | 意义 |
---|---|
_new_ | 实例化一个对象 该方法需要一个返回值,如果该值缺少cls的实例,则不会调用_init_ 该方法永远是都是静态方法 |
class A:
def __new__(cls,*args,**kwargs):
pass
def __init__(self):
pass
a= A()
print(a)
>>>None
class A:
# def __new__(cls,*args,**kwargs):
# pass
def __init__(self):
pass
a= A()
print(a)
>>><__main__.A object at 0x0000000008227978>
#不执行__new__,所以会执行__init__.实例a 是类A 的实例
class A:
def __new__(cls,*args,**kwargs):
return 'abc'
def __init__(self,name):
self.name =name
a= A()
print(a)
>>> abc
class A:
def __new__(cls,*args,**kwargs): # 会报错,缺少name
return super().__new__(cls) # 找父类
def __init__(self,name):
self.name =name
a= A()
print(a)
class A:
# @staticmethod 可加可不
def __new__(cls,*args,**kwargs): # 会报错,缺少name
print(cls)
print(args)
print(kwargs)
return object.__new__(cls) # 找父类
def __init__(self,name):
self.name =name
a= A(name = 'tom')
print(a)
#实例化时,首先会调用__new__方法,然后将结果返回,在调用__init__
class A:
# @staticmethod 可加可不
def __new__(cls,*args,**kwargs):
cls.test = 'abc' #动态增加属性,但不太好,每创建一个实例就执行一次
return object.__new__(cls) # 找父类
def __init__(self,name):
self.name =name
a= A(name = 'tom')
print(A.__dict__)
>>><__main__.A object at 0x000000000822A6A0>
__new__
方法很少使用,即使创建了该方法,也会使用return super().__new__(cls)
基类object的__new__
方
法来创建实例并返回。
可视化
方法 | 含义 |
---|---|
_str_ | str()函数、format()函数、print()函数调用,需要返回对象的字符串表达。如果没有定义,就去调用__repr__ 方法返回字符串表达,如果__repr__ 没有定义,就直接返回对象的内存地址信息 |
_repr_ | 内建函数repr()对一个对象获取字符串表达。 调用 __repr__ 方法返回字符串表达,如果__repr__ 也没有定义,就直接返回object的定义就是显示内存地址信息 |
_bytes_ | bytes()函数调用,返回一个对象的bytes表达,即返回bytes对象 |
举例:
class Person :
def __init__(self,name,age = 18):
self.name = name
self.age = age
def __str__(self):
return 'str:{}{}'.format(self.name,self.age)
def __repr__(self):
return 'repr:{}{}'.format(self.name,self.age)
def __bytes__(self):
return "bytes:{}{}".format(self.name,self.age).encode()
a = Person('tom')
print(a) # 缺省调用str
print(str(a).encode())#先调用后转化为bytes对象
print(bytes(a))#强制类型转换 #有__bytes__会调用
print(repr(a)) #优先调用repr
print([a],(a,)) # 间接使用调用的是repr
print("{} {} {}".format(a,str(a),repr(a))) # 相当于还是在调用str
>>>
str:tom18
b'str:tom18'
b'bytes:tom18'
repr:tom18
[repr:tom18] (repr:tom18,)
str:tom18 str:tom18 repr:tom18
若缺省__str__会怎么样呢?
class Person :
def __init__(self,name,age = 18):
self.name = name
self.age = age
# def __str__(self):
# return 'str:{}{}'.format(self.name,self.age)
def __repr__(self):
return 'repr:{}{}'.format(self.name,self.age)
def __bytes__(self):
return "bytes:{}{}".format(self.name,self.age).encode()
a = Person('tom')
print(a) # 缺省调用str
print(str(a).encode())
# print(bytes(a))#强制类型转换
print(repr(a)) #优先调用repr
print([a],(a,)) # 间接使用调用的是repr
print("{} {} {}".format(a,str(a),repr(a))) # 相当于还是在调用str
>>>
repr:tom18
b'repr:tom18'
repr:tom18
[repr:tom18] (repr:tom18,)
repr:tom18 repr:tom18 repr:tom18
hash
方法 | 含义 |
---|---|
_hash_ | 内建函数hash()调用的返回值,返回一个整数,如果定义这个方法该类的实例就可以hash |
class A:
def __init__(self,name,sge = 18):
self.name = name
def __hash__(self):
return 1
def __repr__(self):
return self.name
#def __eq__(self, other): # 这个函数作用?
# return self.name == other.name
print(hash(A('tom')))
print((A('tom'),A('tom')))
print([A('tom'),A('tom')])
print('~~~~~~~~~~~')
print({1,1})
a1 = A('tom')
a2 = A('tom')
s = {a1,a2}
print(s)
# 当加了判断是否相等__rq__后结果又会如何呢?
print(hash(a1),hash(a2))
print(hash(A('tom')))
print((A('tom'), A('tom')))
print([A('tom'), A('tom')])
print('~~~~~~~~~~~~~~~~~~~~')
s = {A('tom'), A('tom')} # set #
print(s)
方法 | 含义 |
---|---|
_eq_ | 对应==操作符,判断两个对象是否相等,返回bool值 定义了这个方法,如果不提供__hash__方法,那么实例将不可hash了 |
__hash__
方法只是返回一个hash值作为set的key,但是去重还需要 __eq__
来判断2个对象的hash值是否相等,只是hash冲突,不能说明两个对象是相等的
因此,一般来说,__hash__
方法提供了作为set 或者dict的key,如果去重要同时提供__eq__
方法
去重: 集合是否去重,需要判断hash值是否相等,然后还有判断内容是否相等,即_eq_
思考:
-
list类实例为什么不可hash?
答:# list类在类定义时将__hash__设定为None 了
-
functools.lru_cache使用到的functools._HashedSeq类继承自list,为什么可hash?
答:#这个虽然继承于list,但是子类增强了功能,将值进行了拷贝赋值,转化为了tup,然后就可以使用hash了
练习:
设计二维坐标,使其成为可hash类型,并比较2个坐标的实例是否相等.
from collections import Hashable
class Point:
def __init__(self, pointx, pointy):
self.pointx = pointx
self.pointy = pointy
def __hash__(self):
return hash((self.pointx, self.pointy))
def __eq__(self, other):
return self.pointx == other.pointx and self.pointy == other.pointy
p1 = Point(5, 6)
p2 = Point(5, 6)
print(hash(p1),hash(p2))
print(p1 == p2)
print(p1 is p2)
print(hex(id(p1)),hex(id(p2)))
print(set((p1,p2)))
print(isinstance(p1,Hashable))
>>>
3713085962043070856 3713085962043070856 # hash值一致
True #相等
False#两个不同的实例对象,所以不等(is 是内存地址要一致)
0x259ee10 0x259ee80 #id不一致
{<__main__.Point object at 0x000000000259EE10>} #去重了
True #可hash
bool
方法 | 含义 |
---|---|
_bool_ | 内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值,没有定义``bool(),就找 len,返回长度,非0为真,如果 len()`也没有定义,那么所有实例都返回真 |
class A:
pass
print(bool(A()))
if A():
print('real A')
# 默认实例都会返回True
class B:
def __bool__(self):
return False # 设定返回值
print(bool(B()))
if B():
print('real B')
### 没有设定__bool__则返回__len__
class C:
def __len__(self):
return 0
print(bool(C()))
if C():
print('rael C')
运算符重载
operator模块提供以下的特殊方法,可以将类的实例使用下面的操作符来操作
运算符 | 特殊方法 | 含义 |
---|---|---|
<, <=, ==, ,>,>=, != | __lt__ , __le__ , __eq__ , __gt__ , __ge__ , __ne__ | 比较运算符 |
+, -, *, /, %, //,**, divmod | __add__ , __sub__ , __mul__ , __truediv__ , __mod__ ,__floordiv__ , __pow__ , __divmod__ | 算数运算符,移位、位运算也有对应的方法 |
+=, -=, *=, /=,%=, //=, **= | __iadd__ , __isub__ , __imul__ , __itruediv__ ,__imod__ , __ifloordiv__ , __ipow__ |
正常情况下,两个例之间是无法进行加减算数运算的,但是现实中又要想使用?该怎么进行呢?
举例:
class A:
def __init__(self,age):
self.age= age
def __sub__(self,other):
return self.age-other.age # 这里返回的只是相减的数值 #return self.__class__(self.age-other.age)
def __isub__(self,other)
return A(self.age -other.self) # 创建了新的实例
def __isub__(self,other)
self.age-=other.age
return self #就地修改
def __repr__(self):
return "{}".format(self.age)
a1= A(20)
a2 = A(16)
print(a1-a2)
a1-=a2 # a1.__isub__.(a2) # 如果没有定义__isub__会转而调用__sub__ a1 = a1-a2 先将a1-a2进行相减,然后赋值给a1,
print(a1)# 结果会按照设定好的格式输出
>>>
4
4
总结:
__isub __方法定义,一般会 in-place 就地来修改自身
如果没有定义,则会调用_sub_
练习:
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def add(self,other):
return self.__class__(self.x+other.x,self.y+other.y)
def __eq__(self,other):
return self.x==other.x and self.y == other.y
def __add__(self,other):
return self.add(other)
def __iadd__(self,other):
self.x+=other.x
self.y+=other.y
return self
def __repr__(self):
return "({},{})".format(self.x,self.y)
p1 = Point(5,6)
p2 = Point(6,5)
# print(p1==p2)
# print(p1+p2)
p1+p2
print(p1+p2)
print(p1+p1+p2) #上面的add函数返回的仍然是一个实例对象.所以可以链式编程
print((p1+p2).add(p2).__add__(p2))#每一次返回的都是实例对象,所以可以链式相加
运算符重载应用场景
往往是用面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式。例如,上例中
的对+进行了运算符重载,实现了Point类的二元操作,重新定义为Point + Point。
提供运算符重载,比直接提供加法方法要更加适合该领域内使用者的习惯。
int类,几乎实现了所有操作符,可以作为参考。
@functools.total_ordering 装饰器
from functools import total_ordering
@total_ordering
class Person:
pass
正常情况下不建议使用
容器相关方法
方法 | 含义 |
---|---|
__len__ ★ | 内建函数len(),返回对象的长度,(>=0)的整数,如果把对象当做容器类型看,就如同list或者dict() bool()函数调用的时候,如果没有 __bool__ ,则会看__len__ 方法是否存在,存在返回非0为真 |
__iter__ ★ | 迭代容器时调用,返回一个新的迭代器对象 |
__contains__ | in成员运算符,没有实现,就调用__iter__ 方法遍历 |
__getitem__ ★ | 实现self[key]的访问,序列对象,key接受整数位索引,或者切片,对于set和dict,key为hashable key 不存在时引发keyErrpror |
__setitem__ ★ | 和__getitem__ 的访问类似,是设置值的方法 |
__missing__ | 字典或其子类使用,__missing__ (),调用时,key不存在执行该方法 |
class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def additem(self,item):
self.items.append(item)
def __iter__(self):
return iter(self.items)
def __getitem__(self,index):
return self.items[index]
def __setitem__(self,key,value):
self.items[key]=value
def __str__(self):
return str(self.items)
def __add__(self,other):
self.items.append(other)
return self
cart = Cart()
cart.additem(1)
cart.additem('abc')
cart.additem(3)
#长度,bool
print(1,len(cart))
print(2,bool(cart))
#迭代
for x,i in enumerate(cart):
print(x,i)
print('-'*30)
print(4,cart.items)
#in
print(5,3 in cart,2 in cart)
#索引操作
print(6,cart[1])
cart[1]='xyz'
print(7,cart)
#链式编程实现加法
print(8,cart+4+5+6)
print(9,cart)
print(10,cart.__add__(13).__add__(18))
print(11,cart)
>>>
1 3
2 True
0 1
1 abc
2 3
------------------------------
4 [1, 'abc', 3]
5 True False
6 abc
7 [1, 'xyz', 3]
8 [1, 'xyz', 3, 4, 5, 6]
9 [1, 'xyz', 3, 4, 5, 6]
10 [1, 'xyz', 3, 4, 5, 6, 13, 18]
11 [1, 'xyz', 3, 4, 5, 6, 13, 18]
可调用对象
先看一个例子
def foo():
print('foo function')
foo()
foo.__call__()
函数的调用可以使用foo()
调用,但用foo.__call__
同样可以调用.那么类呢?
class A:
def __call__(self,*args,**kwargs):
print(args)
print(kwargs)
adder = A()
adder(*range(10))
方法 | 含义 |
---|---|
__call__ | 类中定义一个该方法,实例就可以向函数一样调用 |
class Point:
def __init__(self,x,y):
self.x =x
self.y = y
def __call__(self,*args,**kwargs):
return 'point{}{}'.format(self.x,self.y)
p = Point(4,5)
print(p)
#print(p()) 定义__call__与不定义__call__是不同的
定义了__call__
之后,实例便可以使用p()调用
练习:
定义一个斐波那契数列,方便调用,计算第n项
class Fib:
def __init__(self):
self.items=[0,1,1] # 首先定义一个列表,用于存储
def __call__(self,index):
if index < 0:
raise IndexError('wrong Index')
if index <len(self.items):
return self.items[index]
for i in range(len(self.items),index+1):
self.items.append(self.items[i-1]+self.items[i-2])
return self.items[index]
print(Fib()(35))# 可以直接使用括号调用
在上例中增加迭代的方法,返回容器长度,支持索引
class Fib:
def __init__(self):
self.items=[0,1,1] # 首先定义一个列表,用于存储
self.newitems=[]
def __len__(self):
return len(self.newitems)
def __iter__(self):
return iter(self.newitems)
def __getitem__(self,index):
if index < 0:
raise IndexError('wrong Index')
if index <len(self.items):
self.newitems= self.items[:index]
return self.items[index],self.newitems
for i in range(len(self.items),index+1):
self.items.append(self.items[i-1]+self.items[i-2])
return self.items[index],self.newitems[:index]
def __call__(self,index):
return self[index]
def __repr__(self):
return "{}".format(self.newitems) # 应该返回上次的计算值
f = Fib()
print(f(35))
print(f[9])
# print(f)
print(f(5))
print(f(8))
print(f)
print(len(f))
for i in f:
print(i)
上下文管理
文件IO操作可以对对象上使用上下文管理,使用with …as…语法
上下文管理格式
class A:
def __init__(self):
pass
def __enter__(self):
pass
def __exit__(self,exc_type,exc_val,exv_tb):
pass
with A() as B:
pass
方法 | 含义 |
---|---|
__enter__ | 进如与此对象相关的上下文,如果存在该方法,with 语句会把该方法的返回值作为绑定到as 子句中指定的变量中 |
__exit__ | 退出时此对象相关的上下文 |
上下文格式中的__enter_
与__exit__
是成对使用的,二者缺一不可,否则将会报错AttributeError
实例化对象的时候,并不会调用enter 进入with语句调用__enter__
方法.然后执行语句体,最后离开时,调用__exit__
方法
with 可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作,
注意with并不是开启一个新的作用域
测试上下文管理执行的顺序:
import time
class A:
def __init__(self):
print(1,'init~~~~~~')
time.sleep(2)
def __enter__(self):
time.sleep(3)
print(2,'enter~~~~~~~~~~')
time.sleep(2)
def __exit__(self,exc_type,exc_val,exc_tb):
print(5,'out~~~~~')
time.sleep(2)
f = open('test')
with f as f1:
print()
with A() as a : # 类中定义__enter__和__exit__ 成对出现,一个不能少,这样就支持上下文管理
# with 操作的是实例
print(3,'enter with')
time .sleep(3)
print(4,'with exit')
>>>
1 init~~~~~~
2 enter~~~~~~~~~~
3 enter with
4 with exit
5 out~~~~~
上下文管理的安全性
上下文管理,不管with语句中会引发什么异常,__exit__
语句都会执行.所以上下文管理是安全的
with 语句
class A:
def __init__(self):
print(1,'init~~~~~~')
def __enter__(self):
print(2,'enter~~~~~~~~~~')
# return self #返回self结果会出现什么
def __exit__(self,exc_type,exc_val,exc_tb):
print(exc_type)
print(exc_val)
print(exc_tb)
print(5,'out~~~~~')
a =A()
with a as b: # b =a.__enter__() 等效,返回的是__enter__,
print(1,a)
print(2,b)
print(a ==b)
print(a is b)
print(id(a),id(b))
# a 和 b 竟然不一样
>>>
1 init~~~~~~
2 enter~~~~~~~~~~
1 <__main__.A object at 0x0000000005CAB160>
2 None
False
False
97169760 1575814352
None
None
None
5 out~~~~~
with a as b:
与with open('test')as f:
这个with语句有什么区别?
者里a和b不想等的原因在于__enter__
上,它将自己的返回值赋值给了f ,因此在__enter__
的返回值应该是实例本身,这样才能保证a和b相等
with 语法,会调用with 后的对象的 __enter__
方法,如果有as,则将该方法的返回值赋值给as子句的变量,上例中就等价于,b = a.__enter__
__exit__
方法的参数__exit__(self,exc._type,exc._val,exc._tb)
这三个参数都与异常相关,如果该上下文退出时没有异常,则这三个值都为None如果有异常,参数意义如下
exc._type
:异常类型exc._value
:异常的值exc._tb
:异常的追踪信息
__exit__
返回一个等效True的值,则压制异常,否则继续抛出异常
class A:
def __init__(self):
print(1,'init~~~~~~')
def __enter__(self):
print(2,'enter~~~~~~~~~~')
def __exit__(self,exc_type,exc_val,exc_tb):
print(exc_type)
print(exc_val)
print(exc_tb)
print(5,'out~~~~~')
# return 1
a =A()
with a as b:
print('with enter')
1/0
print('exit with')
#如果将上面的return 1注释行取消,结果会是什么样?
>>>#释放后返回结果
1 init~~~~~~
2 enter~~~~~~~~~~
with enter
<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x00000000057551C8>
5 out~~~~~
#可以看出释放后,可以打印出结果,而且异常也被拦截了,没有抛到外面
练习:
给一个加法运算加上时间统计
import time
import datetime
from functools import wraps,update_wrapper
def timeit(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta =(datetime.datetime.now()-start).total_seconds()
print(delta)
return ret
return wrapper
@timeit
def add(x,y):
time.sleep(2)
return x+y
# add(4,5)
class Timeit:
def __init__(self,fn):
self.fn= fn
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self,exc_type,exc_val,exc_tb):
delta = (datetime.datetime.now()-self.start).total_seconds()
print("{}took{}s ".format(self.fn.__name__,delta))
def __call__(self,x,y): # 加了__call__可以使用实例加括号调用
print(x,y)
return self.fn(x,y)
with Timeit(add) as b:
print(b(4,5))
# add(4,5)
那么能不能把类当做装饰器使用呢?
import time
import datetime
from functools import wraps ,update_wrapper
class Timeit:
"""this is Timeit class"""
def __init__(self,fn):
self.fn = fn ##将函数初始化到实例
# self.__doc__ = fn.__doc__ # 直接修改__doc__和__name__
# self.__name__=fn.__name__
# update_wrapper(self,fn) #或者使用update_wrapper进行更新
wraps(fn)(self) # 不能使用@wraps(fn)但是可以使用wraps函数进行修改
def __call__(self,*args,**kwargs):
start = datetime.datetime.now()
ret = self.fn(*args,**kwargs)
self.delta =(datetime.datetime.now()-start).total_seconds()
print('{}took{}s,in dec'.format(self.fn.__name__,self.delta))
return ret
@Timeit # 用类装饰
def add(x,y):
"""this is add function"""
return x +y
@Timeit
def sub(x,y):
"""this is sub function"""
return x +y
print(add(4,5))
print(add.__doc__,add.__name__)
print(sub.__doc__,sub.__name__)
总结:
这里加了__call__
的目的在于可以使用add(4,5),函数名加括号加参数的方法直接调用类,如果不加__call__
调用就需要先实例化然后才能调用.
上下文管理应用场景
1.增强代码,在代码执行的前后增强代码,以增强其功能,类似装饰器的功能
2.资源管理:打开了资源需要关闭,例如文件对象,网络连接.数据库连接等
3.权限验证:在执行代码之前,做权限验证,在__enter__
中处理
contexlib.contexmanger
它是一个装饰器实现上下文管理,装饰一个函数,而不同想类一样实现__enter_
和__exit__
方法,对下面的函数有要求,必须有yield ,也就是这个函数必须返回一个生成器,且只有一个yield
也就是这个装饰器接收一个生成器对象作为参数
from contextlib import contextmanager
#import contextlib
#@contexlib.contexmanager
#两种导入模块都可以
@contextmanager
def a():
print('enter')
yield 123
# yield 表示进入之前的代码,yield之后表示退出的代码,默认不加返回None
print('exit')
with a()as f :
print('f=',f)
f接收yield语句的返回值
contextmanager
是否也能和类的上下文管理(__enter__
和__exit__
)一样都能实现异常处理呢?答案是不可以,contextmanager
不具备异常的处理,但是能不能实现呢?
from contextlib import contextmanager
@contextmanager
def a():
print('enter')
try:
yield 123 # 有异常 不能正确执行,所以加一个try finally ,表示退出之后的代码一定会执行
finally:
print('exit')
with a()as f :
print('f=',f)
1/0
print('with over')
print("~~~~~")
>>>
enter #进入能打印
f= 123#执行第一个打印yield的返回值
exit# 报错,所以执行finally
#抛出异常,下面的不在执行
-----------------------------------------------------
ZeroDivisionError
总结:如果业务和逻辑简单,可以使用函数如contexlib.contexmanger 装饰器方式,如果业务复杂,用类的方式加__enter__
和__exit__
方法更好
反射
概述
运行时,区别于编译时,指的是程序被加载到内存中执行的时候
反射,reflection ,指的是运行时获取类型定义的信息
一个对象能够在运行时,像照镜子一样,反射出其类型信息
简单说,在python中,能够通过一个对象,找出其type,class,attritube 或者method 的能力,称为反射或者自省
具有反射能力的函数有type(),isintance(),callable(),dir(),getattr()
反射相关的函数与方法
需求
有一个Point,查看它实例的属性,并修改它,动态为实例增加属性
from functools import update_wrapper
class Point:
def __init__(self,x,y):
self.x= x
self.y =y
def show(self):
print(self.x,self.y)
def __repr__(self):
return "({}{})".format(self.x,self,y)
p1 = Point(4,5) #实例化
print(p1.x,p1.y)#输出x,y
p1.x =20 # 修改的值
print(p1.__dict__)#打印当前__dict__
p1.z = 100 #增加实例属性值
p1.__dict__['x']=200#修改x的值
print(p1.__dict__)#打印字典
print(dir(p1))#打印实例p1的所有参数,包含父类
>>>
4 5
{'x': 20, 'y': 5}
{'x': 200, 'y': 5, 'z': 100}
上述通过属性字典__dict__
来访问对象的属性,本质上也是利用的反射的能力
但是,上面的例子中,访问的方式不优雅,Pyhon提供了内置的函数
内建函数 | 含义 |
---|---|
getattr(object,name[,default]) | 通过name 返回object对象,当属性不存在时,将使用default返回,如果没有default则返回AttributeError,name必须为字符串 |
setattr(object,name[,default]) | object 的属性存在时,则覆盖,不存在新增 |
hasattr(object,name) | 判断对象属性是否有这个名字的属性,name必须为字符串 |
注意: 下面操作都是类对象
举例:
from functools import update_wrapper
class Point:
def __init__(self,x,y):
self.x= x
self.y =y
print(getattr(p1,'y',100)) # 返回y的值,存在则返回当前值,不存在则返回缺省值
print(getattr(p1,'z',100))#z,不存在,所以返回100,但是字典中仍不存在 z
setattr(Point,'xyz',100) # 给类增加一个属性值
print(Point.__dict__)#打印类字典
if not hasattr(Point,'show'): #动态创建属性
setattr(Point,'show',lambda self:print(self.x,self.y))
#相当于定义了一个函数,def show(self):print(self.x,self.y)
p2 = Point(4,5)
p2.show()
p1 = Point(10,5)
if not hasattr(p1,'showy'): #在实例上动态创建属性
setattr(p1,'showy',lambda self:print(self.x,self.y))
p1.showy()#能调用么?
p1.showy(p1)#要自己手动给参数self
print(p1.show) # 存在绑定这是调的point的show,
<bound method <lambda> of <__main__.Point object at 0x0000000005EAF4E0>>
print(p1.showy)#不存在绑定因此需要手动传入参数
<function <lambda> at 0x00000000082B4C80>
#所以不建议把方法放在实例属性上,最好是放在类属性里
这种动态增加属性的方式和装饰器装饰一个类,Mixin有什么区别
这种动态增删属性的方式.是运行时,改变类或者实例的方式,但是装饰器或者Mixin都是在定义时就决定了,因此反射能力具有更大的灵活性
反射相关的魔术方法
__getattr__
( ),__setattr__()
,__delattr__()
这三种魔术方法
class Base:
n=10
class Point(Base):
z= 6
def __init__(self,x,y):
self.x=x
self.y=y
def show(self):
print(self.x,self.y,self.z)
def __getattr__(self,item): #当实例访问找不到属性时会执行这段,不报异常,仅针对实例,类访问不了
print(item,'~~~~~~~~~~~~')# item为查找的属性名
print(type(item))
return 1000
def __setattr__(self,key ,value):#设置属性 若实例有__setattr__
print(key,value,'======')
# super().__setattr__(key,value)# 调用父类的__setattr__
self.__dict__[key]= value
# setattr(self,key,value)# 会出现递归
def __delattr__(self,item):
print('del attribute {}'.format(item))
p1 = Point(4,5)
print(p1.x,p1.y)
print(p1.z) #不存在会调用__getattr__
print(p1.n)
print(p1.__dict__)
# print(p1.xxxxxx)
# # >>>xxxxxx ~~~~~~~~~~~~
# #<class 'str'>
# #返回值为 None
print(p1.y,'++++')
del p1.x
del p1.
总结:
__getattr__
会在实例找属性时,如果找不到属性,将会调用此方法
__setattr__
实例调用时首先调用__setattr__
,如果没定义会调用object的__setattr__
效果等同于self.__dict__.[key]=value
,手动给字典里增加键值对
在看一个例子
class Point:
z = 6
d ={} # 类属性创建一个字典
def __init__(self,x,y):
self.__dict__['x']= x #手动给实例字典创建参数
self.__dict__['y']= y
def __getattr__(self,item):
return self.d[item] # 实例调用不存在转而手动给字典增加参数
def __setattr__(self,key,value):
print(key,value,'====')
if key not in self.__dict__.keys():
self.d[key]= value
else:
self.__dict__[key]=value
p1= Point(4,5)
print(1,p1.__dict__)
print(2,p1.x,p1.y)
p1.tttt=200 # 和初始化的self.x = x类似,对属性赋值,转而调用__setattr__
print(p1.__class__.__dict__)
print(p1.tttt)
__getattribute__
魔术方法
class Point:
z = 6
d ={}
def __init__(self,x,y):
self.x= x
self.y = y
def __getattribute__(self,item):
print(item)
def __getattr__(self,item):
return self.d[item]
def __setattr__(self,key,value):
print(key,value,'====')
if key not in self.__dict__.keys():
self.d[key]= value
else:
self.__dict__[key]=value
p1= Point(4,5) # 直接报错
>>>#创建实例,首先会调用__new__返回实例调用__init__,然后在给实例赋值,转而调用__setattr__,字典内key不存在,所以会调用self.__dict__[key]=value这一语句,但是在调用这句时,用到了self.__dict__,此时变会转而调用__getattribute__,所以_getattribute__里面是啥就返回啥.
x 4 ====
__dict__
由上可得:所有的实例访问第一步都会先调用__getattribute__
描述器
用到三个魔术方法:__get()__
,__set__
(),__delete__()
object.__get__(self,instance,owner)
object__set__(self,instance,value)
object__delete(self,instance)
self 指代当前实例,调用者
instance 是oewner 的实例
owner 是属性的所属类
先看一个例子:
class A:
def __init__(self):
self.a1= 'a1'
print(1,'A init')
class B:
x = A()
def __init__(self):
print(2,'B init')
print(3,"-"*30)
print(4,B.x) # <__main__>
print(5,B.x.a1)# 实际调用的是A().a1 所以为a1
b = B()#初始化实例B
print(6,b.x) # <__main__>
print(7,b.x.a1)#a1
>>>
1 A init # B类中实例化A(),所以会打印 1
3 ------------------------------
4 <__main__.A object at 0x0000000006AA3F98>
5 a1
2 B init
6 <__main__.A object at 0x0000000006AA3F98>
7 a1
进行改造:
class A:
def __init__(self):
self.a1= 'a1'
print('A init')
def __get__(self,instance,owner):
print('get~~~~',self,instance,owner)
print(self.__dict__)
return self
class B:
x = A()
def __init__(self):
self.y =A() # 定义在实例属性是否可以呢?
print('B init')
b= B()
print("-"*30)
print(b.x) # 定义了 __get__后,会先调用实例的__get__ # instance 是None 没有实例
print(b.x.a1) # 先执行B.x 就是访问A实例的访问,转而调用__get__,然后self,然后在调用A实例的属性
print(b.x) # instance 为B实例,与 B.x 区别
print(b.y) #不在进入描述器,所以在实例中属性是不适用的
因为定义了__get__
方法,类A就是一个描述器,对类B或者类B的实例的x的属性的读取,成为对类A的实例的访问,就会调用__get__
方法
上类中:
self 对应的都是A的实例
owner 对应的都是B类
instance说明:
- None 表示不是B类的实例,对应调用B.x
- <__main__
.B object…> 表示 是B的实例,对应调用B().x 描述器定义:
Python 中,一个类实现了__get__
,__set__
,__delete__
三个方法中的任何一个方法,就是描述器,实现这三个中的某个方法,就支持了描述器协议
- 仅实现
__get__
,就是非数据描述符non-data descriptor<br/ - 实现
__get__
,__set__
就是数据描述符 data descriptor
如果一个类的类属性设置为描述器实例,那么它被称为owner 属主
当该类的该类属性被查找,设置,删除时,就会调用描述器相应的方法
属性的访问属性
为上例中的B 增加实例属性x
class A:
def __init__(self):
self.a1= 'a1'
print('A init')
def __get__(self,instance,owner):
print('get~~~~',self,instance,owner)
print(self.__dict__)
return self
def __set__(sef,instance,value):
pass
class B:
x = A()#类属性可以,描述器和属主类的类属性有关
def __init__(self):
self.x ='b.x' #并不会调用描述器
print('B init')
print("-"*30)
print(B.x)
print(B.x.a1)
print('~~~~~~~~~~~~~')
b=B()
print('+++++++++')
print(b.x)# 当注释上面的__set__结果完全不一样,什么原因
#为什么会转而调用__get__了呢?
print(b.__dict__)# 有__set__ 则返回为空
#将上面的def __set__ 代码更改
def __set__(self,instance,value):
print('set~~~~',value)
self.data= value
print(self.__dict__)
# 这里会给实例动态增加了一个属性值 data 值为传进来的value
所有的b.x
就会访问描述器的__get__()
方法,代码中返回的self 就是描述器实例,它的实例字典就保存着a1 和data 属性,可以打印b.x__dict__
就可看到这些属性
属性查找顺序:
实例的__dict__
优先于非数据描述器
数据描述器优先于实例的__dict__
__delete__
方法有同样的效果,有了这个方法,也是数据描述器
Python中的描述器
描述器在Python中应用非常广泛
property()函数实现为一个数据描述器, @classmethod 和@staticmethod 都是非数据描述器
新增方法
python3.6版本新增了__set_name__
的方法,它在属主构建的时候就会调用
class A:
def __init__(self):
self.a1= 'a1'
print('A init')
def __get__(self,instance,owner):
return self
def __set_name__(self,owner, name):#用到描述器时就会调用,比__get__先调用
print(owner ,name,type(name))
self.name = name
print(self.name)
class B:
x = A() # 一个实例仅为一个例实例服务
y = A()
def __init__(self):
print('B init')
提供这个方法,就可以知道属主类型和属主类的类属性名
练习:
1,实现StaticMethod 装饰器
2,实现ClassMethod装饰器
class StaticMethdo:
def __init__(self,fn):
self.fn = fn
def __get__(self,instance,owner):
return self.fn
from functools import partial
class ClassMethod:
def __init__(self,fn):
self.fn = fn
def __get__(self,instance,owner):
# print(type(instance))
# print(instance)
# print(owner)
#上面可得到instance 和owner的相关信息.通过type便可得到属主
return partial(self.fn,owner) #偏函数,将一个参数固定
class A:
def __init__(self):
self.x = 'a1'
@StaticMethdo
#等价式 smtd= StaticMethd(smtd) 这里就想当于创建了描述器实例
def smtd(x,y):
print(x,y)
@ClassMethod
def clsmtd(cls):
print(cls.__name__)
a = A()
a.smtd(4,5)
A().clmtd(4,5)
A.clmtd
优化上面偏函数:使用偏函数会更改函数的__name__
等信息?该如何优化?
from functools import update_wrapper,wpaps
class ClassMethod:
def __init__(self,fn):
self.fn = fn
def __get__(self,instance,owner)
newfun = partial(self.fn,owner)
update_wrapper(newfun,self.fn)
#wraps(self.fn)(newfun)
return newfun
3,对实例的数据进行校验
class Person:
def __init__(self,name:str,age:int):
params = ((name,str),(age,int))# 写死要检查的变量
if not self.checkdata(params):
raise TypeError
self.name = name
self.age = age
def checkdata(self,params):
for p, typ in params:
if not isinstance(p,typ):
return False
return True
t = Person('tom',20)
用装饰器实现:
import inspect
def tycheck(cls):
def wrapper(*args,**kwargs): #需要传入参数,所以需要柯里化在包装一层
# print(inspect.signature(cls))
#(name:str, age:int) #返回签名
sig = inspect.signature(cls)
pass
# 在把以前的参数检查补充
return cls(*args,**kwargs) # 还需要将类返回
return wrapper
@tycheck
class Person:
def __init__(self,name:str,age:int):
self.name = name
self.age = age
t = Person('tom',20)
能都用描述器写么?
class TypeCheck:
def __init__(self,typ):
self.typ = typ
def __get__(self,instance,owner):
if instance :
return instance.__dict__[self.name]
else:
raise Exception
def __set_name__(self,owner,name):
self.name = name
def __set__(self,instance,value):
print('set~~~',self,instance,value) #value = 'tom'
#这里的instance一定有值
if instance:
if isinstance(value,self.typ): # self.typ为上面传入的str
instance.__dict__[self.name]= value # 给实例字典中加入值
else:
raise TypeError
class Person:
"""也是硬编码"""
# name = TypeCheck('name',str) # 每一个类属性单独检验
# age = TypeCheck('age',int)
# 3.6以后可以使用__set_name__
name = TypeCheck(str) #__set_name__能得到实例的名
age = TypeCheck(int)
def __init__(self,name:str,age:int):
self.name = name #转而调用__set__
self.age = age
t = Person('tom',20)
j = Person('jerry',18)
print(t.__dict__)
print(j.__dict__)
print(t.name)
print(j.name)
下面解决硬编码问题:
from functools import update_wrapper,wraps
import inspect
class Typecheck:
def __init__(self,name ,typ):
self.typ = typ
self.name = name
def __get__(self,instance,owner):
print('get~~~~~~~')
if instance:
return instance.__dict__[self.name]
else:
# raise Exception
return self
# def __set_name__(self,owner,name):#动态注入时不会调用
# self.name = name
def __set__(self,instance,value):
print('set~~~',self,instance,value)
if isinstance (value,self.typ):
instance.__dict__[self.name]=value
else:
raise TypeError(self.name)
def datainject(cls):#类属性注入
sig = inspect.signature(cls)
params = sig.parameters
print(params)
for name,param in params.items():
print(name ,param.name,param.kind,param.default,param.annotation)
if param.annotation != inspect._empty:#判断参数注解是否为空
setattr (cls,name,Typecheck(name,param.annotation))
#为一个类注入一个属性
return cls
@datainject
class Person:
def __init__(self,name:str,age:int):
self.name = name # 走描述器
self.age = age
t = Person('tom',20)
# print(t.__dict__)
j= Person('jerry',18)
print(t.__dict__)
print(j.__dict__)
t.name
j.name
现在将函数改装成类:
from functools import update_wrapper,wraps
import inspect
class Typecheck:
def __init__(self,name ,typ):
self.typ = typ
self.name = name
def __get__(self,instance,owner):
print('get~~~~~~~')
if instance:
return instance.__dict__[self.name]
else:
# raise Exception
return self
def __set__(self,instance,value):
print('set~~~',self,instance,value)
if isinstance (value,self.typ):
instance.__dict__[self.name]=value
else:
raise TypeError(self.name)
class Datainject:
def __init__(self,cls):
self.cls=cls
sig = inspect.signature(cls)
params = sig.parameters
print(params)
for name,param in params.items():
print(name ,param.name,param.kind,param.default,param.annotation)
if param.annotation != inspect._empty:#判断参数注解是否为空
setattr (cls,name,Typecheck(name,param.annotation))
def __call__(self,*args,**kwargs):
return self.cls(*args,**kwargs)
def datainject(cls):#类属性注入
sig = inspect.signature(cls)
params = sig.parameters
print(params)
for name,param in params.items():
print(name ,param.name,param.kind,param.default,param.annotation)
if param.annotation != inspect._empty:#判断参数注解是否为空
setattr (cls,name,Typecheck(name,param.annotation))
#为一个类注入一个属性
return cls
@Datainject
class Person: #person = Datainject(Person)('tom',20) # 能用括号调用,所以肯定要用__call__
def __init__(self,name:str,age:int):
self.name = name # 走描述器
self.age = age
t = Person('tom',20)
# print(t.__dict__)
j= Person('jerry',18)
print(t.__dict__)
print(j.__dict__)
t.name
j.name
上面就将函数装饰器改装成了类装饰器.