python学习之 ---面向对象高级进阶+魔术方法+描述器

本文深入探讨Python面向对象的高级特性,包括查看属性的`dir()`函数,魔术方法的分类和应用,如`__new__`、`__str__`、`__hash__`、`__eq__`等。此外,文章还讲解了描述器的概念,如何使用描述器协议来控制属性访问,并讨论了Python的反射机制,以及上下文管理器的实现和用途。最后,通过实例解析了如何利用`@functools.total_ordering`装饰器以及实现静态方法和类方法的装饰器。
摘要由CSDN通过智能技术生成

面向对象高级

附上面向对象所以知识的思维图:

mark

特殊属性含义
_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__方法,则尽可能收集实例的属性名,类的属性和祖先的属性名
  • 如果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_

思考:

  1. list类实例为什么不可hash?

    答:# list类在类定义时将__hash__设定为None 了

  2. 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

上面就将函数装饰器改装成了类装饰器.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值