实现迭代器
简单示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Foo: def __init__(self, x): self.x = x
def __iter__(self): return self
def __next__(self): self.x += 1 return self.x
f = Foo(3) for i in f: print(i)
|
StopIteration异常版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Foo: def __init__(self, start, stop): self.num = start self.stop = stop
def __iter__(self): return self
def __next__(self): if self.num >= self.stop: raise StopIteration n = self.num self.num += 1 return n
f = Foo(1, 5) from collections import Iterable, Iterator print(isinstance(f, Iterator)) True for i in Foo(1, 5): print(i)
''' 1 2 3 4 '''
|
模拟range
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Range: def __init__(self, n, stop, step): self.n = n self.stop = stop self.step = step
def __next__(self): if self.n >= self.stop: raise StopIteration x = self.n self.n += self.step return x
def __iter__(self): return self
for i in Range(1, 7, 3): print(i) ''' 1 4 '''
|
斐波那契数列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Fib: def __init__(self): self._a = 0 self._b = 1
def __iter__(self): return self
def __next__(self): self._a, self._b = self._b, self._a + self._b return self._a
f1 = Fib() for i in f1: if i > 100: break print('%s ' % i, end='')
|
实现文件上下文管理
1 2
| with open('a.txt') as f: '代码块'
|
- 上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明enter和exit方法
上下文管理协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Open: def __init__(self, name): self.name = name
def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊')
with Open('a.txt') as f: print('=====>执行代码块') ''' 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 =====>执行代码块 with中代码块执行完毕时执行我啊 '''
|
- exit()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Open: def __init__(self, name): self.name = name
def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print(exc_type) print(exc_val) print(exc_tb)
try: with Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('***着火啦,救火啊***') except Exception as e: print(e) ''' 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 =====>执行代码块 with中代码块执行完毕时执行我啊 <class 'AttributeError'> ***着火啦,救火啊*** <traceback object at 0x1065f1f88> ***着火啦,救火啊*** '''
|
- 如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Open: def __init__(self, name): self.name = name
def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print(exc_type) print(exc_val) print(exc_tb) return True
with Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('***着火啦,救火啊***') print('0' * 100)
''' 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 =====>执行代码块 with中代码块执行完毕时执行我啊 <class 'AttributeError'> ***着火啦,救火啊*** <traceback object at 0x1062ab048> 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 '''
|
模拟open
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Open: def __init__(self, filepath, mode='r', encoding='utf-8'): self.filepath = filepath self.mode = mode self.encoding = encoding
def __enter__(self): self.f = open(self.filepath, mode=self.mode, encoding=self.encoding) return self.f
def __exit__(self, exc_type, exc_val, exc_tb): self.f.close() return True
def __getattr__(self, item): return getattr(self.f, item)
with Open('a.txt', 'w') as f: print(f) f.write('aaaaaa') f.wasdf ''' <_io.TextIOWrapper name='a.txt' mode='w' encoding='utf-8'> '''
|
优点
- 使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
- 在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在exit中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
什么是元类
- 在python中一切皆对象,那么我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类,即元类可以简称为类的类
1 2
| class Foo: # Foo=元类() pass
|

为什么用元类
- 元类是负责产生类的,所以我们学习元类或者自定义元类的目的:是为了控制类的产生过程,还可以控制对象的产生过程
内置函数exec(储备)
1 2 3 4 5 6 7 8 9 10 11 12
| cmd = """ x=1 print('exec函数运行了') def func(self): pass """ class_dic = {}
exec(cmd, {}, class_dic)
print(class_dic)
|
class创建类
- 如果说类也是对象,那么用class关键字的去创建类的过程也是一个实例化的过程,该实例化的目的是为了得到一个类,调用的是元类
- 用class关键字创建一个类,用的默认的元类type,因此以前说不要用type作为类别判断
1 2 3 4 5 6 7 8 9 10 11 12
| class People: country = 'China'
def __init__(self, name, age): self.name = name self.age = age
def eat(self): print('%s is eating' % self.name) print(type(People))
|

