同时获取列表的元素及其对应的下标
for index, value in enumerate(list_name):
循环
for value in ... : (此方式遍历list,tuple, dict, set, str, generator等元素时也称为迭代)
while condition:
可迭代对象(Iterable)
list,tuple, dict, set, str, generator
isinstance(x, Iterable) (判别是否为可迭代对象)
迭代器
生成器不但可以作用于for
循环,还可以被next()
函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了。
可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
可以使用isinstance()
判断一个对象是否是Iterator
对象
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
Python的for
循环本质上就是通过不断调用next()
函数实现的
例如:
for x in [1, 2, 3, 4, 5]:
pass
实际上完全等价于:
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
不变对象(字符串,整数等)
对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
列表是可变对象
字符编码
ASCII(只对英文字符和部分特殊字符编码,任何字符均采用1字节)->Unicode(任何字符均采用2字节)->UTF-8(可变长编码,即不同类型字符采用不同字节,如英文字符采用1字节,汉字采用3字节,只有特别生僻的汉字才采用4-6字节,从而大大减少了存储空间和传输成本)
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
对于单个字符的编码,Python提供了ord()
函数获取字符的整数表示,chr()
函数把编码转换为对应的字符。
注意区分'ABC'
和b'ABC'
,前者是str
,后者虽然内容显示得和前者一样,但bytes
的每个字符都只占用一个字节(存储或传输时使用,节约传输或存储成本。
纯英文的str
可以用ASCII
编码为bytes
,内容是一样的,含有中文的str
可以用UTF-8
编码为bytes
。含有中文的str
无法用ASCII
编码,因为中文编码的范围超过了ASCII
编码的范围,Python会报错。
在bytes
中,无法显示为ASCII字符的字节,用\x##
显示。
反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes
。要把bytes
变为str
,就需要用decode()
方法:
>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
如果bytes
中包含无法解码的字节,decode()
方法会报错:
>>> b'\xe4\xb8\xad\xff'.decode('utf-8')
Traceback (most recent call last):
...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 3: invalid start byte
如果bytes
中只有一小部分无效的字节,可以传入errors='ignore'
忽略错误的字节:
>>> b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
'中'
len()
函数计算的是
str
的字符数,如果换成
bytes
,
len()
函数就计算字节数。
在操作字符串时,我们经常遇到str
和bytes
的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str
和bytes
进行转换。
由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;
第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。
申明了UTF-8编码并不意味着你的.py
文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8 without BOM编码。
如果.py
文件本身使用UTF-8编码,并且也申明了# -*- coding: utf-8 -*-
,打开命令提示符测试就可以正常显示中文。
格式化字符串输出
%x | 十六进制整数 |
如果你不太确定应该用什么,%s
永远起作用,它会把任何数据类型转换为字符串
有些时候,字符串里面的%
是一个普通字符怎么办?这个时候就需要转义,用%%
来表示一个%
其中,格式化整数和浮点数还可以指定是否补0和整数与小数的位数:
e.g. print('%2d-%02d' % (3, 1))
print('%.2f' % 3.1415926)
Pyhton除法
a/b (一般除法,结果一定为浮点数,不管除数和被除数为何类型数)
a//b(地板除,结果一定只有整数部分,当除数和被除数均为int类型时为int型,当除数和被除数有一个为float型时为float型,但一定只有整数部分,小数部分为0)
a%b(取余运算,结果一定为整数)
Pyhton变量赋值过程
a = 'ABC' (系统创建一个字符串和变量a,并把a指向字符串'ABC')
b = a (系统创建变量b,并把变量b指向字符串'ABC')
a = 'XYZ' (系统创建字符串'XYZ',并把变量a指向'XYZ',但此时变量b还是指向字符串'ABC')
Pyhton变量本身类型不定,为动态语言
关闭字符的转义功能
如果字符串里面有很多字符都需要转义,就需要加很多\
,为了简化,Python还允许用r''
表示''
内部的字符串默认不转义,可以自己试试:
>>> print('\\\t\\')
\ \
>>> print(r'\\\t\\')
\\\t\\
字符串换行另一种方式
如果字符串内部有很多换行,用\n
写在一行里不好阅读,为了简化,Python允许用'''...'''
的格式表示多行内容:
>>> print('''line1
... line2
... line3''')
line1
line2
line3
Tuple
tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来,比如:
>>> t = (1, 2)
>>> t
(1, 2)
如果要定义一个空的tuple,可以写成()
:
>>> t = ()
>>> t
()
但是,要定义一个只有1个元素的tuple,如果你这么定义:
>>> t = (1)
>>> t
1
定义的不是tuple,是1
这个数!这是因为括号()
既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1
。
所以,只有1个元素的tuple定义时必须加一个逗号,
,来消除歧义:
>>> t = (1,)
>>> t
(1,)
Python在显示只有1个元素的tuple时,也会加一个逗号,
,以免你误解成数学计算意义上的括号。
“可变”Tuple
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])
这个tuple定义的时候有3个元素,分别是'a'
,'b'
和一个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?
别急,我们先看看定义的时候tuple包含的3个元素:
当我们把list的元素'A'
和'B'
修改为'X'
和'Y'
后,tuple变为:
表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a'
,就不能改成指向'b'
,指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!
理解了“指向不变”后,要创建一个内容也不变的tuple怎么做?那就必须保证tuple的每一个元素本身也不能变。
Python整数和浮点数均没有大小限制,但是浮点数超过一定范围会当做inf
字典(dict)
a = {'key1': 1, 'key2': 2}
dict内部存在的顺序与定义时的顺序是没有关系的
判断键是否存在
1、'key' is dict_name(返回True或False)
2、 dict_name.get('key', -1)(-1代表不存在时的指定返回值,不指定时返回None)
删除键及其对应的值
dict_name.pop('key')
字典和列表优缺点比较
字典查找速度快(时间),但占用存储空间大(空间)
列表查找速度慢(时间),但占用存储空间小(空间)
需要牢记的是dict的key必须是不可变对象。这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key。
集合(set)
满足确定性、无序性和互异性
s = ([1, 2, 3])
s.add(element)(添加元素到set)
s.remove(element)(删除set中某元素)
s.update([element1, element2,...])(添加多个元素到set)
不同set可以执行数学上集合的相关运算:
set1 & set2(交)
set1 | set2(并)
set1 - set2 (差)(即在set1中,但不在set2中的元素)
set1 ^ set2(对称差)(即在set1或set2中,但不会同时存在set1和set2的元素)
set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。
set可用来去除海量列表中的重复元素,即先将list转换为set,再讲set转换为list。
判断变量类型是否为设定类型
isinstance(x, (type1, type2,...))判断x类型是否为type1,type2,...其中的一种。
空函数
如果想定义一个什么事也不做的空函数,可以用pass
语句:
def nop():
pass
pass
语句什么都不做,那有什么用?实际上pass
可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass
,让代码能运行起来。
pass
还可以用在其他语句里,比如:
if age >= 18:
pass
缺少了pass
,代码运行就会有语法错误。
默认参数
定义默认参数要牢记一点:默认参数必须指向不变对象!
关键字参数
可变参数允许传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
命名关键字参数
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city
和job
作为关键字参数。这种方式定义的函数如下:
def person(name, age, *, city, job):
print(name, age, city, job)
和关键字参数**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。
调用方式如下:
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*
了:
def person(name, age, *args, city, job):
print(name, age, args, city, job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given
由于调用时缺少参数名city
和job
,Python解释器把这4个参数均视为位置参数,但person()
函数仅接受2个位置参数。
命名关键字参数可以有缺省值,从而简化调用:
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
由于命名关键字参数city
具有默认值,调用时,可不传入city
参数:
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*
作为特殊分隔符。如果缺少*
,Python解释器将无法识别位置参数和命名关键字参数:
def person(name, age, city, job):
# 缺少 *,city和job被视为位置参数
pass
Python函数返回多个参数其实是返回一个tuple
定义函数时,如果有必要,可以先对参数的数据类型做检查;切片
list, tuple, str均可进行切片处理。
[a:b:c]取索引为从a到b的元素,间隔为c。
生成器(generator)
g = (x for x in range(10)) (即将生成列表的[ ]换作( )即可)
如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator。
函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
举个简单的例子,定义一个generator,依次返回数字1,3,5:
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
调用该generator时,首先要生成一个generator对象,然后用next()
函数不断获得下一个返回值:
>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
可以看到,odd
不是普通函数,而是generator,在执行过程中,遇到yield
就中断,下次又继续执行。执行3次yield
后,已经没有yield
可以执行了,所以,第4次调用next(o)
就报错。
用for
循环调用generator时,发现拿不到generator的return
语句的返回值。如果想要拿到返回值,必须捕获StopIteration
错误,返回值包含在StopIteration
的value
中:
>>> g = fib(6)
>>> while True:
... try:
... x = next(g)
... print('g:', x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done