day29 元类及异常处理
元类及异常处理
元类
什么是元类
在python
中,一切皆对象,对象是由类产生的,那么类是不是对象呢?
举例:
class A:
pass
print(type(A))
# <class 'type'>
从上例中可以看出,在python
中,类也是对象,是由某个兑现实例化来的,而且这个类的名字叫做type
,那么这个类又是什么呢?这个类就是元类
元类就是产生类的的类
实例化类对象
普通的类是由元类实例化产生的,那么我们如何按照普通类的方式去生成类对象呢?
由于定义类时会使用class
关键字,实际上class
关键字就帮我们做了实例化的过程
p = type() # 通过元类实例化一个普通的类对象
# TypeError: type() takes 1 or 3 arguments
经过上方的代码测试,发现会报错,错误信息为缺少一个或者三个参数,我们来看一下源代码
class type():
def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
"""
type(object_or_name, bases, dict)
type(object) -> the object's type
type(name, bases, dict) -> a new type
# (copied from class doc)
"""
pass
在python中的源代码信息如上,通过上述信息可以发现,在我们使用type元类想要创建一个类对象时需要传入一个或者三个参数:
传入一个参数时:返回实例化这个对象的类
传入三个参数时分别是:
name
:创建的类对象的名称,字符串类型bases
:创建的这个类对象的父类们,tuple类型dict
:这个类对象的名称空间,dict类型
那么我们传入这三个参数进行试验:
p = type("Aclass",(object,),{"a":10})
print(type(p)) # <class 'type'>
通过上方的测试代码我们创建了一个类对象,并且将这个对象的地址赋值给了p
总结
在
python
中,一切皆对象,类也是对象,普通类是由元类实例化产生的类对象元类是用来产生类的类,英文为
metaclass
我们可以通过
type(class_name,bases,dict)
自己实例化元类的类对象
元类可以做什么?
元类的主要作用
自定义类对象时使用
限制类对象的实例化过程时使用
如何使用元类?
1、定义类对象的使用过程__init__
方法(重点)
# 需求:在定义类时类名必须大写,类中的属性及方法必须小写
class MyMetaClass(type):
def __init__(self,class_name,bases,dict):
if not class_name.istitle():
raise Exception("类名必须为大驼峰命名法")
for k in self.__dict__:
if not k.islower():
raise Exception("属性及方法名必须小写")
super().__init__(class_name,bases,dict)
class person(metaclass=MyMetaClass): # 将person的元类指定为 MyMetaClass
pass
# raise Exception("类名必须为大驼峰命名法")
# Exception: 类名必须为大驼峰命名法
class Person(metaclass=MyMetaClass):
def My(self):
pass # raise Exception("属性及方法名必须小写")
# Exception: 属性及方法名必须小写
通过上述的方法,我们可以高度定义类对象的实例化,从而限制类的定义
与普通的实例化相同,在进行对象的实例化时会调用__init__
方法,并执行,此时只要在实例化过程中进行限制就可以定制化类对象的实例化
2、类中的__new__
方法(用的比较少)
在以前说过,类的实例化会进行两步:
产生一个空的对象
对产生的空对象进行初始化
元类的实例化产生类对象与普通的类进行实例化相似,也会经过这两个步骤
使用__init__
方法可以进行初始化过程,那么如何产生空的对象呢?
使用__new__
方法可以创建空的对象
# __new__的源代码解释
@staticmethod # known case of __new__
def __new__(*args, **kwargs): # real signature unknown
""" Create and return a new object. See help(type) for accurate signature. """
pass
上述的源代码显示
__new__
方法为静态方法(非绑定方法)__new__
方法是用来创建一个空对象并将其返回
那么其中的*args
、**kwargs
是什么呢?
class MyMetaClass(type):
def __new__(cls, *args, **kwargs):
print(cls) # <class '__main__.MyMetaClass'>
print(args) # ('A', (), {'__module__': '__main__', '__qualname__': 'A',"a":10})
print(kwargs) # {}
class A(metaclass=MyMetaClass):
a = 10
通过上述方法可以看见:
其中
cls
为MyMetaClass
类args
中的值为使用元类进行创建对象时传入的三个参数kwargs
为创建对象时传入的关键字参数
通过类的实例化过程我们可以知道,会首先执行__new__
方法,然后再执行__init__
方法
在__new__
中也会将对象的名称空间进行传入,那么是否可以在__new__
方法中进行定制化呢?
class MyMetaClass(type):
def __new__(cls, *args, **kwargs):
if not args[0].istitle():
raise Exception("类名必须为大驼峰命名法")
for k in args[2].values():
if not k.islower():
raise Exception("属性及方法名必须小写")
return type.__new__(cls,*args,**kwargs)
class A(metaclass=MyMetaClass):
B = 10
# Traceback (most recent call last):
# File "H:/py8-study/practice/day29/元类的引出.py", line 69, in <module>
# class A(metaclass=MyMetaClass):
# File "H:/py8-study/practice/day29/元类的引出.py", line 66, in __new__
# raise Exception("属性及方法名必须小写")
# Exception: 属性及方法名必须小写
通过上述__new__
与__init__
的学习,产生了以下几个疑问:
1、__init__
与__new__
有什么区别呢?
__init__
方法是在创建了类对象后初识化时执行的__new__
方法是在创建对象时执行
2、在__new__
中也可以进行类的定制化,那么在实现定制化时应该使用__init__
方法还是__new__
方法呢?
在进行定制化时两者都可以使用,按照合理的思维可以是对类对象的名字进行限制或定制化时,应该使用
__new__
方法,在不符合条件时,就不让类进行实例化,如果对类对象的属性及方法进行限制或定制化时使用
__init__
方法,只有符合条件时才能进行初始化但是推荐的是使用
__init__
方法,因为__init__
方法的使用较为简单在使用
__new__
创建对象时必须讲对象进行返回,而创建对象的方法在type
类中,所以每次必须调用type.__new__(cls,*args,**kwargs)
,并将其返回
3、有了__new__
为什么还要使用__init__
方法呢?
虽然在元类创建类对象时看着两者的方法作用没什么区别,但是在创建普通的对象时区别很大,
__new__
就是用来产生空对象的,__init__
就是用来产生初始化对象的,两者的功能交叉很小,因为在实例化普通对象时没有名称空间的字典。在元类实例化类对象时虽然看着两者功能很相似,但是不同的方法有不同的职责,在进行创建对象时就使用
__new__
方法,进行对象初始化时就使用__init__
方法
3、__call__
方法
我们在进行程序编写时经常会出现 以下错误:
class A:
b = 10
def func(self):
pass a = A()
a.func()
a.b() # Traceback (most recent call last):
# File "H:/py8-study/practice/day29/元类的引出.py", line 82, in <module>
# a.b()
# TypeError: 'int' object is not callable
我们执行上述程序时,调用func
函数时就不会报错,但是调用b()
时就会报错,这是为什么的?
这时因为在对象类时有一个__call__
方法进行控制,在进行调用时会执行类的__call__
方法
在上述例子中,由于func
是function
类实例化的对象,而b
是int
类实例化的对象,而在func
中会将函数的执行结果进行返回,而在int
中会抛出异常
那么__call__
在什么时候会被调用呢?
在用类进行实例化对象时会被调用
如何使用__call__
进行控制实例化对象的过程
class MyMeta(type):
def __call__(self, *args, **kwargs):
print(self)
print(args)
print(kwargs) class A(metaclass=MyMeta):
def __init__(self,name,age):
self.age = age
self.name = name # 在此处执行时,不添加下方代码不会打印 a = A("lee",18)
# <class '__main__.A'>
# ('lee', 18)
# {}
从上述代码中可以看出,只有在实例化过程中才会调用__call__
方法
并且调用者时A
,其参数就是实例化时输入的参数
因为在A
类对象中没有__call__
方法,所以继续寻找他的类MyMeta
,并使用其中的__call__
方法
那么我们就可以使用这个特性将元类中的__call__
方法进行继承并自定义,增加我们的限制等
下方的单例模式就是这个的应用方式之一
单例设计模式
当我们设计的类只希望产生一个对象时就要用到单例设计模式
class SingleMetaClass(type):
def __init__(self,name,bases,dic):
self.obj = None
def __call__(self, *args, **kwargs):
if self.obj:
return self.obj
obj = type.__call__(self,*args,**kwargs)
self.obj = obj
return self.obj
class Student(metaclass=SingleMetaClass):
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print("my name is %s"%self.name)
s1 = Student("lee",18)
print(s1)
s2 = Student("lee",18)
print(s2)
class Teacher(metaclass=SingleMetaClass):
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print("my name is %s"%self.name)
t = Teacher("lee",18)
print(t)
# <__main__.Student object at 0x000001F8D96AD320>
# <__main__.Student object at 0x000001F8D96AD320>
# <__main__.Teacher object at 0x000001F8D96AD3C8>
异常
什么是异常
异常是程序运行过程中发生的非正常情况,是一个错误发生时的信号
异常如果没有被正确处理的话,将导致程序被终止,这对于用户体验是非常差的,可能导致严重的后果
处理异常的目的就是提高程序的健壮性
异常的分类
python解释器在执行代码前会先检查语法,语法检查通过才会开始执行代码
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' 不存在这个key
FileNotFoundError: [Errno 2] No such file or directory: 'xxxxx' 文件找不到
异常的组成:
Traceback (most recent call last):
File "F:/python8期/课堂内容/day29/11.常见异常.py", line 22, in <module>
with open("xxxxx") as f:
FileNotFoundError: [Errno 2] No such file or directory: 'xxxxx
Traceback 是异常追踪信息 用于展示错误发生的具体位置 以及调用的过程
其中 包括了 错误发生的模块 文件路径 行号 函数名称 具体的代码 最后一行 前面是错误的类型
后面 错误的详细信息 在查找错误时 主要参考的就是详细信息
异常处理
异常发生后 如果不正确处理将导致程序终止,我们必须应该尽量的避免这种情况发生
重点:
必须掌握的语法
语法: try: 可能会出现异常的代码 放到try里面 except 具体异常类型 as e: 如果真的发生异常就执行except
如何正确处理异常
当发生异常 不是立马加try 要先找出错误原因并解决它
try 仅在 即使你知道为什么发生错误 ,但是你却无法避免 例如 你明确告诉用户 需要一个正确文件路径 然而用户依然传入了错误的路径
如 socket 双方都要使用管道 ,但是如果一方有由于某些原因强行关闭了 ,即使你知道原因也无法避免出错 那就只能try 保证程序正常结束
总结一句话:能不加try 就不加try
自定义异常类
当系统提供异常类不能准确描述错误原因时 就可以自定义异常类
继承自Exception即可
class MyException(Exception):
pass
主动抛出异常:
什么时候需要主动抛出异常
当我们做功能的提供者,给外界提供一个功能接口
但是使用者不按照相应的方式来使用,或者参数类型不正确等原因,导致功能无法正常执行时,就应该主动抛出异常
主动抛出异常使用raise 关键字
后面可以跟任何Exception的子类 或是 对象
raise MyException
raise MyException("错误具体原因!")
断言assert
断言 其实可以理解为断定的意思
即非常肯定某个条件是成立的
条件是否成立其实可以使用if来判断
其存在的目的就是 为了简化if 判断而生的
day29 元类及异常处理的更多相关文章
- 面向对象:元类、异常处理(try...except...)
元类: python中一切皆对象,意味着: 1. 都可以被引用,如 x = obj 2. 都可以被当做函数的参数传入 3. 都可以被当做函数的返回值 4. 都可以当做容器类的元素(列表.字典.元祖.集 ...
- day28元类与异常查找
元类与异常处理1. 什么是异常处理 异常是错误发生的信号,一旦程序出错就会产生一个异常,如果该异常 没有被应用程序处理,那么该异常就会抛出来,程序的执行也随之终止 异常包含三个部分: ...
- Python异常处理及元类
一.异常处理 异常是错误发生的信号,一旦程序出错就会产生一个异常,如果该异常没有被应用程序处理,那么该异常就会跑出来,程序的执行也随之终止,也就是说异常就是一个事件,该事件会在程序执行过程中发生,影响 ...
- Python 29 异常处理, 元类
所学内容 异常处理(常用) AttributeError ·························· 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性xIOError ··· ...
- python day 11: 类的补充,元类,魔法方法,异常处理
目录 python day 11 1. 类的补充 1.1 通过反射来查找类,创建对象,设置对象的属性与方法 1.2 类的魔法方法:getitem,setitem 1.3 元类__metaclass__ ...
- DAY29、元类
一.eval内置函数eval内置函数的使用场景: 1.执行字符串会得到相应的执行结果 2.一般用于类型转换,得到dict.list.tuple例: dic_str = ''{'a':1,'b':2}' ...
- day29 二十九、元类、单例
一.eval.exec内置函数 1.eval函数 eval内置函数的使用场景: ①执行字符串会得到相应的执行结果 ②一般用于类型转换得到dict.list.tuple等 2.exec函数 exec应用 ...
- python-异常处理、元类
一.异常处理 1.异常处理介绍: 异常是错误发生的信号,一旦程序出错就会产生一个异常,如果该异常没有被应用程序处理,那么该异常就会被抛出来,程序执行随之停止 2.异常通常包含三个部分 1.traceb ...
- day 28-1 元类
元类 元类的用途:自定义元类控制类的创建行为及类的实例化行为 Python 中一切皆为对象. 一切接对象,对象可以怎么用呢? 1.都可以被引用,x=obj 2.都可以当作函数的参数传入 3.都可以当作 ...
随机推荐
- CI环境搭建下-Jenkis与git结合
设置权限: 也可以通过公私钥的方式,添加权限,公私钥填写在gitblit用户中心: Jenkins中填写私钥: 添加: 添加后如果仍然报错,是因为windows下要使用http的地址. 在此,可 ...
- react-native-page-listview使用方法(自定义FlatList/ListView下拉刷新,上拉加载更多,方便的实现分页)
react-native-page-listview 对ListView/FlatList的封装,可以很方便的分页加载网络数据,还支持自定义下拉刷新View和上拉加载更多的View.兼容高版本Flat ...
- Hdu 2047 Zjnu Stadium(带权并查集)
Zjnu Stadium Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total ...
- 在windows使用gvim的感受
用新下载的gvim写几行代码习惯一下,感觉vim用起来要比atom占用的内存少多了,更加的便捷.由于之前一直在用sublime text2,虽然我也很喜欢ST,但我还是抱着膜拜的心态来试了试gvim, ...
- 下载 OllyDbg
http://www.ollydbg.de/
- HDU 2176 取(m堆)石子游戏 —— (Nim博弈)
如果yes的话要输出所有情况,一开始觉得挺难,想了一下也没什么. 每堆的个数^一下,答案不是0就是先取者必胜,那么对必胜态显然至少存在一种可能性使得当前局势变成必败的.只要任意选取一堆,把这堆的数目变 ...
- 关于keepalive
linux内核配置有一项tcp_keepalive_time,即tcp的保活定时器.当网络上两个建立连接的进程都没有数据向对方发送的时候,tcp会隔段时间发送一次保活数据,以保持连接,间隔时间就是tc ...
- CISCO实验记录三:CDP邻居发现
一.CDP邻居发现要求 1.识别二层连接 2.识别CDP邻居 二.CDP邻居发现操作 1.CDP邻居发现 #interface gigabitEthernet 0/0/0 //启动端口 #no shu ...
- 用gcov来检查Qt C++程序的代码覆盖率
最近才发现MinGW里面包含一个叫做gcov的工具,可以用来检查你的程序运行时调用了哪些代码,同时显示代码行被调用的次数.这个功能在代码的覆盖率和性能调优方便都能用上. 我的运行环境 Window ...
- Python可变参数函数用法详解
来自:http://c.biancheng.net/view/2257.html 很多编程语言都允许定义个数可变的参数,这样可以在调用函数时传入任意多个参数.Python 当然也不例外,Python ...