廖雪峰的python教程(1)

教程网址: 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, 节省空间.

<p>廖雪峰的python教程(1)</p>

<p>廖雪峰的python教程(1)</p>

在最新的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
'''

迭代器一般并不会改变执行结果, 只是在内存空间上做了优化.

正文完
 
评论(没有评论)