元类
#1、什么是元类
在python中有句话:一切皆对象
所以类也是对象,也可以把类当成普通对象来使用,比如存储到列表中,或者作为参数传给函数等等。。。
对象是通过类的实例化产生的,而类对象是通过元类(type)实例化产生的
对于类而言主要有三个部分组成:
--1、类的名字
--2、类的父类们(基类们)
--3、类的名称空间(类在定义阶段会把类的下面代码走一遍,放到类的名称空间中)
总结:我们在class一个类时,class底层就是调用type来完成类的实例化,并且会把上面的三部分作为参数传进去
元类:用于产生类的类,称之为元类
元类翻译为metaclass,只要看见metaclass就该想起元类,在定义元类时也该尽量添加metaclass,方便阅读
#2、元类的作用
当我们需要高度制定类时,如限制类名必须大写开头等等
就需要使用元类,但是元类type中的代码无法被修改,只能创建新的元类(继承自type),
通过覆盖__init__来完成对类的限制(推荐),也可以通过覆盖__new__完成对类的限制(不推荐)
#3、如何使用元类
# 想要通过元类去限制类或者限制类的实例化主要是下面三个方法
自定义元类只要继承type类就可以
class MyMetaClass(type):
pass
使用自定义的元类
class student(metaclass = MyMetaClass): # 需要在括号里指定一下元类,不然默认元类是type
pass
__init__方法(重点)
实例化对象时会自动执行类中的__init__方法,类也是对象,所以在实例化类对象时会自动执行元类中的__init__方法
执行__init__时需要传入三个参数,__init__(self,class_name,bases,name_dict)
self:类对象本身 class_name:类的名字 bases:类的父类们 name_dict:类的名称空间
class MyMetaClass(type): def __init__(self,class_name,bases,name_dict): # 由于不知道type类中的init具体怎么实现的,所以在这里覆盖完init方法之后调用一下type中的init方法 super().__init__(class_name,bases,name_dict) if not class_name.istitle(): # 如果首字母没有大写,就会通过raise来抛出异常 raise Exception('类名首字母没有大写') #这里可以加很多逻辑来对类进行一些限制 # 会自动调元类中的__init__方法 class teacher(metaclass=MyMetaClass): # 这里类名时小写就会出现异常 Exception: 类名首字母没有大写 pass
__new__
元类中的__new__方法会在创建类对象时执行,并且会先于__init__方法
作用:创建一个类对象
创建类的过程:
1、执行元类中的__new__方法,拿到一个类对象
2、再执行元类中的__init__方法,传入类对象以及其他属性,进行初始化操作
注意:如果覆盖了__new__方法,也一定要调用type中的__new__并返回执行结果(建议指名道姓调用)
使用new方法也可以完成定制类的工作,和init有什么区别
new方法是在init方法之前被调用,而new方法的作用又是产生新的类对象
所以在调用init方法之前对象就已经产生了
所以如果对性能要求高的话,就可以在new方法中完成定制,如果发现问题,直接抛出异常,就不会产生对象了
__call__方法(重点)
元类中的__call__方法会在类实例化产生对象时执行
可以用于控制对象的创建过程
类对象实例化产生对象时会先执行所属类的元类中的call方法
在执行的过程中会接着去执行所属类中的init方法,最终产生一个实例化对象
class MyMetaClass(type): def __call__(self, *args, **kwargs): print(111) obj = super().__call__(*args,**kwargs) print(222) return obj # __call__返回的就是初始化好的对象,返回值是啥,变量接收的就是啥 class Student(metaclass=MyMetaClass): def __init__(self,name): self.name = name print(333) Student('jack') # 111 从打印结果可以看出调用类实例化产生对象是限制性所属类的元类中的__call__方法 # 333 在执行过中会执行类中的__init__方法初始化对象属性 # 222 最后产生一个实例化对象返回
__call__方法产生类的实例化的实现方式
class MyMetaClass(type): def __call__(self, *args, **kwargs): # obj = super().__call__(*args,**kwargs) # 如果我们不写下面具体产生实例化过程的话一定要调用type里面的方法产生实例化对象 obj = self.__new__(self) # 调用下面Person类中的new方法生成一个空对象 obj.__init__(*args,**kwargs) # 初始化对象的属性 return obj # 将实例化完成产生的对象返回给外界 class Person(metaclass=MyMetaClass): def __init__(self,name): self.name = name def save(self): print(2222) p = Person('xxx') p.save() print(p.name) # call里面如果调用下面的初始化方法,这个地方是找不到属性的 print(p.__dict__) """ >>>>: 2222 xxx {'name': 'xxx'} """
注意:__new__ 和 __init__ 是创建类对象时才会执行(两个方法中都可以限制类对象中的属性)
__call__ 类对象要实例化产生对象时才会执行(call中可以限制对象的一些属性,当然所属
类中的init也可以限制,但是产生一个类对象,想要限制就需要在init方法中添加以
下限制,不如直接在他们指定的元类中的 __call__中限制它)
元类实现单例模式
#1、什么是单例
某个类如果只有一个实例对象,那么改类就是单例类
#2、单例的好处
当某个类的所有对象特征和行为完全一样是,避免重复创建对象,浪费资源
class MyMetaClass(type): def __init__(self,class_name,bases,name_list): super().__init__(class_name,bases,name_list) self.obj = None # 为产生的类对象初始化出一个标记 def __call__(self, *args, **kwargs): if not self.obj: # 如果这个值不是一个对象,就调用type中的call方法产生一个对象 obj = super().__call__(*args,**kwargs) self.obj = obj # 并且把生成完的对象赋值给类对象的标记 return self.obj # 如果标记有对象了,就不会调用call方法产生新对象,节省空间 class Student(metaclass=MyMetaClass): def __init__(self,name): self.name = name s1 = Student('jack') # 如果没有进行单例处理,那么每次调用就会产生一个新对象(内存地址不一样) s2 = Student('jack') # 处理完之后调用只会产生一个对象
异常
#1、异常的定义
异常时程序运行过程中发生的非正常情况,是一个错误发生时的信号
异常如果没有被正常处理的话,将导致程序运行被终止,程序终止影响是非常大的,所以我们需要处理异常
处理异常的目的就是提高程序的健壮性
#2、异常的分类
python解释器在执行代码前会先检测语法,语法检测通过才会开始执行代码
--1)语法异常 -->说明语法除了问题,属于低级错误
--2)运行时异常 -->通过语法检测,开始执行代码了,执行过程中发生异常,就是运行时异常
#3、常见的异常以及其他异常
常见异常
""" - AttributeError 试图访问一个对象没有的属性,比如foo.x,但是foo没有属性x - IOError 输入/输出异常;无法打开文件或无法读写 - ImportError 无法引入模块或包;基本上是路径问题或名称错误 - SyntaxError Python语法错误异常,代码不能编译 - IndentationError 缩进异常;代码没有正确缩进 - IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] - KeyError 试图访问字典里不存在的键 - KeyboardInterrupt Ctrl+C被按下 - NameError 使用一个还未被赋予对象的变量 - TypeError 传入对象类型与要求的不符合 - UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量, 导致你以为正在访问它 - ValueError 传入一个调用者不期望的值,即使值的类型是正确的 """
其他异常
""" - BaseException 所有异常的基类 - Exception 常规错误的基类 - StopIteration 迭代器没有更多的值 - ArithmeticError 所有数值计算错误的基类 - AssertionError 断言语句失败 - DeprecationWarning 关于被弃用的特征的警告 - EnvironmentError 操作系统错误的基类 - EOFError 没有内建输入,到达EOF 标记 - FloatingPointError 浮点计算错误 - FutureWarning 关于构造将来语义会有改变的警告 - GeneratorExit 生成器(generator)发生异常来通知退出 - LookupError 无效数据查询的基类 - MemoryError 内存溢出错误(对于Python 解释器不是致命的) - NotImplementedError 尚未实现的方法 - OSError 操作系统错误 - OverflowError 数值运算超出最大限制 - PendingDeprecationWarning 关于特性将会被废弃的警告 - ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象 - RuntimeError 一般的运行时错误 - RuntimeWarning 可疑的运行时行为(runtime behavior)的警告 - StandardError 所有的内建标准异常的基类 - SyntaxWarning 可疑的语法的警告 - SystemError 一般的解释器系统错误 - SystemExit 解释器请求退出 - TabError Tab 和空格混用 - UnicodeDecodeError Unicode 解码时的错误 - UnicodeEncodeError Unicode 编码时错误 - UnicodeError Unicode 相关的错误 - UnicodeTranslateError Unicode 转换时错误 - UserWarning 用户代码生成的警告 - Warning 警告的基类 - ZeroDivisionError 除(或取模)零 (所有数据类型) """
异常的组成
a = int("he") """ Traceback (most recent call last): (追踪信息) File "F:/pyprogram/day29/part2/test1.py", line 42, in <module> 具体在什么文件几行在什么里 a = int("he") ValueError: invalid literal for int() with base 10: 'he' 错误类型 具体错误原因 """
Traceback: 是异常追踪信息 用于展示错误发生的具体位置,以及调用的过程
其中包括了 错误发生的模块、文件路径、行号、函数名称、具体代码
最后一行:前面表示的是错误的类型
后面表示的是错误的详细信息,在查找错误时,主要参考的就是详细信息
异常处理
异常发生后,如果不正确处理将导致程序终止,我们必须应该尽量避免这种情况发生
#1、重点语法
try:
可能会出现异常的代码,放到try里面
except 具体异常类型 as e: # -->在这给异常类型起了别名,直接打印这个e的话就会把具体错的信息打印出来
如果真的发生异常就执行except里面的代码
#2、如何正确处理异常
--1、当发生异常,不是立马加try 要先找出错误的原因并解决它
--2、try仅在 即使你知道为什么发生错误,但你却无法避免的时候使用
例如:你明确告诉对方需要一个正确的文件路径,然而用户依然传入了错误了路径
总结:能不用try就不用try,try只在一些不可控因素的时候使用
#3、自定义异常类
当系统提供的异常类并不能准确描述错误原因的时候,我们可以自定义异常类
异常类只要继承Exception或者BaseException,然后里面pass即可
class MyException(Exception):
pass
#4、主动抛出异常
什么时候需要我们主动抛出异常
当我们做功能的提供者,给外界提供一个功能接口,但是使用者不按照想用的方式来使用,
或者参数类型不正确等一些原因,导致功能无法正常执行时,就应该主动抛出异常
语法:
raise关键字,后面可以跟任何Exception的子类或者时对象
raise Exception
raise Exception(“错误的具体原因”)
#5、断言assert
断言即为断定的意思,即非常肯定某个条件是成立的
条件判断成立可以通过if判断,assert存在的目的就是简化if判断
assert 后面跟上代码,如果正常就往下走,如果异常就直接抛出异常
(不建议使用,抛出的异常看不出具体的错误信息)