5.1 type实现
- 创建类的3个要素:类名,基类,类的名称空间
- People = type(类名,基类,类的名称空间)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class_name = 'People'
class_bases = (object, )
class_dic = {} class_body = """ country='China' def __init__(self,name,age): self.name=name self.age=age def eat(self): print('%s is eating' %self.name) """
exec( class_body, {}, class_dic, )
print(class_name)
print(class_bases)
print(class_dic) ''' {'country': 'China', '__init__': <function __init__ at 0x10a0bc048>, 'eat': <function eat at 0x10a0bcd08>} '''
|
- People = type(类名,基类,类的名称空间)
1 2 3 4 5
| People1 = type(class_name, class_bases, class_dic) print(People1)
obj1 = People1(1, 2) obj1.eat()
|
1 2 3 4
| print(People)
obj = People1(1, 2) obj.eat()
|
自定义元类控制类的创建
1 2 3 4 5 6 7 8
| class Mymeta(type): # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 def __init__(self, class_name, class_bases, class_dic): print('self:', self) # 现在是People print('class_name:', class_name) print('class_bases:', class_bases) print('class_dic:', class_dic) super(Mymeta, self).__init__(class_name, class_bases, class_dic) # 重用父类type的功能
|
- 分析用class自定义类的运行原理(而非元类的的运行原理):
- 拿到一个字符串格式的类名class_name=’People’
- 拿到一个类的基类们class_bases=(obejct,)
- 执行类体代码,拿到一个类的名称空间class_dic={…}
- 调用People=type(class_name,class_bases,class_dic)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class People(object, metaclass=Mymeta): country = 'China'
def __init__(self, name, age): self.name = name self.age = age
def eat(self): print('%s is eating' % self.name) ''' self: <class '__main__.People'> class_name: People class_bases: (<class 'object'>,) class_dic: {'__module__': '__main__', '__qualname__': 'People', 'country': 'China', '__init__': <function People.__init__ at 0x10a0bcbf8>, 'eat': <function People.eat at 0x10a0bc2f0>} '''
|
应用
- 自定义元类控制类的产生过程,类的产生过程其实就是元类的调用过程
- 我们可以控制类必须有文档,可以使用如下的方式实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Mymeta(type): def __init__(self, class_name, class_bases, class_dic): if class_dic.get('__doc__') is None or len( class_dic.get('__doc__').strip()) == 0: raise TypeError('类中必须有文档注释,并且文档注释不能为空') if not class_name.istitle(): raise TypeError('类名首字母必须大写') super(Mymeta, self).__init__(class_name, class_bases, class_dic) try:
class People(object, metaclass=Mymeta ): country = 'China'
def __init__(self, name, age): self.name = name self.age = age
def eat(self): print('%s is eating' % self.name) except Exception as e: print(e) """类中必须有文档注释,并且文档注释不能为空"""
|
call(储备)
- 要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法、、call方法,该方法会在调用对象时自动触发
1 2 3 4 5 6 7 8 9 10 11 12
| class Foo: def __call__(self, *args, **kwargs): print(args) print(kwargs) print('__call__实现了,实例化对象可以加括号调用了')
obj = Foo() obj('lqz', age=18) ('lqz',) {'age': 18} __call__实现了,实例化对象可以加括号调用了
|
new(储备)
我们之前说类实例化第一个调用的是init,但init其实不是实例化一个类的时候第一个被调用 的方法。当使用 Persion(name, age) 这样的表达式来实例化一个类时,最先被调用的方法 其实是 new 方法。
new方法接受的参数虽然也是和init一样,但init是在类实例创建之后调用,而 new方法正是创建这个类实例的方法。
注意:*new*() 函数只能用于从object继承的新式类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class A: pass
class B(A): def __new__(cls): print("__new__方法被执行") return cls.__new__(cls)
def __init__(self): print("__init__方法被执行")
b = B()
|
自定义元类控制类的实例化
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Mymeta(type): def __call__(self, *args, **kwargs): print(self) print(args) print(kwargs) obj = self.__new__(self) self.__init__(obj, *args, **kwargs) return obj
|
- People = Mymeta(),People()则会触发call
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class People(object, metaclass=Mymeta): country = 'China'
def __init__(self, name, age): self.name = name self.age = age
def eat(self): print('%s is eating' % self.name)
|
- 类的调用,即类实例化就是元类的调用过程,可以通过元类Mymeta的call方法控制
- 分析:调用Pepole的目的
- 先造出一个People的空对象
- 为该对空对象初始化独有的属性
- 返回一个初始化好的对象
1 2 3 4 5 6 7 8
| obj = People('lqz', age=18) ''' <class '__main__.People'> ('lqz',) {'age': 18} '''
print(obj.__dict__)
|
自定义元类后类的继承顺序
结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢???
在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象OldboyTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Mymeta(type): n = 444
def __call__(self, *args, **kwargs): obj = self.__new__(self) self.__init__(obj, *args, **kwargs) return obj
class Bar(object): n = 333
class Foo(Bar): n = 222
class OldboyTeacher(Foo, metaclass=Mymeta): n = 111
school = 'oldboy'
def __init__(self, name, age): self.name = name self.age = age
def say(self): print('%s says welcome to the oldboy to learn Python' % self.name)
print(OldboyTeacher.n)
print(OldboyTeacher.n)
|
- 查找顺序:
- 先对象层:OldoyTeacher->Foo->Bar->object
- 然后元类层:Mymeta->type
依据上述总结,我们来分析下元类Mymeta中call里的self.new的查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class Mymeta(type): n = 444
def __call__(self, *args, **kwargs): obj = self.__new__(self) print(self.__new__ is object.__new__)
class Bar(object): n = 333
class Foo(Bar): n = 222
class OldboyTeacher(Foo, metaclass=Mymeta): n = 111
school = 'oldboy'
def __init__(self, name, age): self.name = name self.age = age
def say(self): print('%s says welcome to the oldboy to learn Python' % self.name)
OldboyTeacher('lqz', 18)
|
总结,Mymeta下的call里的self.new在OldboyTeacher、Foo、Bar里都没有找到new的情况下,会去找object里的new,而object下默认就有一个new,所以即便是之前的类均未实现new,也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找new
练习
需求:使用元类修改属性为隐藏属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Mymeta(type): def __init__(self, class_name, class_bases, class_dic): super(Mymeta, self).__init__(class_name, class_bases, class_dic)
def __call__(self, *args, **kwargs): obj = self.__new__(self) self.__init__(obj, *args, **kwargs) obj.__dict__ = { '_%s__%s' % (self.__name__, k): v for k, v in obj.__dict__.items() }
return obj class Foo(object, metaclass=Mymeta): def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex
obj = Foo('lqz', 18, 'male')
print(obj.__dict__)
|