教程网址: https://www.liaoxuefeng.com/wiki/1016959663602400
空值
空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。
字符串编码
计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。
Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。Unicode标准也在不断发展,但最常用的是UCS-16编码,用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。现代操作系统和大多数编程语言都直接支持Unicode。
如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。
所以,本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间.
字符 | ASCII | Unicode | UTF-8 |
---|---|---|---|
A | 01000001 | 0000000001000001 | 01000001 |
中 | x | 0100111000101101 | 111001001011100010101101 |
总结一下现在计算机系统通用的字符编码工作方式:
- 在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
- 用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件
- 浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器
内存中用 Unicode, 文件中或者网络传输中用 UTF-8, 节省空间.
在最新的Python 3版本中,字符串是以Unicode编码的.
之前提到计算机只能处理数字, 字符和数字之间需要响应的转换.
对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:
- chr(): 数字转字符.built-in function chr(), which is an abbreviation of the word "character".
- ord():字符转数字. The word that built-in function ord() abbreviate for"ordinal".It’s an abbreviation for "ordinal". Ordinal numbers are counting numbers — i.e., 1, 2, 3. ord()converts the character into its (countable) position in the character set.
- 如果知道字符的整数编码,还可以用十六进制写文本, 和chr() 字符拼接等价:
'\u4e2d\u6587'
chr(20013) +chr(25991)
>>> print('包含中文的str')
包含中文的str
>>> ord("中")
20013
>>> ord("中国")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: ord() expected a character, but string of length 2 found
>>> chr(20013)
'中'
>>> chr(25991)
'文'
>>> s = chr(20013) +chr(25991)
>>> s
'中文'
>>> t = '\u4e2d\u6587'
>>> s==t
True
Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。
Python对bytes类型的数据用带b前缀的单引号或双引号表示,要注意区分’ABC’和b’ABC’,前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节, 所能转换的字符是有限的.
- 以Unicode表示的str通过encode()方法可以编码为指定的bytes
- 纯英文的str可以用ASCII编码为bytes,内容是一样的
- 含有中文的str可以用UTF-8编码为bytes。
- 含有中文的str无法用ASCII编码,因为中文编码的范围超过了ASCII编码的范围,Python会报错。
- 在bytes中,无法显示为ASCII字符的字节,用\x##显示
- 反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法
- 如果bytes中包含无法解码的字节,decode()方法会报错
- 如果bytes中只有一小部分无效的字节,可以传入errors=’ignore’忽略错误的字节
- 在操作字符串时,我们经常遇到str和bytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str和bytes进行转换。
- Python当然也支持其他编码方式,比如把Unicode编码成GB2312.但这种方式纯属自找麻烦,如果没有特殊业务要求,请牢记仅使用UTF-8编码。
- len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数
- 1个中文字符经过UTF-8编码后通常会占用3个字节,而1个英文字符只占用1个字节。
>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
>>> b'\xe4\xb8\xad\xff'.decode('utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 3: invalid start byte
>>> b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
'中'
>>> s= '中文'.encode('gb2312')
>>> s
b'\xd6\xd0\xce\xc4'
>>> s.decode('utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuation byte
>>> s.decode('gb2312')
'中文'
>>> len(b'ABC')
3
>>> len('中文')
2
>>> len('ABC')
3
>>> len('中文'.encode('utf-8'))
6
由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。
申明了UTF-8编码并不意味着你的.py文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8 without BOM编码
BOM是Byte Order Mark的缩写,就是字节序标记。UTF-8不存在字节序的问题,UTF-8不需要BOM来表明字节顺序。
当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上如下的行:
# -*- coding: utf-8 -*-
字符串格式化
- c语言风格的格式化
%
- 有些时候,字符串里面的
%
是一个普通字符,这个时候就需要转义,用%%
来表示一个%
- 有些时候,字符串里面的
- 使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}, 占位符编号可以省略, 有格式定义时冒号不能省略,如
.2f
. 可以使用c语言风格定义输出格式 - f-string:变量名替代风格,使用以f开头的字符串,称之为f-string,它和普通字符串不同之处在于,字符串如果包含
{xxx}
,就会以对应的变量替换.这里依然可以使用c语言风格定义输出格式
c语言风格的格式化常见的占位符:
占位符 | 替换内容 |
---|---|
%d |
整数 |
%f |
浮点数 |
%s |
字符串 |
%x |
十六进制整数 |
>>> 'growth rate: %.2f %%' % 7
'growth rate: 7.00 %'
>>> 'Hello, {}, 成绩提升了 {:.2f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.12%'
>>> 'Hello, {:}, 成绩提升了 {:.2f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.12%'
>>> 'Hello, {0:}, 成绩提升了 {1:.2f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.12%'
>>> r = 2.5
>>> s = 3.14 * r ** 2
>>> print(f'The area of a circle with radius {r} is {s:.2f}')
The area of a circle with radius 2.5 is 19.62
元组 turple
不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。
tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来.
空的元组和一个元素的元组的定义:
turple_empty = ()
turple_one_item = (1,)
字典 dict
字典的get 方法存在的问题是, 对应的值本身有可能就是 None或者指定的值
保险的做法是:
- 用in判断是否存在key
- 如果存在, 再获取值: 用get方法或者 d[key]
教程中用get方法判断是否存在key是错误的.
# 定义字典
d = {}
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
d2 =dict()
# 判断 key 是否存在, 用in
print('Bob' in d) # True
print('Bob' in d.keys()) # True
print('bob' in d.keys()) # False
# 获取 key 对应的值: get()方法
# 如果存在,返回的是 key 对应的 value
# 如果key不存在,可以返回None,或者自己指定的value
print(d.get('Bob')) # 75
print(d.get('Bob',-1)) # 75
print(d.get('bob')) # None
print(d.get('bob',-1)) # -1
# 删除 key 和对应的值
# 方法1: d.pop(key): key 如果不存在会报错
# 方法2: del d[key]
try:
d.pop('Bob')
d.pop('Bob')
except KeyError as e:
print('KeyError')
print(e) # 仅打印错误提示信息, 不会打印错误类型
print(d) # {'Michael': 95, 'Tracy': 85}
try:
del d['Michael']
del d['Michael']
except KeyError as e:
print('KeyError')
print(e) # 仅打印错误提示信息, 不会打印错误类型
print(d) # {'Tracy': 85}
和list比较,dict有以下几个特点:
- 查找和插入的速度极快,不会随着key的增加而变慢. 貌似数据量很大时,插入会变慢?
- 需要占用大量的内存,内存浪费多。dict是用空间来换取时间的一种方法
而list相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
这是因为dict根据key来计算value的存储位置,这个通过key计算位置的算法称为哈希算法(Hash).
dict的key必须是不可变对象。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。
集合
set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。
s = set([1, 2, 3])
print(s)
# 通过add(key)方法可以添加元素到set中
s.add(100)
print(s)
# 通过remove(key)方法可以删除元素
s.remove(100)
print(s)
'''
{1, 2, 3}
{1, 2, 3, 100}
{1, 2, 3}
'''
不可变对象
对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
>>> s = 'abc'
>>> id(s)
1562739064432
>>> t = s.replace('a',"A")
>>> s
'abc'
>>> t
'Abc'
>>> id(s)
1562739064432
>>> id(t)
1562740560176
>>> i = 5
>>> id(i)
140713442805552
>>> i += 1
>>> i
6
>>> id(i)
140713442805584
>>> f = 5.0
>>> id(f)
1562740291920
>>> f += 1
>>> f
6.0
>>> id(f)
1562740292048
>>> aList = [1,2,3]
>>> id(aList)
1562740576832
>>> aList.append(5)
>>> aList
[1, 2, 3, 5]
>>> id(aList)
1562740576832
函数
1.函数别名
i = -3
print(abs(i)) # 3
# 函数名其实就是指向一个函数对象的引用
#完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”
f = abs
print(f(i)) # 3
2.默认参数
- Python函数在定义的时候,默认参数的值就被计算出来了,因为默认参数也是一个变量,它指向该参数对应的对象,每次调用该函数,如果改变了参数的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时最初的参数了。定义默认参数要牢记一点:默认参数必须指向不变对象!
- 为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
import traceback
# 数据类型检查可以用内置函数isinstance()实现
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
try:
my_abs('a')
except Exception as e:
traceback.print_exc() # 打印完整的错误信息
print(my_abs(-5.5)) # 5.5
def return_multi(a,b):
return a,b # 实际上这是定义了一个元组类型的返回值
print(return_multi(2,3)) # (2, 3)
# 函数默认参数有个最大的坑
def func(aList=[]):
aList.append('hello')
return aList
a=func()
print(a)
a=func()
print(a)
a=func()
print(a)
a=func()
print(a)
'''
['hello']
['hello', 'hello']
['hello', 'hello', 'hello']
['hello', 'hello', 'hello', 'hello']
'''
a=func(['hello'])
print(a) # ['hello', 'hello']
# 默认参数修改为不可变参数
def func2(aList=None):
if aList is None:
aList = []
aList.append('hello')
return aList
a=func2()
print(a)
a=func2()
print(a)
a=func2()
print(a)
a=func2()
print(a)
'''
['hello']
['hello']
['hello']
['hello']
'''
3. 可变参数,关键字参数
- 可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
*args
表示把args这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。- 可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
- 关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
- 关键字参数可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。
- 可以解包 dict, 传入关键字参数
def func_sum(*args): # *args 是惯用法, 也可以用 *其他名字 替代
s = 0
for item in args:
s += item
return s
print(func_sum()) # 0
print(func_sum(1,2,3)) # 6
print(func_sum(1,2,3,4)) # 10
def person(name, age, **kw):
return ('name:', name, 'age:', age, 'other:', kw)
print(person('Tom',39))
# ('name:', 'Tom', 'age:', 39, 'other:', {})
print(person('Adam', 45, gender='M', job='Engineer'))
# ('name:', 'Adam', 'age:', 45, 'other:', {'gender': 'M', 'job': 'Engineer'})
# 可以解包 dict, 传入关键字参数
extra = {'city': 'Beijing', 'job': 'Engineer'}
print(person('Jack', 24, **extra))
# ('name:', 'Jack', 'age:', 24, 'other:', {'city': 'Beijing', 'job': 'Engineer'})
'''
**extra表示把extra这个dict的所有key-value用关键字参数传入到
函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝
,对kw的改动不会影响到函数外的extra
'''
4.命名关键字参数
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如:
def person(name, age, *, city, job):
print(name, age, city, job)
- 和关键字参数
**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。 - 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符
*
了
def person2(name, age, *, city, job):
return (name, age, city, job)
print(person2('Jack', 24, city='Beijing', job='Engineer'))
# ('Jack', 24, 'Beijing', 'Engineer')
def person3(name, age, *args, city, job):
return (name, age, args, city, job)
print(person3('Sandy', 55, city='Nanjing', job='Teacher'))
# ('Sandy', 55, (), 'Nanjing', 'Teacher')
print(person3('Sandy', 55, 'test1','test2',city='Nanjing', job='Teacher'))
# ('Sandy', 55, ('test1', 'test2'), 'Nanjing', 'Teacher')
5.参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。所以,对于任意函数,都可以通过类似`func(*args, kw)`的形式调用它,无论它的参数是如何定义的。**
*args
是可变参数,args接收的是一个tuple;- “**kw`是关键字参数,kw接收的是一个dict。
- 使用
*args和**kw
是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。 - 命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
- 定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符
*
,否则定义的将是位置参数。
递归函数
如果一个函数在内部调用自身本身,这个函数就是递归函数。
- 优点: 理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
- 缺点: 太耗费内存; 容易溢出
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。
解决方法1: 可以用 Y-Combinator 构造解决: https://zhuanlan.zhihu.com/p/37060182
解决方法2: 修改python的默认递归深度
import sys
# 计算阶乘
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
print(fact(10)) # 3628800
try:
print(fact(1000))
except Exception as e:
print(e)
# maximum recursion depth exceeded in comparison
print(sys.getrecursionlimit()) # 1000, 有些情况下默认是其他值, 如3000
sys.setrecursionlimit(10**6)
print(fact(1000))
'''
402387260077093773543702433923003985719374864210714632543799910429938512398629020592044208486969404800479988610197196058631666872994808558901323829669944590997424504087073759918823627727188732519779505950995276120874975462497043601418278094646496291056393887437886487337119181045825783647849977012476632889835955735432513185323958463075557409114262417474349347553428646576611667797396668820291207379143853719588249808126867838374559731746136085379534524221586593201928090878297308431392844403281231558611036976801357304216168747609675871348312025478589320767169132448426236131412508780208000261683151027341827977704784635868170164365024153691398281264810213092761244896359928705114964975419909342221566832572080821333186116811553615836546984046708975602900950537616475847728421889679646244945160765353408198901385442487984959953319101723355556602139450399736280750137837615307127761926849034352625200015888535147331611702103968175921510907788019393178114194545257223865541461062892187960223838971476088506276862967146674697562911234082439208160153780889893964518263243671616762179168909779911903754031274622289988005195444414282012187361745992642956581746628302955570299024324153181617210465832036786906117260158783520751516284225540265170483304226143974286933061690897968482590125458327168226458066526769958652682272807075781391858178889652208164348344825993266043367660176999612831860788386150279465955131156552036093988180612138558600301435694527224206344631797460594682573103790084024432438465657245014402821885252470935190620929023136493273497565513958720559654228749774011413346962715422845862377387538230483865688976461927383814900140767310446640259899490222221765904339901886018566526485061799702356193897017860040811889729918311021171229845901641921068884387121855646124960798722908519296819372388642614839657382291123125024186649353143970137428531926649875337218940694281434118520158014123344828015051399694290153483077644569099073152433278288269864602789864321139083506217095002597389863554277196742822248757586765752344220207573630569498825087968928162753848863396909959826280956121450994871701244516461260379029309120889086942028510640182154399457156805941872748998094254742173582401063677404595741785160829230135358081840096996372524230560855903700624271243416909004153690105933983835777939410970027753472000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
'''
列表生成式
import os
print([x*x for x in range(1,11)])
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 使用两层循环,可以生成全排列
print([a + b for a in 'hello' for b in 'world!'])
# ['hw', 'ho', 'hr', 'hl', 'hd', 'h!', 'ew', 'eo', 'er', 'el', 'ed', 'e!', 'lw', 'lo', 'lr', 'll', 'ld', 'l!', 'lw', 'lo', 'lr', 'll', 'ld', 'l!', 'ow', 'oo', 'or', 'ol', 'od', 'o!']
# 列出当前目录下的所有文件和目录名
[d for d in os.listdir('.')] # os.listdir可以列出文件和目录
# for 后的 if 不能带 else
print([x for x in range(10) if x%2==0]) # [0, 2, 4, 6, 8]
# if else 可以单独用
print([x if x%2==0 else -x for x in range(10)])
[0, -1, 2, -3, 4, -5, 6, -7, 8, -9]
生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素。
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
- 要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的
[]
改成()
,就创建了一个generator- 可以通过next()函数获得generator的下一个返回值
- generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误
- 第二种方法: 如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator
- 最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
# 创建L和g的区别仅在于最外层的[]和()
# L是一个list,而g是一个generator。
L = [x for x in range(10)]
g = (x for x in range(10))
# 可以通过next()函数获得generator的下一个返回值
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
'''
0
1
2
3
4
5
6
7
8
'''
for i in g:
print(i)
# 9
# 之前已经循环过 0~8, 这里只打印9
# 斐波拉契数列(Fibonacci),
# 除第一个和第二个数外,任意一个数都可由前两个数相加得到:
# 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
# 斐波拉契数列用列表生成式写不出来,
# 但是,用函数把它打印出来却很容易
print('\n')
def fib(max):
# 输出斐波那契数列的前N个数
a,b,n = 0,1,0
while n < max:
n += 1
print(n,b)
a, b = b, a + b
return 'done'
print(fib(10))
'''
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34
10 55
done
'''
print('\n')
# fib函数实际上是定义了斐波拉契数列的推算规则
# 可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator
def fib2(max):
# 输出斐波那契数列的前N个数
a,b,n = 0,1,0
while n < max:
n += 1
yield b
a, b = b, a + b
for b in fib2(10):
print(b)
'''
1
1
2
3
5
8
13
21
34
55
'''
杨辉三角
'''
杨辉三角定义如下:
1
/ \
1 1
/ \ / \
1 2 1
/ \ / \ / \
1 3 3 1
/ \ / \ / \ / \
1 4 6 4 1
/ \ / \ / \ / \ / \
1 5 10 10 5 1
把每一行看做一个list,试写一个generator,不断输出下一行的list
# 期待输出:
# [1]
# [1, 1]
# [1, 2, 1]
# [1, 3, 3, 1]
# [1, 4, 6, 4, 1]
# [1, 5, 10, 10, 5, 1]
# [1, 6, 15, 20, 15, 6, 1]
# [1, 7, 21, 35, 35, 21, 7, 1]
# [1, 8, 28, 56, 70, 56, 28, 8, 1]
# [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
'''
def yhsj(num_line):
# num_line >=1
theList = [1]
temp_num_line = 1
while temp_num_line<=num_line:
yield theList
temp_num_line += 1
theList.append(1) # 长度加1, 最后一个值为1
# 从倒数第2个开始更新数值
if len(theList)>2:
for i in range(-2,-len(theList),-1):
theList[i] = theList[i] + theList[i-1]
for sj in yhsj(10):
print(sj)
'''
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
'''
g = yhsj(10)
print(g) # <generator object yhsj at 0x000001D7183F6AC0>
区分普通函数和generator函数:
- 普通函数调用直接返回结果
- generator函数的“调用”实际返回一个generator对象:
<generator object yhsj at 0x000001D7183F6AC0>
可迭代对象, 迭代器
我们已经知道,可以直接作用于for循环的数据类型有以下几种:
- 一类是集合数据类型,如list、tuple、dict、set、str等;
- 一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:
from collections.abc import Iterable
print(isinstance([], Iterable))#True
print(isinstance({}, Iterable))#True
print(isinstance('abc', Iterable))#True
print(isinstance((x for x in range(10)), Iterable))#True
print(isinstance(100, Iterable))# False
- 生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
- 可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
- 可以使用isinstance()判断一个对象是否是Iterator对象:
- 为什么list、dict、str等数据类型不是Iterator?这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
- 凡是可作用于for循环的对象都是Iterable类型;
- 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
- 集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
from collections.abc import Iterator
print(isinstance((x for x in range(10)), Iterator))
# True
print(isinstance([], Iterator))
# False
print(isinstance({}, Iterator))
# False
print(isinstance('abc', Iterator))
# False
# 生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator
# 把list、dict、str等Iterable变成Iterator可以使用iter()函数
print(isinstance(iter([]), Iterator))
# True
print(isinstance(iter({}), Iterator))
# True
print(isinstance(iter('abc'), Iterator))
# True
# Python的for循环本质上就是通过不断调用next()函数实现的
for x in [1, 2, 3, 4, 5]:
print(x)
'''
1
2
3
4
5
'''
# 以上for循环等价于:
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
print(x)
except StopIteration:
# 遇到StopIteration就退出循环
break
'''
1
2
3
4
5
'''
迭代器一般并不会改变执行结果, 只是在内存空间上做了优化.