一. item函数
item
函数可以把对象操作模拟成字典操作, item
函数包括以下三个函数__getitem__
,__setitem__
和__delitem__
具体用法如下所示
class Foo:
def __init__(self, name):
self.name = name
def __getitem__(self, item):
return self.__dict__[item]
def __setitem__(self, key, value):
self.__dict__[key] = value
def __delitem__(self, key):
self.__dict__.pop(key)
f = Foo('j')
print(f['name'])
f['gender'] = 'male'
print(f.__dict__)
del f['gender']
print(f.__dict__)
>>j
>>{'name': 'j', 'gender': 'male'}
>>{'name': 'j'}
二. slots
-
__slots__
是什么:是一个类变量,变量值可以是列表,元组,或者其他可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) - 当一个类有较少的属性,但是会多次实例化时,为了节省内存可以使用
__slots__
取代实例的__dict__
当你定义__slots__
后。类的实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
字典,这跟元组或列表很类似。在__slots__
中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__
的一个缺点就是我们不能给实例添加新的属性,只能使用在__slots__
中定义的那些属性名。 - 注意事项:
__slots__
的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__
后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,应该只在那些经常被使用到 的用作数据结构的类上定义__slots__
。
__slots__
可以作为一个封装工具来防止用户给实例增加新的属性。使用__slots__
可以达到这样的目的,但是__slots__
的设计初衷是一个内存优化工具。
class Foo:
__slots__='x'
f1=Foo()
f1.x=1
f1.y=2#报错
print(f1.__slots__) #使用__slots__后,不再有__dict__
class Bar:
__slots__=['x','y']
n=Bar()
n.x,n.y=1,2
n.z=3#报错
三. 迭代器协议
迭代器协议: 对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个stopIteration的异常终止迭代
遵循迭代器协议使用__iter__
和__next__
函数实现range
函数
class Range:
def __init__(self, value, start=0, step=1):
self.value = value
self.start = start
self.step = step
def __iter__(self): # 使用__iter__函数为Range类增加可迭代属性
return self
def __next__(self): # 使用__next__方法得到迭代返回值
if self.start >= self.value:
raise StopIteration
n = self.start
self.start += self.step
return n
for i in Range(3):
print(i)
四. 上下文管理
操作文件的时候可以使用with
语句,with
语句遵循上下文协议。使用__enter__
和__exit__
方法可以让一个对象支持上下文协议。
使用with
语句可以实现with中的语句执行结束后自动完成清理工作
一般在文件操作,网络连接和锁的编程环境中,在__exit__
中设计自动释放资源的机制,实现自动释放资源
class Foo:
def __init__(self, filepath, mod='r', encode='utf-8'):
self.f = open(filepath, mod, encoding=encode)
self.filepath = filepath
self.mod = mod
def write(self, line):
self.f.write(line)
def __getattr__(self, item):
return getattr(self.f, item)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.f.close()
del self.f
return True # 返回True可以吞掉异常,异常发生后,`with`代码块外的语句还可以继续执行
def __del__(self):
print('close file')
with Foo('a.txt', 'w') as f:
f.write('ddddd')
print('------>')
五. 元类
__call__
构造方法的执行是由创建对象触发的,即:对象 = 类名() ;
对于 __call__
方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo:
def __init__(self):
print('__init__')
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
>>__init__
>>__call__
metaclass
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
创建类的两种方法
# 方法一
class Foo_1:
x = 1
def func(self):
print('func')
print(Foo_1.__dict__)
#{'__module__': '__main__',
# 'x': 1,
# 'func': ,
# '__dict__': ,
# '__weakref__': ,
# '__doc__': None}
#{'__module__': '__main__',
# 方法二
def func(self):
print('func')
x = 1
Foo_2 = type('Foo_2', (object, ), {'func': func, 'x': 1})
print(Foo_2.__dict__)
#{'__module__': '__main__',
# 'x': 1,
# 'func': ,
# '__dict__': ,
# '__weakref__': ,
# '__doc__': None}
一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类
class Mymeta(type):
def __init__(self,name,bases,dic):
print('Mymeta.__init__')
def __new__(cls, *args, **kwargs):
print('Mymeta.__new__')
return type.__new__(cls,*args,**kwargs)
def __call__(self, *args, **kwargs):
print('Mymeta.__call__')
obj=self.__new__(self)
self.__init__(self,*args,**kwargs)
return obj
class Foo(object,metaclass=Mymeta):
def __init__(self,name):
print('Foo.__init__')
self.name=name
def __new__(cls, *args, **kwargs):
print('Foo.__new__')
return object.__new__(cls)
f = Foo('Joe')
>>Mymeta.__new__
>>Mymeta.__init__
>>Mymeta.__call__
>>Foo.__new__
>>Foo.__init__
名字加括号的本质(即,任何name()
的形式),都是先找到name的父类,然后执行:父类.__call__
而父类.__call__
一般做两件事:
调用父类.__new__
方法并返回一个对象
调用父类.__init__
方法对子类进行初始化
class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行
Foo=Mymeta('foo',(...),{...})
因此我们可以看到,只定义class就会有如下执行效果
Mymeta.__new__
Mymeta.__init__
实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作,
遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的父类type
,然后执行type.__call__(...)
方法
于是触发Mymeta.__new__
方法得到一个具体的对象,然后触发Mymeta.__init__
方法对对象进行初始化
f=Foo('Joe')的原理同上