元类的介绍:
1.什么时元类?
在python中,一切皆对象,类也是对象,可以把一个类当成普通对象来使用,比如存储到列表中,或者作为参数传给函数等等。
对象时如何产生的?
通过类实例化产生的
类对象是由type实例化产生的
obj = type("TestClass",(object,),{})print(obj) #
一个类由三个部分组成:
1.类名,2.类的基类(object) 3. 类的名称空间
而使用type(类名,继承的父类,名称空间字典) 可以获得一个新的类。
所以,总结出来,当定义一个class时,解释器会自动调用type来完成类的实例化
案例:
# 模拟解释器创建类的过程obj = type('TeatClass', (object,), {})print(obj) #得到一个类# 名称空间也可以放实际的函数def test1(a): print(a)def test2(self, b): print(self, b)class_name = 'C'bases = (object,)name_dict = { 'name': 'jack', 'test1': test1, 'test2': test2} # 可以把函数作为名称空间字典C = type(class_name, bases, name_dict) # 创建一个新的类,c1 = C()c1.test2(100) # 可以通过点语法来调用定义的方法。
exec与eval
exec:执行储存在字符串或文件中的 Python 语句,相比于 eval,exec可以执行更复杂的 Python 代码。
语法:exec(object, globals, locals )
object : 必选参数,表示需要被指定的Python代码。它必须是字符串或code对象。如果object是一个字符串,该字符串会先被解析为一组Python语句,然后在执行(除非发生语法错误)。如果object是一个code对象,那么它只是被简单的执行。
globals:可选参数,表示全局命名空间(存放全局变量),如果被提供,则必须是一个字典对象。
locals:可选参数,表示当前局部命名空间(存放局部变量),如果被提供,可以是任何映射对象。如果该参数被忽略,那么它将会取与globals相同的值。
返回值
exec 返回值永远时None
语法:
glob = {}locl = {}code = '''def test(a): print(a)'''print(exec(code,glob,locl))
eval: 用来执行一个字符串表达式,并返回表达式的值,但是不能由任何特殊的语法
元类:metaclass
用于产生类的类,称之为元类
元类翻译为:metaclass , 所以一般在定义元类时,尽量在类名后面添加MetaClass,方便阅读
元类的作用:
当我们需要高度定制类时,比如限制类名,或着属性的名字等等。就需要使用元类,不过因为原类type中的代码无法被修改,所以我们一般创建新的元类方法是使用继承自type的子类,通过覆盖__init__来完成对类的限制
自定义元类的语法:
# 默认创建类时,是找的type来实例化的class MyMetaClass(type): pass# 使用metaclass关键字来定义原类的继承class Person(metaclass=MyMetaClass): passclass Student: def __init__(self): passprint(type(Person)) #print(type(Student)) # # 创建对象也是一样,会先创建空的类对象,再调用__init__方法# Person = MyMetaClass()
1.__init__方法*****(重点)
实例化对象时会自动执行类中的__init__方法,类也是对象,在实例化类对象时会自动执行元类中的__init__方法
并且传入类的三个必要参数:类的名字,继承的父类们,名称空间
会自动传入类对象本身作为第一个参数(self)
自定义原类案例:判断类名首字母是否为大写,判断类中的方法名称是否都为小写
class MyMetaClass(type): def __init__(self, class_name, bases, name_dict): # 元类中的self表示的都是类对象 # 不要忘记调用父类的初始化 super().__init__(class_name, bases, name_dict) print(name_dict) # 判断类名,必须首字母大写,否则直接抛出异常 if not class_name.istitle(): print('类名首字母必须大写') raise Exception # 控制类中方法名必须全部小写 # 遍历类中的名称空间,如果发现方法有大写,则报错 for k in name_dict: if str(type(name_dict[k])) == "": if not k.islower(): print('方法名称必须全小写') raise Exception# 会自动调用其元类中的__init__方法传入 类对象本身 类名称 父类们 名称空间class Student(object, metaclass=MyMetaClass): # # MyMetaClass("Student",(object,),{}) NAME = 10 def say(self): print('SAY') pass
案例:需求:要求每个类必须包含__doc__属性,__doc__用于访问一个对象的注释信息
# 如果你要控制类的创建,那就自定义元类 覆盖__init__class DocMeatClass(type): def __init__(self,class_name,bases,name_dict): super().__init__(class_name,bases,name_dict) if not self.__doc__: raise Exceptionclass Person(metaclass=DocMeatClass): pass
2.__new__方法*****(重点)
元类中的new方法会创建类对象时执行,并且先于init方法,它的作用是创建一个类对象
执行步骤:1.执行__new__方法,拿到一个类对象, 2. 执行__init__方法,传入类对象以及其他属性,进行初始化
注意:定义类的时候,本身有一个默认的__new__,如果覆盖了__new__一定也要调用type中的__new__并返回执行结果,不然无法定义类
使用new方法也可以完成定制类的工作,和init有什么区别?
在调用__init__方法前,类对象已经创建完成了,所以如果对性能要求高的话,可以选择在__new__中完成定制,如果发现有问题,就不用创建类对象了
语法:
class MyMetaClass(type): # 传入类的三大组成部分 def __init__(self, class_name, bases, name_dict): super().__init__(class_name, bases, name_dict) print('init') # 该方法会在实例化类对象时自动调用并且优先在__init__之前调用 # 其作用是用于创建新的类对象的 # 注意这里必须调用type类中的__new__否则将无法产生类对象 并且返回其结果 def __new__(cls, *args, **kwargs): # cls 表示元类自己,即MyMetaClass return type.__new__(cls, *args, **kwargs) # 类本身有一个__new__方法,如果覆盖__new__一定要将__new__返回class Person(metaclass=MyMetaClass): passprint(Person) # None 如果__new__后面没有 return type.__new__(...),则返回None,代表没有生成类对象 # 反之,则
# 就算__init__中什么都不写 这个类对象其实已经创建完成了 该有的属性都有了 # 这是与普通类不同之处
3.__call__方法*****(重点)
元类中的__call__方法会在调用类时执行,可以用于控制对象的创建过程
总结:当你要定制类时,就自定义元类并覆盖__init__方法
案例:使传入的字符串全部改为大写
class MyMeta(type): # 获得某个类的实例 def __call__(self, *args, **kwargs): new_args = [] for i in args: if isinstance(i, str): new_args.append(i.upper()) else: new_args.append(i) return super().__call__(*new_args, **kwargs)# 注意注意注意!!!__new__ __init__是创建类对象时还会执行# __call__类对象要产生实例时执行class Student(metaclass=MyMeta): def __init__(self, name, gender, age): self.name = name self.gender = gender self.age = ages= Student('jack','man',18)print(s.age)class Person(metaclass=MyMeta): def __init__(self,name,gender): self.name = name self.gender = genderp = Person('rose','woman')print(p.gender)
元类实现单例模式
什么是单例:
某个类如果只有一个实例对象,那么该类成为单例类
单例的好处:
当某个类的所有对象特征和行为完全一样时,避免重复创建对象,浪费资源,如果是避免重复创建对象
案例:
# 实例化传入相同参数,得到的实例地址不同,浪费资源class People: def __init__(self, name, age): self.name = name self.age = agep1 = People('李果',25) # <__main__.People object at 0x000002A28FC38400>p2 = People('李果',25) # <__main__.People object at 0x000002A28FC389B0>p3 = People('李果',25) # <__main__.People object at 0x000002A28FC38AC8>print(p1)print(p2)print(p3)
单列需要与__call__一起使用,在__call__内部加判断
案例:
class SingletonMetaClass(type): # 创建类时会执行init 在这为每个类设置一个obj属性,默认为None def __init__(self, a, b, c): super().__init__(a, b, c) self.obj = None # d当类要创建对象时会执行,该方法 def __call__(self, *args, **kwargs): # 判断这个类,如果已经有实例了就 直接返回,从而实现单例 if self.obj: return self.obj # 没有则创建新的实例并保存到类中 obj = type.__call__(self, *args, **kwargs) self.obj = obj return objclass Person(metaclass=SingletonMetaClass): def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def say(self): print('my name is %s my age is %s my gender is %s' % self.name,self.age,self.gender)stu1 = Student("布兰", 16, "man")stu2 = Student("布兰", 16, "man")stu3 = Student("布兰", 16, "man")print(stu1, stu2, stu3) # 地址完全一样
案例二:生成单例播放器
class Single(type): def __init__(self, a, b, c): super().__init__(a, b, c) self.obj = None def __call__(self, *args, **kwargs): if self.obj: return self.obj obj = type.__call__(self, *args, **kwargs) self.obj = obj return objclass QQPlayer(metaclass=Single): def __init__(self, voice_value, repeat=False): self.voice_value = voice_value self.repeat = repeat def play(self, file_path): if hasattr(self, 'file_path'): self.stop() print('正在播放%s' % file_path) self.file_path = file_path def stop(self): print('%s停止播放' % self.file_path)playerl = QQPlayer(100,True)playerl.play('如果我是DJ.mp3')player2 = QQPlayer(100,True)player2.play('你会爱我吗.mp3')player3 = QQPlayer(100,True)player3.play('我的滑板鞋.mp3')
异常
1.什么是异常
异常是程序运行过程中发生的非正常情况,是一个错误发生时的信号,异常如果没有被正常处理的话,将导致程序被终止,这对于用户体验是非常差的,可能导致严重的后果。处理异常的目的就是提高程序的健壮性
2.异常的分类
TypeError: 'int' object is not subscriptable 对象不能被切片 TypeError: 'list' object is not callable 对象不能被调用IndexError: list index out of range 索引超出范围TypeError: 'builtin_function_or_method' object is not iterable 对象不能被迭代KeyError: 'xxx' 不存在这个keyFileNotFoundError: [Errno 2] No such file or directory: 'xxxxx' 文件找不到
3.异常的组成
Traceback (most recent call last): File "F:/python8期/课堂内容/day29/11.常见异常.py", line 22, inwith open("xxxxx") as f:FileNotFoundError: [Errno 2] No such file or directory: 'xxxxx'
Traceback : 是异常追踪信息,用于展示错误发生的具体位置,以及调用的过程,其中包括了错误发生的 模块 , 文件路径 ,行号 ,函数名称 ,具体的代码
最后一行: 前面是错误的类型,后面是错误的详细信息,在查找错误时,主要参考的就是详细信息
4.异常的处理
异常发生后,如果不正确处理将会导致程序终止,所以我们应该尽量的避免这种情况发生
必须掌握的语法:
try:
可能会出现异常的代码,放到try里面
except 具体的类型 as e:
如果真的发生异常就会执行except
5.如何正确处理异常
1.当发生异常时,不是立马加try, 要先找出错误原因并解决它
2.try 仅在 即使你知道为什么发生错误,但是你却无法避免
例如,你明确告诉用户需要一真确的文件路径,然而用户依然传入了错误的路径
如 socket 双方都要使用管道,但是如果以访由于某些原因强行关闭了,即使你只要原因也无法避免出错,那就只能try 保证程序正常结束
能不加try 就不加 try
6.自定义异常类:
当系统提供异常类不能准确描叙错误原因时,就可以自定义异常类,继承自Exception即可
class MyException(Exception) :
pass
7.主动抛出异常
什么时候需要主动抛出异常?
当我们做功能的提供者,给外界提供一个功能接口
但是使用者不按照相应的方式来使用,或者参数类型不正确等原因,导致功能无法正常执行时,就应该主动抛出异常
主动抛出异常使用 raise 关键字
后面可以跟任何Exception的 子类 或是 对象。
raise MyException
raise MyException("错误具体原因!")
8.断言assert
断言,其实可以理解为断定的意思
即非常肯定某个条件是成立的
条件是否成立其实可以使用 if 来判断
其存在的目的就是为了简化 if 判断而生的