Python进阶
面向对象编程
面向对象编程——Object Oriented Programming,简称 OOP,是一种程序设计思想。OOP 把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
在 Python 中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class 是一种抽象概念,比如我们定义的 Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的 Student,比如,张三和李四是两个具体的 Student。
所以,面向对象的设计思想是抽象出 Class,根据 Class 创建 Instance。
面向对象的抽象程度又比函数要高,因为一个 Class 既包含数据,又包含操作数据的方法。
创建类
类的特殊方法
Python 使用 __ 开头的名字来定义特殊的方法和属性,它们有:
__init__()
__repr__()
__str__()
__call__()
__iter__()
__add__()
__sub__()
__mul__()
__rmul__()
__class__
__name__
构造方法 __init__()
在产生对象之后,我们可以向对象中添加属性。 事实上,还可以通过构造方法,在构造对象的时候直接添加属性:
class Clothes(object):
"""
init_demo
"""
def __init__(self, color="green"):
self.color = color
my_clothes = Clothes()
my_clothes.color
传入有参数的值:
your_clothes = Clothes('orange')
your_clothes.color
表示方法 __repr__() 和 __str__()
:
class Clothes(object):
"""
repr and str demo
"""
def __init__(self, color="green"):
self.color = color
def __str__(self):
"This is a string to print."
return ("a {} clothes".format(self.color))
def __repr__(self):
"This string recreates the object."
return ("{}(color='{}')".format(self.__class__.__name__, self.color))
__str__()
是使用 print 函数显示的结果,类似 java 中的 toString:
my_clothes = Clothes()
print(my_clothes)
__repr__()
返回的是不使用 print 方法的结果:
my_clothes
print(my_clothes.__class__, my_clothes.__class__.__name__, my_clothes.color)
my_clothes.__class__, my_clothes.__class__.__name__, my_clothes.color
类的属性
只读属性:
class Clothes(object):
def __init__(self, price):
self.price = price
# 这样 discount_price 就变成属性了
@property
def discount_price(self):
return self.price * 0.8
这里 discount_price 就是一个只读不写的属性了(注意是属性不是方法), 而 price 是可读写的属性:
my_clothes = Clothes(100)
print(my_clothes.discount_price) # 80.0
可以修改 price 属性来改变 discount_price:
my_clothes.price = 200
print(my_clothes.discount_price) # 160.0
my_clothes.discount_price()会报错,因为 my_clothes.discount_price 是属性,不是方法;
my_clothes.discount_price=100 也会报错,因为只读。
对于 @property 生成的只读属性,我们可以使用相应的 @attr.setter 修饰符来使得这个属性变成可写的:
class Clothes(object):
def __init__(self, price):
self.price = price
# 这样就变成属性了
@property
def discount_price(self):
return self.price * 0.8
@discount_price.setter
def discount_price(self, new_price):
self.price = new_price * 1.25
测试一下:
my_clothes = Clothes(100)
print(my_clothes.discount_price)
my_clothes.price = 200
print(my_clothes.discount_price)
修改 discount_price 属性:
my_clothes.discount_price = 180
print(my_clothes.price)
print(my_clothes.discount_price)
一个等价的替代如下,用方法:
class Clothes(object):
def __init__(self, price):
self.price = price
def get_discount_price(self):
return self.price * 0.8
def set_discount_price(self, new_price):
self.price = new_price * 1.25
discount_price = property(get_discount_price, set_discount_price)
my_clothes = Clothes(100)
print(my_clothes.discount_price)
my_clothes.price = 200
print(my_clothes.discount_price)
my_clothes.discount_price = 180
print(my_clothes.price)
print(my_clothes.discount_price)
继承
类定义的基本形式:
class ClassName(ParentClass):
"""class docstring"""
def method(self):
return
里面的 ParentClass 就是用来继承的。
class Clothes(object):
def __init__(self, color="green"):
self.color = color
def out_print(self):
return self.__class__.__name__, self.color
my_clothes = Clothes()
my_clothes.color
my_clothes.out_print()
定义一个子类,继承父类的所有方法:
class NikeClothes(Clothes):
def change_color(self):
if self.color == "green":
self.color = "red"
继承父类的所有方法:
your_clothes = NikeClothes()
your_clothes.color
your_clothes.out_print()
但有自己的方法:
your_clothes.change_color()
your_clothes.color
如果想对父类的方法进行修改,只需要在子类中重定义这个类即可:
class AdidasClothes(Clothes):
def change_color(self):
if self.color == "green":
self.color = "black"
def out_print(self):
self.change_color()
return self.__class__.__name__, self.color
him_clothes = AdidasClothes()
print(him_clothes.color)
him_clothes.change_color()
print(him_clothes.color)
print(him_clothes.out_print())
super() 函数
super(CurrentClassName, instance)
返回该类实例对应的父类对象。
刚才 AdidasClothes 可以改写为:
class NewAdidasClothes(Clothes):
def change_color(self):
if self.color == "green":
self.color = "black"
def out_print(self):
self.change_color()
print(super(NewAdidasClothes, self).out_print())
her_clothes = NewAdidasClothes()
print(her_clothes.color)
her_clothes.out_print()
new() 方法
new()用来创建一个实例,它至少有一个参数 cls,代表当前类。默认情况下new()会创建当前类的实例,该方法也可以被重载,重载后也可以创建其他类的实例。
class Fun(object):
def __init__(self, fun):
self.fun = fun
def __new__(cls, *args, **kwargs):
return object.__new__(Fun)
if __name__ == '__main__':
f = Fun.__new__(Fun)
print(type(f))
new()方法只是创建实例,此时拿到的实例并不能正常使用。一个实例需要被init()方法初始化后才可以被正常使用。也就是说,正常场景下,我们生成一个类的实例,Python 先调用该类的new()**方法创建一个实例,然后再调用init**()方法初始化该实例。**new()**方法存在于 object 方法中,通常情况下不需要被重载。
可以使用new方法创建出其它类的实例。在这种场景下,new方法创建后会调用对应类的init方法完成初始化:
class Fun(object):
def __init__(self, fun):
self.fun = fun
def __new__(cls, *args, **kwargs):
return Demo(*args, **kwargs)
class Demo(object):
def __init__(self, d):
self.demo = d
if __name__ == '__main__':
f = Fun(1)
print("type f:", type(f))
print("f.demo:", f.demo)
可以看出,f 不是 Fun 类的一个实例,而是 Demo 类的一个实例,拥有 Demo 类的字段。因为 Fun 类的new方法创建的是一个 Demo 类实例,而非 Fun 类本身。因此 Fun.new方法在 return 后调用了 Demo.init方法,以完成该实例的初始化。
接口
接口的调用:
class Clothes(object):
def __init__(self, color="green"):
self.color = color
def out(self):
print("father.")
class NikeClothes(Clothes):
def out(self):
self.color = "brown"
super(NikeClothes, self).out()
class AdidasClothes(object):
def out(self):
print("adidas.")
因为三个类都实现了 out() 方法,因此可以这样使用:
objects = [Clothes(), NikeClothes(), AdidasClothes()]
for obj in objects:
obj.out()
类方法
类方法包括以下几种:
- special 方法和属性,即以 __ 开头和结尾的方法和属性
- 私有方法和属性,以 _ 开头,不过不是真正私有,而是可以调用的, 但是不会被代码自动完成所记录(即 Tab 键之后不会显示)
- 共有的方法和属性
以 __
开头不以 __
结尾的属性是更加特殊的方法,调用方式也不同:
class MyDemoClass(object):
def __init__(self):
print("special.")
def _get_name(self):
print("_get_name is private method.")
def get_value(self):
print("get_value is public method.")
def __get_type(self):
print("__get_type is really special method.")
demo = MyDemoClass()
demo.get_value()
demo._get_name()
demo._MyDemoClass__get_type()
文件
写文件
我们使用 open 函数的写入模式来写文件:
f = open('test.txt', 'w')
f.write('hello world.')
f.close()
print(open('test.txt').read())
使用 w 模式时,如果文件不存在会被创建
除了写入模式,还有追加模式 a
读写模式 w+
f = open('test.txt', 'w+')
f.write('hello world. morning.')
f.seek(3)
print(f.read()) # hello world.
f.close()
读文件
使用 open 函数 来读文件,使用文件名的字符串作为输入参数:
默认打开文件是 ‘r’ 读模式
f = open("test.txt")
# 默认以读的方式打开文件,如果文件不存在会报错。
# 可以使用 read 方法来读入文件中的所有内容:
text = f.read()
print(text)
按照行读入内容,readlines 方法返回一个列表,每个元素代表文件中每一行的内容:
f = open("test.txt")
lines = f.readlines()
print(lines)
f.close()
# 事实上,我们可以将 f 放在一个循环中,得到它每一行的内容:
f = open('test.txt')
for line in f:
print(line)
f.close()
上下文管理器
with open('my_file.txt', 'w') as fp:
data = fp.write("Hello world")
这等效于下面的代码,但是要更简便:
fp = open('my_file.txt', 'w')
try:
# do stuff with f
data = fp.write("Hello world")
finally:
fp.close()
自定义上下文管理器
比如可以这样定义一个简单的上下文管理器:
class ContextManager(object):
def __enter__(self):
print("Entering")
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting")
with ContextManager():
print("inside operate")
enter 的返回值
如果在 enter 方法下添加了返回值,
那么我们可以使用 as 把这个返回值传给某个参数:
class ContextManager2(object):
def __enter__(self):
print("Entering")
return "my value"
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting")
with ContextManager2() as val:
print(val)
一个通常的做法是将 enter 的返回值设为这个上下文管理器对象本身, 文件对象就是这样做的.
class ContextManager3(object):
def __enter__(self):
print("Entering")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting")
错误处理
上下文管理器对象将错误处理交给 exit 进行,可以将错误类型, 错误值和 traceback 等内容作为参数传递给 exit 函数:
class ContextManager4(object):
def __enter__(self):
print("Entering")
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting")
if exc_type is not None:
print(" Exception:", exc_value)
return True # 不想让错误抛出,只需要将 __exit__ 的返回值设为 True
with ContextManager4():
print(1 / 0)
import os
os.remove('my_file.txt')
二进制文件
二进制读写模式 b:
import os
f = open('binary.bin', 'wb')
f.write(os.urandom(10))
f.close()
f = open('binary.bin', 'rb')
print(repr(f.read()))
f.close()
with 方法
事实上,Python 提供了更安全的方法,当 with 块的内容结束后, Python 会自动调用它的 close 方法,确保读写的安全:
with open('new_file.txt', 'w') as f:
for i in range(3000):
x = 1.0 / (i - 1000)
f.write('hello world: ' + str(i) + '\n')
与 try/exception/finally 效果相同,但更简单。
查看文件写的结果,虽然触发 error,但已经写的内容是成功的。
!tail new_file.txt
!wc -l new_file.txt
# 删除文件:
import os
os.remove('test.txt')
os.remove('binary.bin')
os.remove('new_file.txt')
异常
try & except 块
捕捉不同的错误类型,尝试在下面输入框输入:-1,1,2,q
import math
while True:
try:
text = input('>')
if text[0] == 'q':
break
x = float(text)
y = 1 / math.log10(x)
print("1/log10({0}) = {1}".format(x, y))
except ValueError:
print("value must bigger than 0")
except ZeroDivisionError:
print("the value must not be 1")
自定义异常
异常是标准库中的类,这意味着我们可以自定义异常类:
尝试在文本输入框输入:k,start,q
class CommandError(ValueError):
print("bad command operation. must input 'start', 'stop', 'pause'")
valid_commands = {'start', 'stop', 'pause'}
while True:
command = input('>')
if command == 'q':
break
try:
if command.lower() not in valid_commands:
raise CommandError('Invalid command: %s' % command)
print('input command:', command)
except CommandError:
print("bad command string: %s" % command)
finally
try/catch 块还有一个可选的关键词 finally。
不管 try 块有没有异常, finally 块的内容总是会被执行, 而且会在抛出异常前执行,因此可以用来作为安全保证,
比如文件操作时,常在 finally 关闭文件。
try:
print(1 / 0)
except ZeroDivisionError:
print('divide by 0.')
finally:
print('finally was called.')
装饰器
如果你有一批变量想统一按一个规则处理,并且需要缩减代码,你需要函数。
如果你有一批函数想统一按一个规则处理,并且需要缩减代码,你需要装饰器(Decorator)
理清下面 2 点:
函数
- 接受参数
- 做点事情
- 返回结果
装饰器
- 接受函数作为参数
- 做点事情
- 返回一个函数
用 @ 来使用装饰器
使用 @ 符号来将某个函数替换为装饰符之后的函数:
例如这个函数:
def dec(f):
print('I am decorating function', id(f))
return f
def foo(x):
print(x) # I am decorating function 45206384
foo = dec(foo)
可以替换为:
@dec
def foo(x):
print(x)
那么他有什么实际作用?故事的开始是这样的,你写好了 2 个函数:
def test1():
print('test1 ..')
def test2():
print('test2 ..')
test1()
test2()
当你准备把它放到服务器上,这个时候领导提醒你要输出日志,不然查错跑断腿。 输出要求是:在每次函数调用的前后加上时间。 于是你写成了下面这个样子
import time
def test1():
print('测试开始:现在时间是',time.time())
print('test1 ..')
print('测试结束:现在时间是',time.time())
def test2():
print('测试开始:现在时间是',time.time())
print('test2 ..')
print('测试结束:现在时间是',time.time())
test1()
test2()
领导说,他有 3 个问题:
- 首先代码 1 和代码 2 是一样的,也就是说把同样的代码写了 2 遍,这一点也不程序员!
- 而且,你修改了你的核心代码,使得它变得很长。后面要再删也很麻烦,万一手抖删错了就完了。
- 最后,在大项目合作中,可能 test 代码是 A 同事写的,输出日志代码是 B 同事写的,代码保密,每个程序员只能拿到部分片段,所以你根本不知道对方的代码,要提供一个通用的打印日志的方式。
思考下,可以怎么修改能既不修改源代码,又对代码结构影响最小呢?
我说,这样子,那我可以写成这样?
import time
def a_decorator(func):
print('测试开始:现在时间是',time.time())
func()
print('测试结束:现在时间是',time.time())
def test1():
print('test1 ..')
def test2():
print('test2 ..')
a_decorator(test1)
a_decorator(test2)
领导说:有进步,但是原本调用 test1()的语法被你改成了 a_decorator(test1),这要是再多几个功能不得把我绕晕了啊。
看来函数嵌套掌握的不熟啊,给你点提示,我带你透过现象看本质
- 变量的本质:就是变量指向的内存地址
- 函数名的本质:就是函数的内存地址
- 变量可以作为函数的参数,因此函数名可以用做函数的参数
- 变量可以作为函数的返回值,同理,函数名也可以作为函数的返回值
我说,那就写成这样?
import time
def a_decorator(func):
def wrap_the_func():
print('测试开始:现在时间是',time.time())
func()
print('测试结束:现在时间是',time.time())
return wrap_the_func
def test1():
print('test1 ..')
def test2():
print('test2 ..')
test1 = a_decorator(test1) #这里a_decorator(test1) 代指wrap_the_func(),把这个wrap_the_func()函数的地址赋值给test1,由于代码从上而下执行,从而替换掉原本test1的指向。
test2 = a_decorator(test2)
test1()
test1()
领导说:这倒数 3、4 行看着很碍眼,且会占据命名空间,你不会修饰符吗?我教你啊。
- 我们先定义一个函数(名字随便起,这里只是用 a_decorator 做示例)
- 然后简单的设置下这 个函数运行逻辑,
- 最后在原有的函数的头上加@函数名就行啦
直接使用@函数修饰符是很方便的,你也看出来所谓【@函数修饰符】其实就是【函数】嵌入。
这里我再假设你的函数是带参数的。我也用修饰符写一下吧。好好看,好好学。
核心代码(下方的 test 函数)无需知道我(下方的 log 函数)是怎么写的,我也无需知道核心代码是怎么写的,我们就能快速完成协作。
import time
#args 是 arguments 的缩写,表示位置参数;
#kwargs 是 keyword arguments 的缩写,表示关键字参数。
#这其实就是 Python 中可变参数的两种形式,
#并且 *args 必须放在 **kwargs 的前面,因为位置参数在关键字参数的前面。
def log(func):
def wrapper(*args,**kwargs):
print('测试开始:现在时间是',time.time())
ret = func(*args,**kwargs)
print('测试结束:现在时间是',time.time())
return ret
return wrapper
@log
def test1(s):
print('test1 ..', s)
return s
@log
def test2(s1, s2):
print('test2 ..', s1, s2)
return s1 + s2
test1(1)
test2(1,2)
于是你回想起之前 Python 也提供了一些自带函数,例如:print()、input()
那会不会也有一些自带的【@函数修饰符】呢?还真有,常见的包括:@property、@classmethod、@staticmethod 还有 typing 里面各种用于测试的函数。
默认的self结构,可以通过self访问类的属性、方法。但是需要先实例化。
@staticmethod (静态方法)不需要 self 参数,也不需要 cls 参数。它们与类或实例没有任何绑定,只是类中的一个普通函数。静态方法通常用于与类相关,但不依赖于实例或类属性的操作。调用时不需要实例化类。
@classmethod (类方法)使用 cls 作为第一个参数,表示调用该方法的类本身,而不是实例。它可以访问类级别的属性和方法。调用时可以实例化类,也可以不实例化类。
不过这些结构相对复杂,当你理解普通的@修饰符之后,这些自带的你只需要记得用法即可,原理都是一样的。
例子
定义两个装饰器函数,一个将原来的函数值加一,另一个乘二:
def plus_one(f):
def new_func(x):
return f(x) + 1
return new_func
def times_two(f):
def new_func(x):
return f(x) * 2
return new_func
定义函数,先乘二再加一:
@plus_one
@times_two
def foo(x):
return int(x)
b = foo(2)
b # 5
修饰器工厂
decorators factories 是返回修饰器的函数
它的作用在于产生一个可以接受参数的修饰器,
例如我们想将 函数 输出的内容写入一个文件去,可以这样做:
def super_loud(filename):
fp = open(filename, 'w')
def loud(f):
def new_func(*args, **kw):
fp.write(str(args))
fp.writelines('\n')
fp.write('calling with' + str(args) + str(kw))
# 确保内容被写入
fp.flush()
fp.close()
rtn = f(*args, **kw)
return rtn
return new_func
return loud
@super_loud('test.txt')
def foo(x):
print(x)
# 调用 foo 就会在文件中写入内容:
foo(100)
import os
os.remove('test.txt')
@classmethod 装饰器
在 Python 标准库中,有很多自带的装饰器,
例如 classmethod 将一个对象方法转换了类方法:
class Foo(object):
@classmethod
def bar(cls, x):
print('the input is', x)
def __init__(self):
pass
类方法可以通过 类名.方法 来调用:
Foo.bar(10)
@property 装饰器
有时候,我们希望像 Java 一样支持 getters 和 setters 的方法,
这时候就可以使用 property 装饰器:
class Foo(object):
def __init__(self, data):
self.data = data
@property
def x(self):
return self.data
此时可以使用 .x 这个属性查看数据(不需要加上括号):
foo = Foo(22)
print(foo.x)
这样做的好处在于,这个属性是只读的:
foo.x = 1 会报错
如果想让它变成可读写,可以加上一个装饰符 @x.setter:
class Foo(object):
def __init__(self, data):
self.data = data
@property
def x(self):
return self.data
@x.setter
def x(self, value):
self.data = value
foo = Foo(1000)
foo.x
foo.x = 2222
foo.x
应用:定时器
要求:写一个定时器功能,要求监控一个执行程序,超时则报警。
如何完成?
下方代码在 mac 下可用
import signal
import time
def set_timeout(num, callback):
def wrap(func):
def handle(signum, frame): # 收到信号 SIGALRM 后的回调函数,参数1是信号的数字,参数2是the interrupted stack frame.
raise RuntimeError
def to_do(*args, **kwargs):
try:
signal.signal(signal.SIGALRM, handle) # 设置信号和回调函数
signal.alarm(num) # 设置 num 秒的闹钟
print('start alarm signal.')
r = func(*args, **kwargs)
print('close alarm signal.')
signal.alarm(0) # 关闭闹钟
return r
except RuntimeError as e:
callback()
return to_do
return wrap
def after_timeout(): # 超时后的处理函数
print("do something after timeout.")
raise RuntimeError
@set_timeout(2, after_timeout) # 限时 2 秒超时
def connect(): # 要执行的函数
time.sleep(2.4) # 函数执行时间,写大于2的值,可测试超时
return "完成"
class Demo:
@set_timeout(2, after_timeout)
def conn(self):
time.sleep(3)
return "ok"
试一下:
try:
a = connect()
print(a)
except Exception as e:
a = 'err'
print(a)
如果不超时:
b = Demo()
try:
c = b.conn()
print(c)
except RuntimeError as e:
print('run time err.')
class Demo:
@set_timeout(2, after_timeout)
def conn(self):
time.sleep(1)
return "ok"
b = Demo()
try:
c = b.conn()
print(c)
except RuntimeError as e:
print('run time err.')
迭代器
迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
字符串,列表或元组对象都可用于创建迭代器:
list=[1,2,3,4]
it = iter(list) # 创建迭代器对象
next(it) # 输出迭代器的下一个元素
next(it) # 再输出下一个元素
enumerate
列表好处是不需要对下标进行迭代,直接输出列表的值:
x = [2, 4, 6]
for i in x:
print(i)
但是有些情况下,我们既希望获得下标, 也希望获得对应的值,那么:
可以将迭代器传给 enumerate 函数, 这样每次迭代都会返回一组 (index, value) 组成的元组:
x = [2, 4, 6]
for i, n in enumerate(x):
print(i, 'is', n)
自定义迭代器
一个迭代器都有 __iter__()
与 __next__()
__iter__()
方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__()
方法并通过 StopIteration 异常标识迭代的完成。
__next__()
方法(Python 2 里是 next())会返回下一个迭代器对象。
自定义一个 list 的取反迭代器:
class ReverseListIterator(object):
def __init__(self, lst):
self.list = lst
self.index = len(lst)
def __iter__(self):
return self
def __next__(self):
self.index -= 1
if self.index >= 0:
return self.list[self.index]
else:
raise StopIteration
x = range(10)
for i in ReverseListIterator(x):
print(i)
只要我们定义了这三个方法(__init__, __iter__, __next__
),我们可以返回任意迭代值:
实现 Collatz 猜想
这里我们实现 Collatz 猜想:
- 奇数 n:返回 3n + 1
- 偶数 n:返回 n / 2
- 直到 n 为 1 为止:
class Collatz(object):
def __init__(self, start):
self.value = start
def __iter__(self):
return self
def __next__(self):
if self.value == 1:
raise StopIteration
elif self.value % 2 == 0:
self.value = self.value / 2
else:
self.value = 3 * self.value + 1
return self.value
for x in Collatz(5):
print(x)
不过迭代器对象存在状态,有问题:
i = Collatz(5)
# zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的迭代器。
for x, y in zip(i, i):
print(x, y)
# 下方代码等价于上方代码
i = Collatz(5)
# *zipped 可理解为解压,返回 二维矩阵式
zipped = zip(i, i)
#<zip object at 0x00000200CFC1F400> #返回的是一个对象
x, y = zip(*zipped)
print(x, y)
解决方法是将迭代器和可迭代对象分开处理。
迭代器和可迭代对象分开处理
这里提供了一个二分树的中序遍历实现:
class BinaryTree(object):
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
def __iter__(self):
return InorderIterator(self)
class InorderIterator(object):
def __init__(self, node):
self.node = node
self.stack = []
def __next__(self):
if len(self.stack) > 0 or self.node is not None:
while self.node is not None:
self.stack.append(self.node)
self.node = self.node.left
node = self.stack.pop()
self.node = node.right
return node.value
else:
raise StopIteration()
测试:
tree = BinaryTree(
left=BinaryTree(
left=BinaryTree(1),
value=2,
right=BinaryTree(
left=BinaryTree(3),
value=4,
right=BinaryTree(5)
),
),
value=6,
right=BinaryTree(
value=7,
right=BinaryTree(8)
)
)
for value in tree:
print(value)
不会出现之前的问题:
for x, y in zip(tree, tree):
print(x, y)
生成器
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
- 迭代器则通过 next 的 return 将值返回;
- 与迭代器不同的是,生成器会自动记录当前的状态, 而迭代器则需要进行额外的操作来记录当前的状态。
之前的 collatz 猜想,简单循环的实现如下:
collatz:
- 奇数 n:返回 3n + 1
- 偶数 n:返回 n / 2
- 直到 n 为 1 为止:
def collatz(n):
sequence = []
while n != 1:
if n % 2 == 0:
n /= 2
else:
n = 3 * n + 1
sequence.append(n)
return sequence
for x in collatz(5):
print(x)
生成器的版本如下:
def collatz(n):
while n != 1:
if n % 2 == 0:
n /= 2
else:
n = 3 * n + 1
yield n
for x in collatz(5):
print(x)
迭代器的版本如下:
class Collatz(object):
def __init__(self, start):
self.value = start
def __iter__(self):
return self
def next(self):
if self.value == 1:
raise StopIteration
elif self.value % 2 == 0:
self.value = self.value / 2
else:
self.value = 3 * self.value + 1
return self.value
for x in collatz(5):
print(x)
事实上,生成器也是一种迭代器:
x = collatz(5)
x
它支持 next 方法,返回下一个 yield 的值:
next(x)
next(x)
__iter__
方法返回的是它本身:
x.__iter__()
return 和 yield 有什么区别?
yield 是暂停的意思(它有程序中起着类似红绿灯中等红灯的作用);yield 是创建迭代器,可以用 for 来遍历,有点事件触发的意思
return 在方法中直接返回值;是函数返回值,当执行到 return,后续的逻辑代码不在执行
相同点: 都是定义函数过程中返回值
不同点:yield 是暂停函数,return 是结束函数; 即 yield 返回值后继续执行函数体内代码,return 返回值后不再执行函数体内代码。
yield 返回的是一个迭代器(yield 本身是生成器-生成器是用来生成迭代器的);return 返回的是正常可迭代对象(list,set,dict 等具有实际内存地址的存储对象)
如果要返回的数据是通过 for 等循环生成的迭代器类型数据(如列表、元组),return 只能在循环外部一次性地返回,yeild 则可以在循环内部逐个元素返回。
yiled from 还可以使一个生成器可以委派子生成器,建立双向通道
def g1(x):
yield range(x, 0, -1)
yield range(x)
print(list(g1(5)))
#[range(5, 0, -1), range(0, 5)]
def g2(x):
yield from range(x, 0, -1)
yield from range(x)
print(list(g2(5)))
#[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
迭代器和生成器有什么区别?
在 Python 中,使用了 yield 的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象:迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。迭代器有两个基本的方法:iter() 和 next()
new和 init的区别?
执行顺序的不同:只有在new返回一个 cls 的实例时后面的init才能被调用
功能上的不同:当创建一个新实例时调用new,初始化一个实例时用init
返回值的不同:new方法会返回一个创建的实例,而init什么都不返回