面向对象高级(下)
1 item方法
想对比__getattr__(), __setattr__() 和 __deltattr__()这三个通过属性的方式的三个方法
还有__getitem__(), __setitem__() 和 __delitem__()这三个函数, 是通过字典形式来处理属性
字典形式使用中括号的方式获取值
在实现__setitem__()的时候仍然需要使用__dict__来实现增值的设置
具体的用法如下所示
class People:
def __init__(self,name):
self.name=name
def __getitem__(self, item):
print("==>get")
print(self.__dict__[item])
def __setitem__(self, key, value):
print("==>set")
self.__dict__[key]=value
def __delitem__(self, key):
print("==>del")
self.__dict__.pop(key)
whc = People('whc')
whc['age'] = 18
whc['age']
del whc['age']
2 __str__
__str__() 和 __repr__()两个方法是用于输出信息的方法
和__iter__()方法一样, 对象调用的时候可以直接用 str(对象) 或者 repr(对象) 来对应的调用__str__()和__repr__()
当使用 str函数 或者是print的时候, 调用__str__()
当使用 repr函数 或者直接在交互式环境的时候, 调用__repr__()
如果没有__str__就会使用__repr__代替
由于一般这俩效果相同, 因而一般先定义__str__, 然后写上__repr__ = __str__
这俩的返回值必须是字符串
和他俩相关的是__format__(), 当调用 format() 方法的时候会调用
具体操作代码如下
class Space:
def __init__(self,name,addr,type):
self.name=name
self.addr=addr
self.type=type
def __repr__(self):
return 'Space(%s,%s)' %(self.name,self.addr)
def __str__(self):
return '(%s,%s)' %(self.name,self.addr)
def __format__(self, format_spec):
return 'Space({},{},{})'.format(self.name, self.addr, self.type)
space = Space('兴业家园', '北京', '公寓')
print( repr(space) )
print( str(space) )
print( space )
print(format(space))
3 __slots__
python中对对象可以任意绑定属性, 但是如果需要限制属性的绑定的时候就可以在类中定义一个__slots__
它的值一般赋值成一个元组, 里面的写上可以绑定的属性的名字
当绑定别的名字的时候就会报错
__slots__的实现细节是将对象的__dict__取消了, 而分别对每个对象指定一个__slots__
具体使用代码如下
class People:
__slots__=['name','age']
whc = People()
whc.name = 'whc'
print(whc.__slots__)
fizz = People()
fizz.name = 'fizz'
print(fizz.__slots__)
4 __iter__
可迭代对象是有方法__iter__()
迭代器是有方法__next__()
因而可以自己构建一个类, 使得它的对象, 既是一个可迭代对象, 也是一个迭代器
具体仿造range方法实现的代码如下
class MyRange:
def __init__(self, *args):
if len(args) == 1:
self.start = 0
self.end = args[0]
else:
self.start = args[0]
self.end = args[1]
if len(args) == 3:
self.len = args[2]
else:
self.len = 1
def __iter__(self):
return self
def __next__(self):
if self.start >= self.end:
raise StopIteration
n = self.start
self.start += self.len
return n
for i in MyRange(11):
print(i)
for i in MyRange(2, 11):
print(i)
for i in MyRange(2, 11, 3):
print(i)
5 __doc__
在类中最开始定义的光秃秃的字符串是类的文档信息
查看该文档信息可以使用__doc__来获取
在函数中的第一个光秃秃的字符串也是, 通过函数名来调用__doc__即可获得
另外__doc__无法被继承
具体代码如下
class Foo:
'我是Foo的描述信息'
def foo(self):
'我是foo()的描述信息'
pass
print(Foo.__doc__)
print(Foo().__doc__)
print( Foo.foo.__doc__ )
print(Foo().foo.__doc__ )
6 __module__
__module__和__class__是两个特殊属性
可以通过对象点的方式获取
__module__是获取对象所在模块的名字. __class__是获取对象所在的类
具体代码如下
import time
print(time.time.__module__)
print(time.time.__class__)
# time
# <class 'builtin_function_or_method'>
7 __del__
这是一个特殊地函数--析构函数
调用内置方法del的时候可能会调用到类中__del__()方法
是否执行析构函数的判定是, del后面的变量指引的内容的指引数是否大于0, 如果不大于0则就会调用
在程序结束之后, 如果有没有清除的变量, 对应的__del__()也会执行
具体实例如下
class Foo:
def __init__(self, name):
self.name = name
def __del__(self):
print('{} 执行我啦'.format(self.name))
f0 = Foo('f0')
del f0 #执行__del__
f1 = Foo('f1')
f2 = f1
del f1 #由于还有f2这个指向, 所以不执行__del__
f3 = Foo('f3') #由于此处再新建了一个, 结束了之后这个也要__del__
# f0 执行我啦
# f1 执行我啦
# f3 执行我啦
8 __enter__
在文件操作中, 有个with操作, 语句块结束后可以自动关闭文件
具体可以模拟文件操作来实现open()的功能
with语句的流程如下
with后的操作会生成一个对象, 此时后调用__init__()方法
通过as需要返回给一个对象给as后的变量, 此时是执行__enter__()方法, 返回值给该变量
在执行完毕with内部的代码块后, 会再执行__exit__()方法
__exit__自带三个额外的参数, 分别是异常类型, 异常值, 异常的堆栈信息
__exit__可以有返回值, 但是会判定成布尔值, 为Ture之后, with代码块出现异常的语句之后的语句不再执行, 但是with之后的语句会继续执行
具体顺序的验证如下
class Foo:
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
print("====>",exc_type, exc_val, exc_tb, "<=====")
return True
with Foo() as f:
raise TypeError("出现错误")
print("***************")
print("==> ######## <==")
# ====> <class 'TypeError'> 出现错误 <traceback object at 0x0000000000D4F1C8> <=====
# ==> ######## <==
完成自定义的open()操作的类的实现代码如下
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = 'weihuchao'
import time
class LogFile:
def __init__(self,filename,mode='r',encoding='utf-8'):
self.file=open(filename,mode,encoding=encoding)
def write(self,line):
t=time.strftime('%Y-%m-%d %T')
self.file.write('%s %s' %(t,line))
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def __getattr__(self, item):
return getattr(self.file,item)
with LogFile('log', 'w+') as f:
f.write("aaa")
f.seek(0)
print(f.read())
9 __call__
在类中还可以定义一个特殊函数__call__()来实现对象的可调用属性
具体实现是
class Foo:
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo()
obj()
10 元类
对象是由类产生的, 类是由type产生的, type继续由type产生
type()手动创建类需要三个元素,
一个是字符串类型, 表示类名
一个是元组类型, 表示父类的集合
一个是字典类型, 表示绑定的属性和方法
具体实现如下
def run(self):
print("running..")
PeopleClass = type("People",(object,), {"country":"China",'run':run})
whc = PeopleClass()
print(PeopleClass)
print(whc)
# <class '__main__.People'>
# <__main__.People object at 0x0000000000B27CC0>
10.1 利用元类新增功能
可以写一个元类Mateclass, 它需要继承自type类
原来的类需要关联该元类, 也就是在继承中有 metaclass=元类名字
此时执行元类就可以生成一个对象, 也就是创建的这个类
基于此, 就是元类中的__init__()方法创建的 类对象, 所以新加的功能只需在__init__()方法中就行
元类的__init__()有额外三个参数, 分别是类名, 类基类, 类属性字典
实现检查__doc__的代码如下
class MyMetaclass(type):
def __init__(self, class_name, class_bases, class_dic):
for key, value in class_dic.items():
if callable(value) and not value.__doc__:
raise Exception("{}方法内必须写入注释..".format(key))
class Foo(metaclass=MyMetaclass):
x = 1
def __init__(self, name):
self.name = name
def run(self):
'run function'
print('running')
def go(self):
print('going')
10.2 新建对象的过程
首先, 类要生成对象, 类本身需要可调用
针对于基类, 类是基类的对象, 也就是说在基类中, 需要有一个__call__()函数, 而这个函数, 是在类的__init__之前执行的
在基类的__call__()方法中, 需要使用self.__new__(self)来创建一个空对象, 这个对象就是类
有了类之后就可以调用原有的方法__init__(), 这时在其中就是熟悉的生成对象了
最后再返回这个类就行了
完成整个过程的代码如下
class MyMetaclass(type):
def __call__(self, *args, **kwargs):
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs) # obj.name='egon'
return obj
class People(metaclass=MyMetaclass):
def __init__(self, name):
self.name = name
whc = People('whc')
print(whc.name)