一、上下文管理协议
with open('filename') as f:
'代码块'
这样的with语句,叫做上下文管理协议,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
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 # 如果返回True,则异常被吞掉,代码正常执行
with Open('a.txt') as f:
print('=====>执行代码块')
raise AttributeError('***着火啦,救火啊***') # 只要出现异常,且没被吞掉异常,代码块以后的部分都不会被运行
print('0'*100) #------------------------------->不会执行
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,使用者无须再去关心这个问题,这将大有用处
二、描述符
本身是一个新式类
python是弱类型语言,即参数的赋值没有类型限制,可以通过描述符来实现类型的限制功能
class Typed:
def __init__(self,name,expected_type): # expected_type 可以判断任意类型
self.name = name
self.expected_type = expected_type
def __get__(self, instance, owner):
print('get--->', instance, owner)
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
print('set--->', instance, value)
if not isinstance(value, self.expected_type):
raise TypeError('Expected %s' %str(self.expected_type))
instance.__dict__[self.name] = value
def __delete__(self, instance):
print('delete--->', instance)
instance.__dict__.pop(self.name)
class People:
name=Typed('name', str) # 这里代码重复,可以使用装饰器改进
age=Typed('name', int)
salary=Typed('name', float)
def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
p1=People(123, 18, 3333.3) # 报错
p1=People('yuanshen', '18', 3333.3) #报错
p1=People('yuanshen', 18, 3333)
使用类的装饰器完成类型限制
class Typed:
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, instance, owner):
print('get--->', instance, owner)
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
print('set--->', instance, value)
if not isinstance(value, self.expected_type):
raise TypeError('Expected %s' %str(self.expected_type))
instance.__dict__[self.name] = value
def __delete__(self, instance):
print('delete--->', instance)
instance.__dict__.pop(self.name)
def typeassert(**kwargs):
def decorate(cls):
print('类的装饰器开始运行啦------>', kwargs)
for name, expected_type in kwargs.items():
setattr(cls, name, Typed(name, expected_type))
return cls
return decorate
@typeassert(name=str,age=int,salary=float)
# 有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs
# 2.People=decorate(People)
class People:
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary
print(People.__dict__)
p1=People('yuanshen', 18, 3333.3)
实用描述符实现静态属性
自制property
class Lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
if instance is None: # 当尝试进行类调用的时候
return self
return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情
class Room:
def __init__(self,name,width,length):
self.name=name
self.width=width
self.length=length
@Lazyproperty # area = Lazyproperty(area) 相当于定义了一个类属性,即描述符
def area(self):
return self.width * self.length
r1 = Room('yuanshen', 1, 1)
print(r1.area)
自定义property实现延迟计算
class Lazyproperty:
def __init__(self,func):
self.func=func
def __get__(self, instance, owner):
print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
if instance is None:
return self
else:
print('--->')
value=self.func(instance)
setattr(instance,self.func.__name__,value) #计算一次就缓存到实例的属性字典中
return value
class Room:
def __init__(self,name,width,length):
self.name=name
self.width=width
self.length=length
@Lazyproperty #area=Lazyproperty(area) 相当于'定义了一个类属性,即描述符'
def area(self):
return self.width * self.length
r1=Room('yuashen',1,1)
print(r1.area) #先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法
print(r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
class Lazyproperty:
def __init__(self,func):
self.func = func
def __get__(self, instance, owner):
print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
if instance is None:
return self
else:
value = self.func(instance)
# 把值写入实例对象的__dict__起作用,仅当描述符为非数据描述符才行
instance.__dict__[self.func.__name__] = value
# setattr(instance, self.func.__name__, value)
return value
# return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情
def __set__(self, instance, value):
print('hahahahahah')
class Room:
def __init__(self,name,width,length):
self.name = name
self.width = width
self.length = length
@Lazyproperty # area = Lazyproperty(area) 相当于定义了一个类属性,即描述符
def area(self):
return self.width * self.length
print(Room.__dict__)
r1 = Room('yuanshen', 1, 1)
print(r1.area)
print(r1.area)
print(r1.area)
print(r1.area)
# 缓存功能失效,每次都去找描述符了,为何,因为描述符实现了set方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了
一个静态属性property本质就是实现了get、set、delete三种方法
class Foo:
@property
def AAA(self):
print('get的时候运行我啊')
@AAA.setter
def AAA(self, value):
print('set的时候运行我啊')
@AAA.deleter
def AAA(self):
print('delete的时候运行我啊')
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1 = Foo()
f1.AAA
f1.AAA = 'aaa'
del f1.AAA
# 上下两种定义方法都可以
class Foo:
def get_AAA(self):
print('get的时候运行我啊')
def set_AAA(self,value):
print('set的时候运行我啊')
def delete_AAA(self):
print('delete的时候运行我啊')
AAA = property(get_AAA, set_AAA, delete_AAA) # 内置property三个参数与get,set,delete一一对应
f1 = Foo()
f1.AAA
f1.AAA = 'aaa'
del f1.AAA
只定义getter方法,不定义setter方法就是一个只读属性,birth是读写属性,age就是只读属性。
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015 - self._birth
# 方法名称和实例变量均为gender:
@property
def gender(self):
return self.gender #应该使用self._gender,不重名
属性的方法名不要和实例变量重名,通过实例.方法调用的时候,会首先转换为方法调用,但在执行return self.gender时,又视为访问self的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错RecursionError
三、枚举类
枚举(Enum)是一种数据类型,是绑定到唯一值的符号表示
# 不使用枚举类
from dataclasses import dataclass
@dataclass
class Car:
model: str
price: float
registraion_state: str
def total_cost(self) -> float:
match self.registraion_state:
case "OR":
return self.price + (self.price * 0.05)
case "WA":
return self.price + (self.price * 0.10)
case "CA":
return self.price + (self.price * 0.08)
case _:
raise TypeError("Invalid registraion_state value")
car1 = Car(model="RAV4", price=30000, registraion_state="OR")
car2 = Car(model="RAV4", price=30000, registraion_state="WA")
car3 = Car(model="RAV4", price=30000, registraion_state="CA")
print(car1.total_cost())
print(car2.total_cost())
print(car3.total_cost())
# 使用枚举类
from dataclasses import dataclass
from enum import Enum
class StateTax(Enum):
OR = 0.05
WA = 0.10
CA = 0.08
@dataclass
class Car:
model: str
price: float
tax: StateTax
def total_cost(self) -> float:
return self.price + (self.price * self.tax.value)
def get_tax(self):
return self.tax.value
car1 = Car(model="RAV4", price=30000, tax=StateTax.OR)
car2 = Car(model="RAV4", price=30000, tax=StateTax.WA)
car3 = Car(model="RAV4", price=30000, tax=StateTax.CA)
print(car1.total_cost())
print(car2.total_cost())
print(car3.total_cost())
上下两个代码运行结果不变,但是代码更优雅,更容易阅读了
访问枚举的三种方式
from enum import Enum
tax = {
'OR': 0.05,
'WA': 0.10,
'CA': 0.08,
'CN': 0.03
}
StateTax = Enum('tax', tax)
print(StateTax['CN'])
print(StateTax.CN)
print(StateTax(0.03)) #通过 value 来访问
四、元类
【高级货,但基本不会使用到】
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的
一切皆对象,那么用class定义而来的类本身也是一个对象,负责产生该对象的类称之为元类,python内置的元类为type
type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义
>>> def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
要创建一个class对象,type()函数依次传入3个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
当我们使用class关键字创建类的时候,解释器就是在使用type()在进行创建类
一个类没有声明自己的元类时,默认元类为type,也可以通过继承type来自定义元类,使用metaclass关键字参数为一个类指定元类
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
pass
class OldboyTeacher(object,metaclass=Mymeta): # OldboyTeacher=Mymeta('OldboyTeacher',(object),{...})
school='oldboy'
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s says welcome to the oldboy to learn Python' %self.name)
调用Mymeta会先产生一个空对象OldTeacher,然后连同调用Mymeta括号里的参数都传给Mymeta元类下的__init__方法,完成初始化
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __init__(self,class_name,class_bases,class_dic):
# print(self) #<class '__main__.OldboyTeacher'>
# print(class_bases) #(<class 'object'>,)
# print(class_dic) #{'__module__': '__main__', '__qualname__': 'OldboyTeacher', 'school': 'oldboy', '__init__': <function OldboyTeacher.__init__ at 0x102b95ae8>, 'say': <function OldboyTeacher.say at 0x10621c6a8>}
super(Mymeta, self).__init__(class_name, class_bases, class_dic) # 重用父类的功能
if class_name.islower():
raise TypeError('类名%s请修改为驼峰体' %class_name)
if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0:
raise TypeError('类中必须有文档注释,并且文档注释不能为空')
class OldboyTeacher(object,metaclass=Mymeta): # OldboyTeacher=Mymeta('OldboyTeacher',(object),{...})
"""
类OldboyTeacher的文档注释
"""
school='oldboy'
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s says welcome to the oldboy to learn Python' %self.name)
调用t1=OldboyTeacher('egon',18)会做三件事
1、产生一个空对象obj
2、调用__init__方法初始化对象obj
3、返回初始化好的obj
对应着,OldboyTeacher类中的__call__方法也应该做这三件事
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>
#1、调用__new__产生一个空对象obj
obj=self.__new__(self) # 此处的self是类OldoyTeacher,必须传参,代表创建一个OldboyTeacher的对象obj
#2、调用__init__初始化空对象obj
self.__init__(obj,*args,**kwargs)
#3、返回初始化好的对象obj
return obj
class OldboyTeacher(object,metaclass=Mymeta):
school='oldboy'
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s says welcome to the oldboy to learn Python' %self.name)
t1=OldboyTeacher('egon',18)
print(t1.__dict__) #{'name': 'egon', 'age': 18}
属性查找顺序
1、先对象层:OldoyTeacher->Foo->Bar->object 2、然后元类层:Mymeta->type
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
n=444
def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>
obj=self.__new__(self)
self.__init__(obj,*args,**kwargs)
return obj
class Bar(object):
n=333
class Foo(Bar):
n=222
class OldboyTeacher(Foo,metaclass=Mymeta):
n=111
school='oldboy'
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s says welcome to the oldboy to learn Python' %self.name)
print(OldboyTeacher.n)
元类的作用非常强大,它可以改变类的行为,从而改变类的实例化过程。它可以控制类的创建,可以控制类的属性和方法,还可以控制类的继承。
元类还可以用来实现自定义的类型检查,可以用来实现自定义的类型转换,以及实现自定义的类型转换函数。此外,元类还可以用来实现自定义的类型比较,以及实现自定义的元类。