第8天,面向对象进阶
@(python)[笔记]
目录
一、isinstance()和issubclass()
1. isinstance()
2. issubclass()
二、反射
1. hasattr()
2. getattr()
3. setattr()
4. delattr()
5. 扩展用法
三、__setattr__、__delattr__、__getattr__
四、二次加工标准类型(包装)
授权
五、__next__和__iter__实现迭代器协议
六、__doc__
七、__module__和__class__
八、__str__
九、__del__析构方法
十、__setitem__,__getitem__,__delitem__
十一、__enter__和__exit__
十二、__call__
十三、元类metaclass
先来看看exec
未完
一、isinstance()和issubclass()
1. isinstance()
语法:isinstance(obj,cls)
功能:检查对象obj是否是类cls的实例
class foo:
pass
obj = foo()
print(isinstance(obj,foo)) #True
2. issubclass()
语法:issubclass(sub, super)
功能:检查sub类是否是super类的派生类(子类)
class foo:
pass
obj = foo()
print(isinstance(obj,foo)) #True
class bar(foo):
pass
print(issubclass(bar,foo)) #True
二、反射
反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,也可以称为自省。
python面向对象中的反射是指通过字符串的形式操作对象相关的属性。python中的一切事物都是对象,都可以使用反射。
python中可以实现自省的四个函数:
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
1. hasattr()
语法:hasattr(object,"name")
功能:判断object中有没有一个name字符串对应的方法或属性。
用法:
class foo:
name = "egon"
pass
obj = foo()
print(hasattr(obj,"name")) #True
print(hasattr(obj,"func")) #True
print(hasattr(foo,"name")) #True
print(hasattr(foo,"func")) #True
2. getattr()
语法:getattr(object, "name"[, default=None])
功能:从对象object获取一个name字符串对应的方法,如果没有name字符串对应的方法,并且没有设置default,则报错;如果没有name字符串对应的方法,设置了default,则将default的值返回。getattr(object,"name")
等价于object.name
class foo:
name = "egon"
def func(self):
print("Hello world")
obj = foo()
print(getattr(obj,"func"))
#>
#可以看出,输出结果是一个绑定方法
f1 = getattr(obj,"func")
f2 = getattr(obj,"bar","不存在")
f1() #Hello world
print(f2) #不存在
3. setattr()
语法:setattr(x, y, v)
功能:设置属性,setattr(x,'y',v)
等价于“x.y = v
”
用法:
class foo:
name = "egon"
def func(self):
print("Hello world")
obj = foo()
setattr(obj,"age",20)
setattr(obj,"show_name",lambda self:self.name+"_NB")
print(obj.__dict__) #查看属性字典
print(obj.show_name(obj))
'''
输出:
{'age': 20, 'show_name': at 0x000000000115E2F0>}
egon_NB
'''
4. delattr()
语法:delattr(x, y)
功能:删除属性,delattr(x,'y')
相当于```del x.y`''
用法:
delattr(obj,"show_name")
# delattr(obj,"sex") #不存在,则报错
print(obj.__dict__) #{'age': 20}
5. 扩展用法
反射当前模块
import sys
def s1():
print("s1")
def s2():
print("s2")
this_module = sys.modules[__name__]
print(hasattr(this_module,"s1")) #True
print(getattr(this_module,"s2"))
#,加括号可执行
导入其他模块,利用反射查找该模块是否存在某个方法
程序目录:
- module_test.py
- current.py
# module_test.py
def test():
print("from the module_test.test")
#current.py
import module_test as mt
print(hasattr(mt,"test")) #True
f = getattr(mt,"test","不存在")
f() #from the module_test.test
三、__setattr__
、__delattr__
、__getattr__
__setattr__
:添加/修改属性会触发它的执行;
__delattr__
:删除属性的时候会触发;
__getattr__
:只有在使用点调用属性且属性不存在的时候才会触发。
class Foo:
x = 1
def __init__(self,y):
self.y = y
def __getattr__(self, item):
print("---> 你找的属性不存在")
def __setattr__(self, key, value):
print("---> from __setattr__")
# self.key = value #这样会陷入无限递归,只能通过__dict__字典进行赋值
self.__dict__[key] = value #这样才可以正确赋值
def __delattr__(self, item):
print("---> from __delattr__")
f1 = Foo(10) #相当于设值,触发__setattr__执行,---> from __setattr__
print(f1.__dict__)
#{},直接打印为空,是因为你自己重写了__setattr__方法,
# 而你在__setattr__方法中没有真正赋值
f1.z #触发__getattr__执行,---> 你找的属性不存在
del f1.x #触发__delattr__执行,---> from __delattr__
四、二次加工标准类型(包装)
包装:python为用户提供了标准数据类型,以及丰富的内置方法,其在很多场景下,我们需要基于标准数据类型来定制我们自己的数据类型,新增 / 改写方法,这就用到继承和派生的知识,其他标准类型均可以通过下面的方式进行二次加工。
示例1:对list
进行二次加工,限制append只能增加int
整型数据;并且增加mid
方法,得到列表的中间值;其余方法都继承list
的。
class List(list):
def append(self,p_object):
#派生出自己的append方法,会覆盖父类list中的append方法
if not isinstance(p_object,int):
raise TypeError("%s must be int"%p_object)
super(List, self).append(p_object)
@property
#中间值听起来更像一个属性,而非方法,所以使用property
def mid(self):
mid_num = len(self) // 2
return self[mid_num]
l = List([1,2,3,4])
print(l) #[1, 2, 3, 4]
l.append(5)
print(l) #[1, 2, 3, 4, 5]
print(l.mid) #中间值3
示例二:为list
的clear
方法增加权限
class List(list):
def __init__(self,item,perm=False):
super(List, self).__init__(item)
self.perm = perm
#先设定一个默认的权限
def clear(self):
if not self.perm:
raise PermissionError("权限拒绝")
super(List, self).clear()
l = List([1,2,3])
print(l) #[1, 2, 3]
# l.clear() #抛出“权限拒绝”的异常
l = List([1,2,3],True) #给一个授权参数为True
l.clear()
print(l) #[],可以正常清空列表
授权
授权是包装的一个特性,包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建、修改或删除原有产品的功能,其它的则保持原样。授权的过程就是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__
方法
示例三:利用open()
函数重新定制一个文件处理器,增加写内容添加时间的功能;
import time
class FileHandler:
def __init__(self,filename,mode='r',encoding="utf-8"):
self.file = open(filename,mode,encoding=encoding)
#self.file获取到一个文件句柄
def write(self,line):
t = time.strftime("%Y-%m-%d %X")
self.file.write("%s %s"%(t,line))
def __getattr__(self, item):
return getattr(self.file,item)
#当对象调用FileHandler类不存在的方法时,会返回open()函数的item字符串对应的方法;
f1 = FileHandler("a.txt","r+")
f1.write("你好吗\n")
f1.seek(0)
print(f1.tell()) #0
示例四:
#我们来加上b模式支持
import time
class FileHandle:
def __init__(self,filename,mode='r',encoding='utf-8'):
if 'b' in mode:
self.file=open(filename,mode)
else:
self.file=open(filename,mode,encoding=encoding)
self.filename=filename
self.mode=mode
self.encoding=encoding
def write(self,line):
if 'b' in self.mode:
if not isinstance(line,bytes):
raise TypeError('must be bytes')
self.file.write(line)
def __getattr__(self, item):
return getattr(self.file,item)
def __str__(self):
if 'b' in self.mode:
res="<_io.BufferedReader name='%s'>" %self.filename
else:
res="<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" %(self.filename,self.mode,self.encoding)
return res
f1=FileHandle('b.txt','wb')
# f1.write('你好啊啊啊啊啊') #自定制的write,不用在进行encode转成二进制去写了,简单,大气
f1.write('你好啊'.encode('utf-8'))
print(f1)
f1.close()
示例五:利用授权的方式重新定制list
的append
方法,只能往列表中添加int
整型数据;与示例一作对比,看一下两者的区别。
#授权,定制list
class List:
def __init__(self,seq):
self.seq = list(seq)
def append(self,p_object):
if not isinstance(p_object,int):
raise TypeError("'%s' must be int"%p_object)
self.seq.append(p_object)
def __getattr__(self, item):
return getattr(self.seq,item)
def __str__(self):
return str(self.seq)
l = List([1,2,3])
l.append("4") #TypeError: '4' must be int
print(l)
总结:授权这种方式用在定制源不是类的情况下。例如示例三中,定制
open()
函数
五、__next__
和__iter__
实现迭代器协议
迭代器必须要有__next__
和__iter__
方法;
现在就来自己实现一个迭代器吧;
简单实现:
class Foo:
def __init__(self,x):
self.x=x
def __iter__(self):
return self
def __next__(self):
n=self.x
self.x+=1
return self.x
f=Foo(3)
for i in f:
print(i)
模拟实现range()函数:
class foo:
def __init__(self,start,stop=None,step=1):
self.start = start
self.stop = stop
self.step = step
if isinstance(self.start,str) \
or isinstance(self.stop,str) \
or isinstance(self.step,str):
raise InterruptedError("Must be Numeric")
def __next__(self):
if self.stop:
res = self.compute()
else:
self.stop = self.start
# 如果只传了一个数字,则将其设为迭代停止数字
self.start = 0
# 如果只传了一个数字,则默认从0开始迭代
res = self.compute()
return res
def __iter__(self):
return self
#迭代器执行__iter__方法,返回的是它本身
def compute(self):
if self.start >= self.stop:
#判断是否超出迭代停止数字
raise StopIteration
# 这是超出迭代器范围后迭代器协议规定的抛出异常
iter_val = self.start #迭代后的值
self.start += self.step
return iter_val
for i in foo('a',20,3.5):
print(i)
for i in foo(5,15,3):
print(i)
六、__doc__
查看对象的描述信息
def func():
'''我是函数的描述信息'''
pass
print(func.__doc__)
class Foo:
'''我是类的描述信息'''
pass
class bar(Foo):
pass
obj = Foo()
print(obj.__doc__)
print(Foo.__doc__)
print(bar.__doc__) #该属性不会继承给子类
'''
输出:
我是函数的描述信息
我是类的描述信息
我是类的描述信息
None
'''
七、__module__
和__class__
__module__
表示当前操作的对象在那个模块
__class__
表示当前操作的对象的类是什么
以下两个文件在同一级目录下:
#current.py
class C:
def __init__(self):
self.name = "alex"
#test.py
import current
def test():
print("from the test.test")
obj = current.C()
print(obj.name)
print(obj.__class__) #输出是哪个类实例化得到的对象
print(obj.__module__) #输出属性哪个模块
'''
输出:
alex
current
'''
八、__str__
l = list([1,2,3])
print(l) #打印的是[1, 2, 3]
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
conn = mysql("127.0.0.1","3306")
print(conn) #打印的是<__main__.mysql object at 0x0000000000701898>
从以上两段代码可以看到,我们自己定义的类,生成的对象直接被打印时,打印的是对象的内存地址,而这并不是我们想要的,我们实际想要的也是像第一段代码那样返回一个有用的信息,这时就要用到__str__
这个内置方法了,它定义在类的内部,只要类被实例化,就会自动触发__str__
的执行,并返回一个值给实例化后的对象。如下示例:
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
def __str__(self):
return "Host:%s,Port:%s"%(self.host,self.port)
conn = mysql("127.0.0.1","3306")
#会将`__str__`方法的返回值赋值给对象conn
print(conn)
'''
输出:
Host:127.0.0.1,Port:3306
'''
九、__del__
析构方法
析构方法,当对象在内存中被释放时,会自动触发__del__
执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
简单示例:
class Foo:
def __del__(self):
print("执行__del__")
f1 = Foo()
del f1 #释放f1,会触发__del__执行
print("------->")
'''
输出:
执行__del__
------->
'''
再看看下面这种情况:
class Foo:
def __del__(self):
print("执行__del__")
f1 = Foo()
# del f1 #注释掉
print("------->")
'''
输出:
------->
执行__del__
'''
#可以看出__del__仍然会在程序执行完毕后,被触发执行
十、__setitem__
,__getitem__
,__delitem__
类中有这三个方法,那就意味着类中的属性可以像字典一样进行操作;
class Foo:
def __init__(self,name):
self.name = name
def __getitem__(self, item):
print("from __getitem__")
def __setitem__(self, key, value):
print("from __setitem__")
self.__dict__[key] = value #加入此代码,才会真正设值
def __delitem__(self, key):
print("from __delitem__")
self.__dict__.pop(key)
f1 = Foo("egon")
# f1.age = 18 #不会触发__setitem__的执行
f1["age_2"] = 20 #会触发__setitem__的执行,并不会真正设值;
print(f1.__dict__)
del f1["age_2"]
print(f1.__dict__)
print(f1["name"]) #会触发__getitem__执行,如果__getitem__没有设定返回值,则会返回一个None
'''
输出结果:
from __setitem__
{'name': 'egon', 'age_2': 20}
from __delitem__
{'name': 'egon'}
from __getitem__
None
'''
#从结果可以看出,age_2 = 20并没有真正设值成功
十一、__enter__
和__exit__
通过__enter__
和__exit__
这两个方法可以实现上下文件管理协议,即with
语句。为了让一个对象兼容with
语句,必须在这个对象的类中声明__enter__
和__exit__
方法。
class Open:
def __init__(self,name):
self.name = name
def __enter__(self):
#出现with语句,对象的__enter__方法就会被触发,有返回值则赋值给as声明的变量
print("from __enter__")
return 3 #会返回给as语句后面的变量
def __exit__(self, exc_type, exc_val, exc_tb):
print("from __exit__, with代码块执行完毕时触发")
with Open("egon") as f:
print("from with 代码块")
print("f:",f)
'''
输出:
from __enter__
from with 代码块
f: 3
from __exit__, with代码块执行完毕时触发
'''
__exit__(self, exc_type, exc_val, exc_tb)
中的三个参数的含义:
-
exc_type
代表异常类型 -
exc_val
代表异常值 -
exc_tb
代表异常的追溯信息
class Open:
def __init__(self,name):
self.name = name
def __enter__(self):
#出现with语句,对象的__enter__方法就会被触发,有返回值则赋值给as声明的变量
# print("from __enter__")
return self #会返回给as语句后面的变量
def __exit__(self, exc_type, exc_val, exc_tb):
print("from __exit__, with代码块执行完毕时触发")
print("exc_type:",exc_type)
print("exc_val:",exc_val)
print("exc_tb:",exc_tb)
return True #返回True,则表示不抛出异常,with代码之后的代码可正常执行,但with子代码raise后的代码不会执行。
with Open("egon") as f:
print("from with 代码块")
raise AttributeError("属性错误")
# print("=====> 在异常之后") #不会执行
print("=====> 在异常之后,with之外") #__exit__返回True才会执行
'''
输出:
from with 代码块
from __exit__, with代码块执行完毕时触发
exc_type:
exc_val: 属性错误
exc_tb:
=====> 在异常之后
'''
示例:模拟open(),并实现上下文管理
#模拟open()功能
class Open:
def __init__(self,filename,mode='r',encoding="utf-8"):
self.filename = filename
self.mode = mode
self.encoding = encoding
def __enter__(self):
self.file = open(self.filename,self.mode,encoding =self.encoding)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
return True #遇到异常不抛出,with之外的代码继续执行
with Open("a.txt","w+") as f:
f.write("11111\n")
f.write("22222\n")
f.seek(0)
print(f.tell())
f.abc #抛出异常,交给__exit__处理
十二、__call__
对象后面加括号,就会触发__call__
方法的执行;反之类中有了__call__
方法,通过这个类生成的对象,才能加括号执行。
class foo:
def __init__(self):
print("from __init__")
def __call__(self, *args, **kwargs):
print("from __call__")
obj = foo() #执行__init__
obj() #执行__call__
'''
输出:
from __init__
from __call__
'''