目录
一、基础知识
1、数和表达式
浮点除法和整数除法
1/2 # 浮点除法
0.5
1//2 # 整数除法,直接扔掉小数
0
5//2.4
2.0
负数整除和取余
10 // -3 # 核心:向下圆整,因此离0更远,是-4而不是-3
-4
10 % -3 # 通过整除,剩余部分就是-2
-2
圆整
round(2/3) # 圆整到最接近的整数
1
round(1.5) # 一样近,圆整到偶数
2
乘方运算
-3 ** 2
-9
(-3) ** 2
9
pow(-3,2)
9
2、变量名
Python的标识符只能由字母、数字和下划线构成,不能以数字打头。
3、获取用户输入
name = input("Your name is: ")
Your name is: Ryan
name
'Ryan'
4、模块
以ceil()函数为例,这是一个向上圆整函数(与floor和整除相反)。需要导入math模块。
import math
math.ceil(32.3)
33
麻烦的是,每次都需要加上前缀math.。另一种导入模块方法可以避免前缀:
from math import ceil
ceil(33.3)
34
唯一需要注意的是,必须保证导入的不同模块不存在同名函数。否则还是加上前缀吧,或者赋予别名:
from math import sqrt as foobar
foobar(25)
5.0
可以使用变量来引用函数:
import math
foo = math.sqrt
foo(9.3)
3.0495901363953815
cmath提供复数运算函数:
import cmath
cmath.sqrt(-1)
1j
注意:这里没有使用from..import...语法。因为这会掩盖掉math.sqrt()函数。
5、让脚本像普通程序一样
在UNIX或Linux环境下,脚本的第一行添加:
#!/usr/bin/env python
6、字符串
单、双引号
同时支持单引号和双引号,双引号内可以有一撇,单引号内也可以有两撇:
"Let's go!"
"Let's go!"
'"Hello world!"she said'
'"Hello world!"she said'
引号的转义
还可以对引号进行转义,且在有些情况下必须这么做。如:
'Let\'s say "Hello,world!"'
'Let's say "Hello,world!"'
'Let\'s go!' # 打印时有变化
"Let's go!"
字符串拼接和print
字符串拼接也可以避免转义:
"Let's say"+'"Hello,world!"'
'Let's say"Hello,world!"'
Python打印字符串时,会用引号将其括起,保留其在代码中的样子。使用print可以避免:
print('Hello world!')
Hello world!
换行符的差异更明显:
"Hello,\nworld!"
'Hello,\nworld!'
print('Hello,\nworld!')
Hello,
world!
需要同时打印多个字符串时,可以使用逗号分隔,而不需要拼接:
name='Ryan'
action='scored'
score='100'
print(name,action,score,'.')
Ryan scored 100 .
问题是引入了空格。以下是解决方案:
print(name,action,score+'.')
Ryan scored 100.
print甚至可以自定义分隔符和结束符(默认是换行符):
print('Hello','world',sep='!',end='') # 在脚本中输出不换行,继续当前行输出
Hello!world
差异的本质
本质上,前者用的是repr函数,后者用的是str类,返回值不同。测试:
print(repr("Hello,\nworld!"))
'Hello,\nworld!'
print(str("Hello,\nworld!"))
Hello,
world!
长字符串
要表示很长的字符串(跨越多行),可以用三引号:
print('''This is a very long string.
Hello~
my world!''')
This is a very long string.
Hello~
my world!
此时,字符串本身可以包含单、双引号而无需用反斜杠转义。
常规字符串也可以跨越多行,但需要在换行符前转义,解释器即可忽略该换行符:
1+2+\
3
6
print('Hello,\
world')
Hello,world
原始字符串
如果字符串中有大量需要转义的符号,那么反斜杠会很多。为此,我们采用原始字符串形式输出:
print(r'C:\nb\bar\foo')
C:\nb\bar\foo
print('C:\nb\bar\foo')
C:
baroo
有一个例外:最后一个字符不能是反斜杠:
print(r'This is illegal\')
File "<ipython-input-3-27a1f564a7ec>", line 1
print(r'This is illegal\')
^
SyntaxError: EOL while scanning string literal
如果进行转义,虽然不会报错,但会保留转移:
print(r'This is legal\\')
This is legal\
解决方法是:将反斜杠单独作为一个常规字符串:
print(r'C:\Program Files\foo\bar' '\\')
C:\Program Files\foo\bar
二、列表和元组
数据结构:以某种方式组合起来的数据元素的集合。
Python支持一种数据结构的基本概念:容器container——可包含其他对象的对象。
两种主要的容器:序列sequence(如列表和元组)和映射(如字典)。还有一种容器:集合set。
举个列表例:
Ryan= ['Ryan Xing',21]
Cathy=['Cathy Lee',21]
database=[Ryan,Cathy]
database
[['Ryan Xing', 21], ['Cathy Lee', 21]]
1、通用的序列操作
索引
序列中的元素是从0递增的。
注意:Python中没有专门用于表示字符的类型,因此一个字符也是字符串。
还可以用负数索引,-1表征最右元素:
greeting='Hello'
greeting[-1]
'o'
对于字符串字面量,可以直接索引,而无需先赋给一个变量:
'Hello'[-2]
'l'
如果调用某函数,返回一个序列,可以直接索引:
fourth = input('Year: ')[3]
Year: 2018
fourth
'8'
切片
索引用来访问单个元素,切片用来访问特定范围内的元素:
numbers = [0,1,2,3,4,5]
numbers[0:2]
[0, 1]
注意,第二个索引numbers[2]不包括在内。
如果一定要访问到最后一个元素,可以这么操作:
numbers[2:6]
[2, 3, 4, 5]
注意,numbers[6]不存在,但这样做达到了目的。
当然有个更简单的办法:
numbers[2:]
[2, 3, 4, 5]
如果从序列第一个元素开始索引,也可以忽略:
numbers[:2]
[0, 1]
复制整个序列:
numbers[:]
[0, 1, 2, 3, 4, 5]
当索引为负时,左边索引对应序列左边的数,右边索引虽然也对应最右数,但不在结果内:
numbers[-5:-2]
[1, 2, 3]
更大的步长
之前的切片操作实际上省略了第三个参数:步长。
如果想每3个元素中取1个元素,即每隔2个元素取1个,则设步长为3:
numbers[::3]
[0, 3]
当然步长可以为负,向左提取元素:
numbers[::-2]
[5, 3, 1]
序列相加
注意:不同类型的序列不能相加。
[1,2,3]+[4]
[1, 2, 3, 4]
序列乘法(复制序列)
'Python' * 3
'PythonPythonPython'
成员资格
检查用户名和密码例:
database=[
> ['Ryan','1111'],
> ['Cathy','7777']
]
username=input('username: ')
username: Ryan
pin=input('pin: ')
pin: 1111
if[username,pin] in database: print('Access granted!')
Access granted!
2、列表详解
函数list(实际上是一个类)
可以通过函数list来创建list,对象可以是任何序列:
list('Hello')
['H', 'e', 'l', 'l', 'o']
list((1,2))
[1, 2]
修改(赋值)和删除元素
虽然list可修改,但我们不可以对不存在的元素赋值!我们需要用到列表方法。
x=[1,2,3]
x[3]=4
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-5-46f943026b50> in <module>()
1 x=[1,2,3]
----> 2 x[3]=4
IndexError: list assignment index out of range
del x[2]
x
[1, 2]
给切片赋值
name = list('Perl')
name[1:] = list('ython')
name
['P', 'y', 't', 'h', 'o', 'n']
还可以插入新元素:
numbers=[1,5]
numbers[1:1]=[2,3,4]
numbers
[1, 2, 3, 4, 5]
a=[2,3]
b=[1]
a[2:]=b
a
[2, 3, 1]
反过来就是删除元素:
numbers[2:]=[]
numbers
[1, 2]
综上,切片插入解决了赋值不能实现的功能,且切片删除和del是等效的。
列表的方法
添加元素到列表末尾:
lst=[1,2,3]
lst.append(4)
lst
[1, 2, 3, 4]
同时附加多个元素:
a=[1,2,3]
b=[4,5,6]
a.extend(b)
a
[1, 2, 3, 4, 5, 6]
拼接返回的是新序列,而这里仍返回a。
清空列表:
lst.clear()
lst
[]
有问题的复制:
a=[1,2,3]
b=a # 这样只是a和b同时指向一个列表
b[1]=4
a
[1, 4, 3]
创造副本:
a=[1,2,3]
b=a.copy()
b[1]=4
a
[1, 2, 3]
等效做法:
b=a[:]
c=list(a)
统计频次:
['to','o','world','0'].count('world')
1
查找指定值第一次出现的索引:
num=[1,2,3]
num.index(2)
1
将一个对象插入列表,功能和切片类似,但可读性更强:
num=[1,2,3]
num.insert(2,'ins')
num
[1, 2, 'ins', 3]
从列表删除一个元素,缺省即最后一个元素:
num=[1,2,3,4]
num.pop()
num
[1, 2, 3]
num.pop(0)
num
[2, 3]
实际上append就代替了push的功能,因此x.append(x.pop())是不变操作。
删除第一个为指定值的元素:
num=[2,3,1,2,1,1,1]
num.remove(1)
num
[2, 3, 2, 1, 1, 1]
不同于pop,remove不返回值、就地修改。pop是唯一就地修改且返回非None值的列表方法。
反向排列列表中的元素:
num=[1,2,3]
num.reverse()
num
[3, 2, 1]
如果要返回迭代器,可以使用reversed函数:
num=[1,2,3]
o=reversed(num)
next(o)
3
对序列就地排序:
num=[1,3,2]
num.sort()
num
[1, 2, 3]
容易产生的问题是,sort返回值为None,并且是就地修改。如何保存成副本而不修改原序列?
num=[1,3,2]
y=num.sort()
print(y)
None
实验说明,sort返回None,因此这种方法是不可取的。最好的做法就是先copy,再排序。
我们还可以用sorted函数,这样就可以返回一个新列表了:
num=[1,3,2]
sorted(num)
[1, 2, 3]
num
[1, 3, 2]
sort方法还可以接受参数key和reverse。有时可以把key设置成自定义参数,很有用。
将key设置成一个用于排序的函数,通过其对每一个元素的返回值,最终进行排序。如按长度排序:
x=['Ryan','Xing','Tim','Cathy']
x.sort(key=len)
x
['Tim', 'Ryan', 'Xing', 'Cathy']
reverse参数设置成True或False即可:
num=[1,2,3,4,5]
num.sort(reverse=True)
num
[5, 4, 3, 2, 1]
以上注意:sorted返回序列,而reverse返回可迭代对象。
3、元组tuple
元组用小括号括起来。只有一个元素的元组必须这么写,避免数学表达式的歧义:
(42,)
(42,)
也有转换任意序列至tuple的函数(实际上是类):
tuple([1,2,3])
(1, 2, 3)
tuple('abc')
('a', 'b', 'c')
tuple可以用作映射的key,而列表不行。
tuple和list使用方法类似,除了index和count等方法不可用。
三、字符串
1、字符串是不可变的
所有标准序列操作都适用于字符串。
但是要注意:字符串是不可变的,因此赋值和切片赋值是非法的!
website='http://www.python.org'
website[-3:]='com'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-1-2a5d4b796c2b> in <module>()
1 website='http://www.python.org'
----> 2 website[-3:]='com'
TypeError: 'str' object does not support item assignment
2、快速设置字符串格式
我们学习如何将值转换成字符串。Python中提供了许多方法,其中最主要的是%方法:
format = "Hello, %s. %s enough for ya?"
values = ('world','Hot')
format % values
'Hello, world. Hot enough for ya?'
其中的%s称为转换说明符。如果value不是字符串,将使用str将其转换为字符串。因此%s是万金油。
"Hello, %s. %s enough for ya?" % ('world','Hot')
'Hello, world. Hot enough for ya?'
还可以用format方法,更灵活:
"{3} {0} {2} {1} {3} {0}".format("be","not","or","to")
'to be or not to be'
from math import pi
"{name} is approximately {value:.2f}.".format(value=pi,name="π")
'π is approximately 3.14.'
如果只是值,还可以简化,前面加一个f:
from math import pi
f"We introduce {pi:.2f}"
'We introduce 3.14'
还有许多更精细、更复杂的设置方法,比如文字位置等,参见P43。
3、字符串方法
字符串的方法要比列表的方法多得多。
在早期Python中,这些方法多为模块string中的函数。我们现在学习方法,更简单。
center
"test".center(10)
' test '
"test".center(20)
'> > test> > '
"test".center(20,'*')
'********test********'
find
find在字符串中寻找子串。如果找到,就返回子串的第一个字符的索引。
"Ryan".find('an')
2
我们说过,in也可以检查是否为成员。区别是:in返回布尔值。
subject = '$$ Get rich now!!! $$'
'Ge'in subject
True
没有找到就返回-1:
subject.find('*')
-1
搜索可以指定起点(和终点,注意终点也是不包括的,这是Python惯例):
subject.find('$',2)
19
subject.find('$',2,19)
-1
join
join用于合并字符串。不同于直接相加,join方法比较灵活:
'+'.join(['1','2','3'])
'1+2+3'
'/'.join(['','usr','bin','env'])
'/usr/bin/env'
print('C:'+'\\'.join(['','usr','bin','env'])) # 注意//是转义操作,不是加双斜杠
C:\usr\bin\env
split
split与join相反:
'/usr/bin/env'.split('/')
['', 'usr', 'bin', 'env']
'Hello world'.split() # 缺省为空格
['Hello', 'world']
lower
name = input('Your name is:')
Your name is:Ryan
if name.lower() in ('ryan','cathy'): print('Found it!')
Found it!
replace
'This is a test'.replace('is','emmm')
'Themmm emmm a test'
translate
translate也用于替换,缺点是只能替换字符,优点是可以同时处理多个字符,并且效率比replace高。
先要建立转换表:
table=str.maketrans('cs','kz') # c变成k,s变成z
'this is an incredible test'.translate(table)
'thiz iz an inkredible tezt'
甚至还可以增加第三个参数,决定删除谁。比如删除t:
table=str.maketrans('cs','kz','t')
'this is an incredible test'.translate(table)
'hiz iz an inkredible ez'
strip
' This is a test '.strip()
'This is a test'
这个功能特别有用。比如输入用户名的时候,容易多加空格。
还可以指定两端删除的内容:
'***name**is**xing***'.strip('*')
'nameisxing'
'***name**is**xing!!!***'.strip('*!')
'nameisxing'
判断字符串是否满足特定条件
'10'.isdigit()
True
' '.isspace()
True
'Ad'.isupper()
False
'AD'.isupper()
True
四、字典
映射mapping是可以通过名称来访问其值的数据结构。
字典是Python中唯一的内置映射类型,其值不按顺序存储,而是存在key下。
1、创建字典
phonebook={'Ryan':'1234','Cathy':'4321'}
phonebook
{'Ryan': '1234', 'Cathy': '4321'}
item=[('Ryan','1234'),('Cathy','4321')]
phonebook=dict(item) # 看着像函数,实际上和list,tuple,str一样,是类的方法
phonebook
{'Ryan': '1234', 'Cathy': '4321'}
还可以用关键字实参创建:
d=dict(name='Ryan',age=21)
d
{'name': 'Ryan', 'age': 21}
2、字典基本操作
字典中的键可以任何不可变类型,如浮点数,整数,字符串或元组。
前面学过,列表是不可以直接给不存在的元素赋值的,而必须通过append或其他方法;但字典可以:
x={}
x[1.1]='test'
x
{1.1: 'test'}
成员资格:
x={}
x[1.1]='test'
1.1 in x.keys()
True
'test' in x.values()
True
其他基本操作:
x[1.1]
'test'
x[1.1] = 'test2'
x
{1.1: 'test2'}
del x[1.1]
x
{}
字典是可以嵌套的:
phonebook={'Ryan':{'age':21,'city':'Beijing'},'Cathy':{'age':21,'city':'Nanjing'}}
phonebook
{'Ryan': {'age': 21, 'city': 'Beijing'},
'Cathy': {'age': 21, 'city': 'Nanjing'}}
phonebook['Ryan']['city']
'Beijing'
3、字符串格式设置功能应用
data={'title':'Home Page','text':'Welcome!'}
template='''{title}
{text}
repeat
{text}
{title}'''
print(template.format_map(data))
Home Page
Welcome!
repeat
Welcome!
Home Page
4、字典方法
clear
clear用于清空字典,就地执行,返回None。
如果两个变量同时指向一个字典,clear方法和其他方法有些不同:
x={}
y={}
x[1]='test'
y=x
x.clear()
y
{}
x={}
y={}
x[1]='test'
y=x
x={}
y
{1: 'test'}
由上例,给x赋空集,没有改变y。但clear改变了。
copy
简单的copy会执行浅复制:如果替换副本中的值,原件不受影响;但如果是修改,那么原件受影响:
book={'name':'Ryan','tag':['good','handsome']}
book2=book.copy()
book2['name']='Cathy'
book['name'] # 替换,原件不受影响
'Ryan'
book={'name':'Ryan','tag':['good','handsome']}
book2=book.copy()
book2['tag'].remove('good')
book['tag'] # 修改,原件受影响
['handsome']
若希望副本完全与原件无关,则需要调用copy模块中的深复制函数deepcopy:
from copy import deepcopy
book={'name':'Ryan','tag':['good','handsome']}
book2=deepcopy(book)
book2['tag'].remove('good')
book['tag']
['good', 'handsome']
fromkeys
fromkeys创建字典,可以统一初始化:
book=dict.fromkeys(['name','age']) # 默认为None
book
{'name': None, 'age': None}
book=dict.fromkeys(['name','age'],'unknown')
book
{'name': 'unknown', 'age': 'unknown'}
get
用get访问字典中不存在的项,不会发生异常,只会返回None或指定值:
d={}
print(d.get('name'))
None
d={}
d.get('name',404) # 指定值404
404
setdefault
setdefault方法有点像get,但当访问项不存在时,可以添加指定项:
d=dict([('Ryan',99),('Cathy',100)])
d.setdefault('Lee',60)
d
{'Ryan': 99, 'Cathy': 100, 'Lee': 60}
d.setdefault('Ryan',110)
99
d
{'Ryan': 99, 'Cathy': 100, 'Lee': 60}
items, keys and values
返回 字典视图:项,字典视图:键 和 字典视图:值。
d=dict([('Ryan',99),('Cathy',100)])
it=d.items()
it
dict_items([('Ryan', 99), ('Cathy', 100)])
len(it)
2
d.values()
dict_values([99, 100])
pop
d=dict([('Ryan',99),('Cathy',100)])
d.pop('Cathy')
100
d
{'Ryan': 99}
update
用一个字典x更新另一个字典d,添加d没有的项,替换d中同key项:
d=dict([('Ryan',99),('Cathy',100)])
x=dict([('Ryan',100),('Lee',60)])
d.update(x)
d
{'Ryan': 100, 'Cathy': 100, 'Lee': 60}
五、条件、循环及其他语句
1、赋值魔法
序列解包
x,y,z = 1,2,3
x,y=y,x
print(x,y,z)
2 1 3
d=dict([('Ryan',99),('Cathy',100)])
name,score=d.popitem()
print(name,score)
Cathy 100
左右两边的数目必须严格相同。为了简化,我们可以采用*:
a,b,*rest=[1,2,3,4,5]
rest
[3, 4, 5]
a,*b,c,d=[1,2,3,4,5]
b
[2, 3]
a,*b,c,d="1234" # 注意*变量总是一个序列
b
['2']
链式赋值
x=y=1
增强赋值
test='foo'
test *=2
test
'foofoo'
2、条件和条件语句
布尔值
用作布尔bool表达式时,以下值都被解释器视为假:
False None 0 "" () [] {}
其他值都被视为真。
False == 0
True
但注意:
()==0
False
甚至可以用来转换:
bool(None)
False
and逻辑运算时,如果条件为假,返回的是第一个“假”的值:
1 and ""
''
or同理:
None or 1
1
条件表达式
name="Xing Tim"
status="friend" if name.endswith("Tim") else "stranger"
status
'friend'
综合
name="Ryan Xing"
if name.endswith('Xing'):
> if name.startswith('Mr.'):
> > print('Hello, Mr.Xing')
> elif name.startswith('Mrs.'):
> > print('Hello, Mrs.Xing')
> else:
> > print('Hello, Xing')
else:
> print('Hello~')
Hello, Xing
链式比较
x=1
0<=x<=2
True
相同运算符is
x=[1,2,3]
y=[1,2,3]
x==y
True
x=[1,2,3]
y=[1,2,3]
x is y
False
区别在于,== 检查的是值是否相同,而is检查的是两个对象是否相同。显然,x和y是独立的变量,分别指向各自的列表。
警告:不要将is用于不可变量,如数字和字符串。否则输出不可预测。
字符串比较
字符串比较根据的是码点序:
'alpha'<'beta'
True
虽然看着是字典序,但遇到大小写就会出现问题:
'a'<'B'
False
序列比较
[2,[1,4]]<[2,[1,5]]
True
[2,1]<[1,2]
False
断言
断言是一个好东西。当不满足条件时,我们可以人为使程序中断并报错,避免后续崩溃:
age = int(input('Your age is: '))
assert 0 < age <= 100, 'The age must be realistic' # 提示语句可以缺省
Your age is: -1
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-125-01e571e58487> in <module>()
1 age = int(input('Your age is: '))
----> 2 assert 0 < age <= 100, 'The age must be realistic' # 提示语句可以缺省
AssertionError: The age must be realistic
3、循环
while循环
name=''
while not name:
> name=input('Please enter your name: ')
print('Hello,{}!'.format(name))
Please enter your name:
Please enter your name:
Please enter your name: Ryan
Hello,Ryan!
上述程序如果输入的是空格,就会被接受了。因此可以改成:
name=''
while not name.strip():
> name=input('Please enter your name: ')
print('Hello,{}!'.format(name))
Please enter your name:
Please enter your name:
Please enter your name: Ryan
Hello,Ryan!
for循环
while循环已经足够强大了。当我们希望对可迭代对象中的每一个元素执行程序时,for循环更强大:
names=['Ryan','Xing']
for name in names:
> print(name)
Ryan
这里引入一个创建范围的内置函数range:
list(range(0,10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
迭代字典
d={'x':1,'y':2}
for key in d:
> print(key,'is',d[key])
x is 1
y is 2
d={'x':1,'y':2}
for key,value in d.items():
> print(key,'is',value)
x is 1
y is 2
注意:由于字典输出顺序不定,因此该循环输出的顺序也是不确定的。
如果顺序很重要,可以先输出到列表,再对列表排序,最后输出。
并行迭代
如果想同时迭代两个序列,最直接的办法是:
x=[1,2]
y=[3,4]
for i in range(len(x)):
> print(x[i],y[i])
1 3
2 4
Python提供了一个很有用的并行迭代工具zip:
x=[1,2]
y=[3,4]
list(zip(x,y))
[(1, 3), (2, 4)]
for i,j in zip(x,y):
> print(i,j)
1 3
2 4
如果长度不同,zip遵循最短原则:
list(zip([1,2],[1]))
[(1, 1)]
迭代时获取索引
我们知道,for迭代时不需要索引,而只根据list的值。如果我们想得到索引:
strings=['bad','xxx','good']
index=0
for string in strings:
> if 'x' in string:
> > strings[index]='censored'
> index += 1
strings
['bad', 'censored', 'good']
Python提供了内置的enumerate函数解决这一问题:
strings=['bad','xxx','good']
for index, string in enumerate(strings):
> if 'x' in string:
> > strings[index]='censored'
strings
['bad', 'censored', 'good']
index完全不需要初始化和增量计数,而正是list的索引。
跳出循环
break和continue就不赘述了。这里介绍while True/break。
while True:
> word = input('Enter a word: ')
> if word:
> > break
print(word)
Enter a word:
Enter a word: Ryan
Ryan
简单推导
列表推导的原理与for循环类似,但更简洁:
[x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[x**2 for x in range(10) if x%2==0]
[0, 4, 16, 36, 64]
[(x,y) for x in range(3) for y in [4,5,6]]
[(0, 4), (0, 5), (0, 6), (1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6)]
寻找同姓者:
boys=['chris','arnold','bob']
girls=['alice','bernice','clarice']
[b+' and '+g for b in boys for g in girls if b[0]==g[0]]
['chris and clarice', 'arnold and alice', 'bob and bernice']
如果用圆括号代替方括号,得到的将是生成器。后叙。
如果用花括号代替,可以实现字典推导:
squares={i:"{} squared is {}".format(i,i**2) for i in range(10)}
squares
{0: '0 squared is 0',
1: '1 squared is 1',
2: '2 squared is 4',
3: '3 squared is 9',
4: '4 squared is 16',
5: '5 squared is 25',
6: '6 squared is 36',
7: '7 squared is 49',
8: '8 squared is 64',
9: '9 squared is 81'}
4、其他语句
pass
pass充当占位符,让代码不至于报错。
垃圾收集
a=dict(name='Cathy',age=21)
a=1
此时,这个字典没有变量指向,将会被自动删除。
另一种办法是del语句。注意删除的是变量,而不是指向的实体(值):
a=dict(name='Cathy',age=21)
b=a
del a
b
{'name': 'Cathy', 'age': 21}
事实上,你没有任何办法删除一个值,也没有必要。Python会自动删除。
六、函数和参数
计算机本身喜欢具体而明确的指令,但人通常不是这样的。抽象使得程序更具有可读性。
1、自定义函数
一般而言,要判断某个对象是否可调用,可使用内置函数callable:
from math import sqrt as y
callable(y)
True
定义函数使用def语句:
def hello(name):
return "Hello, "+name+'!'
hello('Ryan')
'Hello, Ryan!'
放在函数开头的字符串称为文档字符串docstring,很有必要,一般说明函数功能:
def square(x):
'Calculate the square of the number x.'
return x**2
square.__doc__
'Calculate the square of the number x.'
help(square)
Help on function square in module main:
square(x)
Calculate the square of the number x.
return后如果缺省,或直接没有写return,将默认返回None:
def test():
pass
print(test())
None
2、参数魔法
关键字参数和默认值
前面学过的都是最简单的位置参数。下面的参数都可以忽略位置,有时更好用。
def info(name, age):
print('%s is %d.' % (name,age))
info(age=21,name='Ryan')
Ryan is 21.
以上两个参数称为关键字参数,优点是澄清参量作用,而且不强调输入顺序。
还可以使用默认值,简化输入:
def info(name, age=21):
print('%s is %d.' % (name,age))
info(name='Ryan')
Ryan is 21.
收集参数
优势在于不限数目:
def summ(*nums):
sum=0
for i in range(len(nums)):
sum+=nums[i]
return sum
summ(1,2,3)
6
注意nums实际上是一个元组,是自动生成的:
def summ(*nums):
print(nums)
summ(1,2,3)
(1, 2, 3)
收集参数可以放在任意位置,但其后的参数必须注明名称,否则会报错:
def summ(a,*nums,c):
print(nums)
summ(1,2,c=3)
(2,)
收集参数不能为关键字参数,但加两个星可以收集,变成字典而不是元组:
def print_params(__params):
print(params)
print_params(x=1,y=2,z=3)
{'x': 1, 'y': 2, 'z': 3}
分配参数
星号放在形参里,用于收集参数;但放在其他地方,用于分配参数(反作用):
def add(x,y):return x+y
params=(1,2)
add(*params)
3
同理,用两个星号可以将字典的值取出来分配:
def sayhi(greeting,name): return greeting+' '+name+'!'
params={'name':'Ryan','greeting':'Hi'}
sayhi(__params)
'Hi Ryan!'
强烈推荐使用这种拆分运算符!这样使得我们的函数输入变得非常灵活,也是常用的做法。
3、 作用域
变量可以理解为“指向某个值的名称”,和字典中的key类似。
这种看不见的关系,我们称之为命名空间或作用域。
除了全局作用域外,每个函数调用时都会创建一个域。因此函数内部重新关联参数时,函数外部的变量不受影响。
并且,参数与全局变量同名是没问题的。但此时我们无法直接访问同名的全局变量,因为已经被遮盖了。否则就要用globals函数:
def print_combine(para): return para+globals()['para']
para='Xing'
print_combine('Ryan')
'RyanXing'
同理,如果要在函数内访问、修改全局变量,可以声明:
def change():
global x
x+=1
x=0
change()
x
1
注意,尽管存在作用域,但如果关联的是可变量如list,结果会很危险:
def change(n):
n[0]='Gumby'
names=['Ryan','Tim']
change(names)
names
['Gumby', 'Tim']
函数内部修改names,居然把外部的names也改了!问题在于实际执行的是:
n=names
变量n虽然在局部作用域,但指向的值和names指向的是同一个。因此就被修改了。
进一步说明,对于不可变类型,传递到函数内的是值;对可变类型,传递的是变量名。
此时,我们可以用创建副本的方式,避免指向同一值:
def change(n):
n[0]='Gumby'
names=['Ryan','Tim']
change(names[:])
names
['Ryan', 'Tim']
如果希望不可变参数发生变化,有以下两种做法。比如加1函数:
def inc(x): return x+1
foo=10
foo=inc(foo) # 实际上是变量更新
foo
11
def inc(x): x[0]=x[0]+1 # 放到list中,自动更新
foo=[10]
inc(foo)
foo
[11]
最后,作用域是可嵌套的。这在函数创建函数时很管用:
def mul(fac):
fac2=2
def mulbyfac(num):
return num*fac*fac2
return mulbyfac
这里,mul返回的是mulbyfac函数。函数嵌套有什么好处呢?子函数返回时,不仅返回值,还携带环境,因此变量是携带着的。
因此,当调用mulbyfac时,不仅使用num,还可以用到父函数的环境fac2和fac:
func1=mul(3)
func1(4)
24
像mulbyfac这样,存储其所在作用域的函数,称为闭包。
4、递归
大多数情况下,使用循环的效率更高;但递归的可读性更好。
我们以二分查找为例。bisect模块提供了标准的二分查找实现,这里我们输入两个参数:目标和范围,输出搜索次数。
def search(time,tar,lower,upper):
middle=(upper+lower)//2
if middle==tar:
return time
elif tar>middle:
time+=1
return search(time,tar,middle,upper)
else:
time+=1
return search(time,tar,lower,middle)
search(1,1,0,99) # 在0到100之间找99
6
5、函数式编程
Python提供了map,filter和reduce有助于函数式编程。前两者都可以用列表推导代替,不常用:
list(map(str,range(6)))
['0', '1', '2', '3', '4', '5']
[str(i) for i in range(6)]
['0', '1', '2', '3', '4', '5']
二者等价,后者更简单。
filter根据布尔函数返回值,对元素进行过滤。一般需要自行编写布尔函数:
def func(x):
return x.isalnum() # 检测字符串是否由数字和字母组成
seq=['foo','123','?']
list(filter(func,seq))
['foo', '123']
seq=['foo','123','?']
[x for x in seq if x.isalnum()==True]
['foo', '123']
显然列表推导更简单,不需要另写函数。
Python还提供了lambda表达式的功能编写匿名函数,主要供这三个功能使用:
seq=['foo','123','?']
list(filter(lambda x:x.isalnum(),seq))
['foo', '123']
但可读性明显不如列表推导。所以这两个功能还是用列表推导吧!
reduce函数不容易被替换。reduce可以将列表前两个元素用于运算,结果再与第三个元素运算……:
nums=[1,2,3,4,5]
from functools import reduce
reduce(lambda x,y:x+y, nums)
15
当然该例用内置sum更简单。
七、面向对象
对象:一系列数据(属性)以及一套访问和操作这些数据的方法。
对象的优势:多态(对不同类型的对象执行相同的操作),封装(对外隐藏细节)和继承(基于通用类创建出专用类)。
1、多态和方法
方法:与对象属性相关联的函数。
实际上我们接触过多态:
print('abc'.count('a'))
print([1,2,'a'].count('a'))
1
1
from operator import add
print(add(1,2))
print(add('a','b')) # 和+等价
3
ab
但请注意,这些对象必须支持它们之间的加法,因此以下操作是非法的:
add(1,'a')
TypeError Traceback (most recent call last)
in ()
----> 1 add(1,'a')TypeError: unsupported operand type(s) for +: 'int' and 'str'
事实上,很多函数和运算符都是多态的,个人编写的函数也是。唯一破坏多态的方法,就是使用诸如type,issubclass等函数进行检查。
值得一提的是,引入本章后面要讨论的抽象基类和模块abc后,函数issubclass也将是多态的!
多态形式又称为鸭子类型。
2、类
类:一种对象;
实例:属于某一类的一个对象。
以鸟类和云雀为例,云雀是鸟类的子类,鸟类是云雀的超类。
类是由其支持的方法定义的;类的所有实例都有该类的所有方法,子类的所有实例都有超类的所有方法。
创建自定义类
class Person:
def set_name(self,name):
self.name=name
def get_name(self):
return self.name
def greet(self):
print("Hello,world! I'm {}.".format(self.name))
foo=Person() # 创建对象。实际上foo会自动传给self
foo.set_name('Luke') # self自动为foo,'Luck'传给name
foo.greet() # 不再需要传self
Hello,world! I'm Luke.
Person.greet(foo) # 这是foo.greet()的完整版,但显然多态性更差。
Hello,world! I'm Luke.
属性、函数和方法
方法和函数的区别我们都看到了,即是否需要提供self。属性:和self有关的变量。
class Bird:
song = 'Squaawk!'
def sing(self):
print(self.song)
bird = Bird()
birdsing = bird.sing # 用一个变量指向一个方法
birdsing() # 尽管是简单的函数调用,但一样可以访问到self,即birdsing函数也被关联到类的实例。
Squaawk!
类的命名空间
在class中的代码,都是在类的命名空间内执行的,而类的所有成员都可以访问这个命名空间。
class C:
a=100
m1=C()
m2=C()
print(m1.a)
print(m2.a)
100
100
但如果在一个实例中给a赋值,则实例属性会掩盖类级变量:
m2.a=99
print(m1.a)
print(m2.a)
100
99
3、封装和隐藏
默认情况下,可以从外部访问对象的属性。有些情况下,我们不希望外部可以随意修改。
哪怕我们对set_name方法进行了处理,比如set_name时自动给管理员发邮件,但name属性仍然可以直接修改,治标不治本。
为了避免这类问题,可以将属性定义为私有:私有属性不能从外部访问,只能通过方法访问。
设置方法很简单:在名称前面以两个下划线打头即可:
class Secretive:
def __inaccessible(self):
print("You can't see me directly...")
def accessible(self):
self.__inaccessible()
s=Secretive()
s.__inaccessible() # 直接访问会报错
AttributeError Traceback (most recent call last)
in ()
----> 1 s.__inaccessible() # 直接访问会报错
AttributeError: 'Secretive' object has no attribute '__inaccessible'
s.accessible() # 但可以由内部方法间接调用
You can't see me directly...
实际上,Python不过是把__inaccessible改成了_Secretive__inaccessible:
s._Secretive__inaccessible()
You can't see me directly...
因此,Python实际上没有任何办法阻值你干坏事。这只是一种强烈的信号罢了。
下划线也有一点作用:当from module import * 时,不会导以下划线打头的名称。
4、继承
class Filter:
def init(self):
self.blocked = []
def filter(self, sequence):
return [x for x in sequence if x not in self.blocked] # 显然这个超类的filter无法滤除任何东西,因为blocked为空。
class SPAMFilter(Filter): # 指定超类
def init(self): # 重写init
self.blocked = ['SPAM']
# 关键在于继承了Filter的滤波方法。
s=SPAMFilter()
s.init()
s.filter(['SPAM','SPAM','SPA'])
['SPA']
因此,虽然超类没有滤波功能,但其作用在于给所有子类定义了filter方法。
要确定一个类是否为另一个类的子类,可以使用issubclass内置方法:
issubclass(SPAMFilter,Filter)
True
如果已知一个子类,想知道其超类,可访问其特殊属性_bases_ :
SPAMFilter.__bases__
(main.Filter,)
Filter.__bases__
(object,)
想知道某个对象是否为特定类的实例,可以用isinstance:
isinstance(s,SPAMFilter)
True
isinstance(s,Filter)
True
如果已知对象,想知道其属于哪一类,用__class__属性:
s.__class__
main.SPAMFilter
多重继承
class Calculator:
def calculate(self, expression):
self.value=eval(expression)
class Talker:
def talk(self):
print("Hi, my value is {}.".format(self.value))
class TalkCal(Calculator, Talker):
pass
尽管TalkCal什么也没定义,但其继承了Calculator和Talker的方法,是会说话的计算器:
tim=TalkCal()
tim.calculate('1+2*3')
tim.talk()
Hi, my value is 7.
多重继承很强大,但要谨慎使用。如果多个超类具有同名方法,一定要小心排列。前面的类方法会覆盖后面的:
class Calculator:
def calculate(self, expression):
self.value=eval(expression)
def talk(self):
print("test,test")
class Talker:
def talk(self):
print("Hi, my value is {}.".format(self.value))
class TalkCal(Calculator, Talker):
pass
tim = TalkCal()
tim.calculate("1+2*3")
tim.talk()
test,test
5、接口和内省
有时在使用前,需要检查所需方法是否存在:
hasattr(tim,'talk')
True
hasattr(tim,'test')
False
有时还要检查是否可调用:
callable(getattr(tim,'talk',None))
True
callable(getattr(tim,'test','error'))
False
getattr用来返回对象的属性,如果不存在,要么触发异常,要么返回设置值:
getattr(tim,'test')
AttributeError Traceback (most recent call last)
in ()
----> 1 getattr(tim,'test')
AttributeError: 'TalkCal' object has no attribute 'test'
getattr(tim,'test',404)
404
显然getattr方法比hasattr方法功能更强大。
与之相反的setattr用于设置对象的属性:
setattr(tim,'name','Tim')
tim.name
'Tim'
要查看对象中存储的所有值,可以检查其__dict__属性:
tim.__dict__
{'value': 7, 'name': 'Tim'}
6、抽象基类
以上提供的都是手工检查的方法。长期以来,Python几乎只依赖于鸭子类型,即假设所有对象都能完成其工作,同时偶尔使用hasattr来检查所需方法是否存在。
但有比手工检查更好的方法。Python通过引入abc模块,提供了官方解决方案。这个模块为所谓的抽象基类提供了支持。
抽象类:定义子类应具备的一组抽象方法,而不能实例化。
from abc import ABC,abstractmethod
class Talker(ABC):
@abstractmethod # 用@abstractmethod将方法标记为抽象的。
def talk(self):
pass
抽象类不能实例化:
tim = Talker()
TypeError Traceback (most recent call last)
in ()
----> 1 tim = Talker()
TypeError: Can't instantiate abstract class Talker with abstract methods talk
方法未经修改的子类也不可以:
class Talkerson(Talker):
pass
Timson = Talkerson()
TypeError Traceback (most recent call last)
in ()
2 pass
3
----> 4 Timson = Talkerson()
TypeError: Can't instantiate abstract class Talkerson with abstract methods talk
但重写方法之后可以:
class Talkerson(Talker):
def talk(self):
print("Hi!")
Timson = Talkerson()
Timson.talk()
Hi!
显然,我们可以先检查一个实例是否属于Talker。如果属于,那么一定有talk方法。这就是比手工检查更方便的,又具有保障性的方法。
有一种register方法,可以让一个类从抽象类派生:
class TEST:
pass
Talker.register(TEST)
main.TEST
test = TEST()
hasattr(test, 'talk')
False
以上说明,register并没有让TEST继承抽象类Talker的talk方法,因此这样做仍然是出于信任,而没有保障。
isinstance(test,Talker)
True
八、异常
为了避免异常,我们可以采用条件语句,比如判断除数是否为零;但这样效率低下;有时候我们想直接忽略异常。
Python提供了强大的替代解决方案:异常处理机制。
Python使用异常对象来表示异常状态,并在遇到错误时引发异常。
事实上,每个异常都是某个类的实例。比如除零异常属于ZeroDivisionError类:
1/0
ZeroDivisionError Traceback (most recent call last)
in ()
----> 1 1/0
ZeroDivisionError: division by zero
1、自主引发异常
raise语句
要引发异常,可以用raise语句,并将一个类(必须是Exception的子类)或实例作为参数。前者将自动创建一个实例。下面展示前者:
raise Exception
Exception Traceback (most recent call last)
in ()
----> 1 raise Exception
Exception:
raise Exception('too fast')
Exception Traceback (most recent call last)
in ()
----> 1 raise Exception('too fast')
Exception: too fast
too fast是自己定义的错误信息。Exception是PY内置的异常类,还有很多,见P133.
自定义异常类
比如前面的too fast,你可能想用一个自定义的DrivingError类来表示。方法很简单,定义类时继承Exception即可,也可以添加方法:
class DrivingError(Exception):pass
raise DrivingError
DrivingError Traceback (most recent call last)
in ()
1 class DrivingError(Exception):pass
2
----> 3 raise DrivingError
DrivingError:
2、 捕获异常
如果你编写了一个除法函数,希望提示除零错误的同时,不要异常退出,则可以用try/except语句,而不是if判断:
try:
x=int(input('x= '))
y=int(input('y= '))
print(x/y)
except ZeroDivisionError:
print("y can't be zero!!!")
x= 1
y= 0
y can't be zero!!!
当然,在本例中,if会更简单;但如果是多个除法,if需要多次,而try/except仍只需要一次。
异常会从函数向外传播到调用函数的地方,即所谓的“向程序的最顶层传播”,前提是没有被捕获。try/except语句就实现了捕获功能。
显然,捕获(抑制)异常在交互程序中很有用,但一般的程序我们还是希望关闭抑制。此时应该这么写:
class Calculator:
muffled = False # 这是一个抑制开关
def calc(self, expr):
try:
return eval(expr) # 计算字符串表达式
except ZeroDivisionError:
if self.muffled: # 如果抑制
print('Division by zero!')
else:
raise
cal=Calculator()
cal.calc('10/0') # 默认为关闭抑制,即会触发异常
ZeroDivisionError Traceback (most recent call last)
in ()
11
12 cal=Calculator()
---> 13 cal.calc('10/0') # 默认为关闭抑制,即会触发异常
in calc(self, expr)
3 def calc(self, expr):
4 try:
----> 5 return eval(expr) # 计算字符串表达式
6 except ZeroDivisionError:
7 if self.muffled: # 如果抑制
in ()
ZeroDivisionError: division by zero
cal.muffled = True
cal.calc('10/0')
Division by zero!
启用抑制时,方法calc将返回None。换句话说,此时不应该使用返回值。
raise后面甚至可以跟别的异常。如果是除零,那么除零和该异常都会出现。使用None可以让除零异常不出现,参见P135。
多个except子句
如果希望同时捕获多个异常,可以用圆括号括起所有异常:
try:
x=int(input('x= '))
y=int(input('y= '))
print(x/y)
except (ZeroDivisionError, TypeError, ValueError):
print("Your numbers were bogus!")
x= 1
y= a
Your numbers were bogus!
记录对象
让程序继续运行,同时记录错误:
try:
x=int(input('x= '))
y=int(input('y= '))
print(x/y)
except (ZeroDivisionError, ValueError) as e:
print(e)
x= 1
y= a
invalid literal for int() with base 10: 'a'
一网打尽
expect后面为空即可:
try:
x=int(input('x= '))
y=int(input('y= '))
print(x/y)
except:
print("Something wrong")
x= 1
y= 0
Something wrong
循环输入直至正确
while True:
try:
x=int(input('x= '))
y=int(input('y= '))
print('x/y is',x/y)
except:
print("Invalid Input. Try again.")
else:
break
x= 1
y= 0
Invalid Input. Try again.
x= 1
y= 2
x/y is 0.5
无论是否发生异常,finally子句都会执行:
try:
x=int(input('x= '))
finally:
print('Input over.')
x= 1/0
Input over.
ValueError Traceback (most recent call last)
in ()
1 try:
----> 2 x=int(input('x= '))
3 finally:
4 print('Input over.')
ValueError: invalid literal for int() with base 10: '1/0'
最佳应用
如果字典中不存在相应的key,那么try/except操作要高效得多:
info1=dict(name="Thomas",age=42)
info2=dict(name="Ryan",age=21,occupation='Student')
def print_info(info):
print('Name: ',info['name'])
print('Age: ',info['age'])
try:
print('Occupation: ',info['occupation'])
except KeyError:
pass
print_info(info1)
Name: Thomas
Age: 42
print_info(info2)
Name: Ryan
Age: 21
Occupation: Student
3、发出警告
模块warnings提供了函数warn,可以发出警告。详细见P142。
九、特殊方法、特性property和迭代器iterator
首先注意: 在PY3中没有旧式类,因此不需要显式地继承object。如果没有指定超类,将自动继承object。
1、构造函数constructor
借助constructor,可以在定义类时直接定义一些属性:
class FooBar:
def __init__(self):
self.somevar=42
f=FooBar()
f.somevar
42
创建实例时,可以更简单:
class Book:
def __init__(self,age=21):
self.age=age
Ryan=Book(20)
Ryan.age
20
Cathy=Book()
Cathy.age
21
重写时出现的问题
假设超类Bird中初始化了hungry属性,而子类SongBird初始化却没有提及hungry,将会导致属性不存在:
class Bird:
def __init__(self):
self.hungry=True
def eat(self):
if self.hungry:
print('Eat it!!!')
else:
print('No, thanks!')
class SongBird(Bird):
def __init__(self):
self.sound='Squawk!'
def sing(self):
print(self.sound)
Tim=SongBird()
Tim.eat()
AttributeError Traceback (most recent call last)
in ()
15
16 Tim=SongBird()
---> 17 Tim.eat()
in eat(self)
3 self.hungry=True
4 def eat(self):
----> 5 if self.hungry:
6 print('Eat it!!!')
7 else:
AttributeError: 'SongBird' object has no attribute 'hungry'
为了解决该问题,我们有两种办法。一种是调用超类构造函数(解决老代码的问题):
class SongBird(Bird):
def __init__(self):
Bird.__init__(self) # 多加一行即可
self.sound='Squawk!'
def sing(self):
print(self.sound)
Tim=SongBird()
Tim.eat()
Eat it!!!
如果使用的是PY3,那么就一定要使用下面这种方法:super函数。
class Bird:
def __init__(self):
self.hungry=True
def eat(self):
if self.hungry:
print('Eat it!!!')
else:
print('No, thanks!')
class SongBird(Bird):
def __init__(self):
super().__init__()
self.sound='Squawk!'
def sing(self):
print(self.sound)
Tim=SongBird()
Tim.eat()
Eat it!!!
super函数自动完成了任务,而不需要对其提供任何参数!
不仅如此,当子类继承多个超类时,super可以自动完成对所有超类的调用!
2、我是鸭子
除了__init__,还有很多特殊方法。下面我们通过一些特殊方法,创建一些像序列或映射的对象。
注意,在PY中,多态仅仅基于对象的行为,而与其超类等无关。因此,要成为“序列”,只需要遵循序列的“协议”即可,即满足其行为规范。
基本的序列和映射协议
序列需要满足基本行为,见P150。
从list,dict和str继承
大多数方法不需要自己实现,简单的继承就可以解决问题。比如一个带计数器的列表(访问一次计数加1):
class Counterlist(list):
def __init__(self,*args):
super().__init__(*args) # 访问list的init方法
self.counter = 0
def __getitem__(self,index):
self.counter += 1
return super(Counterlist,self).__getitem__(index) # 访问list的getitem方法
a=Counterlist(range(1,11))
a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a[-2]
9
a.reverse()
a
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
a.counter
1
3、特性property
函数property
假设我们有一个长方形类,只有边长属性,面积没有属性只有存取方法:
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def set_size(self,size):
self.width,sife.height=size
def get_size(self):
return self.width,self.height
r=Rectangle()
r.width=10
r.height=5
r.get_size()
(10, 5)
有一天你突发奇想,希望size变成属性,而不再是通过函数计算得到。那么可以利用property函数来隐藏存取函数:
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def set_size(self,size):
self.width,sife.height=size
def get_size(self):
return self.width,self.height
size=property(get_size,set_size)
r=Rectangle()
r.width=10
r.height=5
r.size
(10, 5)
值得注意的是,property函数内可以只读不可写,还可以加入第三个参数,指定删除属性的方法。
静态方法和类方法
目前为止,为了实现某个方法,我们需要先把类实例化。现在介绍的两种方法可以避免实例化,借助装饰器:
class MYCLASS:
@staticmethod
def Ryan(): # 静态方法,不需要self,不需要实例化即可调用
print("Hello world!")
@classmethod
def Cathy(cls): # 用cls代替self
print("Hello world!",cls) # 注意cls
MYCLASS.Cathy()
Hello world! <class 'main.MYCLASS'>
MYCLASS.Ryan()
Hello world!
4、迭代器
迭代器协议
前面提到的for循环,不仅可以迭代序列和字典,还可以迭代其他所有可迭代对象。
可迭代对象:具有方法__iter__的对象。
迭代器:具有方法__next__的对象。
使用迭代器的好处:不需要像序列一样全部存储、占用内存,并且更简单、优雅:
class Fibs: # 斐波那契数列
def __init__(self):
self.a=0
self.b=1
def __next__(self):
self.a,self.b=self.b,self.a+self.b
return self.a
def __iter__(self):
return self # 注意:iter方法返回迭代器本身。谁需要迭代器,我就返回我自己。
fibs=Fibs()
for f in fibs:
if f<1000:
print(f)
else:break
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
可以通过内置函数iter,输入一个可迭代对象,快速得到一个迭代器:
it=iter(list(range(10)))
for i in range(5):
print(next(it))
0
1
2
3
4
从迭代器创建序列
it=iter(list(range(10)))
ti=list(it)
ti
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
5、生成器
例如,我们想迭代以下嵌套列表的列表:
nested=[[1,2],[3,4],[5]]
我们可以通过嵌套循环取出:
def flatten(nested):
for sublist in nested:
for element in sublist:
yield element
含yield的函数都称为生成器。每次yield后,函数都会冻结并停在此处,下一次从断点继续执行:
list(flatten(nested))
[1, 2, 3, 4, 5]
和return不同,return会退出函数。
生成器推导
第五张我们介绍过列表推导,例如:
[x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
生成器推导采用圆括号,输出的是可迭代对象,而不是立即完成迭代:
g=(i**2 for i in range(10))
next(g)
next(g)
next(g)
4
使用生成器推导可以写出如下优美的代码,如果已有圆括号,无需多加一层:
sum(i for i in range(5))
10
递归式生成器
我们现在希望:无论可迭代对象结构有多复杂,我们都能把它迭代出来:
def flatten(nested):
try:
try:
nested + ''
except TypeError:
pass # 如果不是字符串,就对了,pass。可惜的是,没有标准类可以判断一个对象是否为字符串,只能通过行为判断。
else:
raise TypeError # 是字符串,异常传递到外部去,被下面的except捕获
for sublist in nested: # 不是字符串,接着pass
for element in flatten(sublist): # 递归:如果目标不可分解,那么就一次循环;flatten(sublist)会传回sublist的最终分解结果。
yield element
except TypeError:
yield nested # 捕获异常,直接输出字符串
list(flatten(['foo',[1,['haha',2,['good']]]]))
['foo', 1, 'haha', 2, 'good']
生成器的方法
参见P162-163。
实操:八皇后问题
生成器可以用来解决复杂的递归问题。比如八皇后问题:
在8x8的棋盘上,要放置8个皇后。皇后可以吃同行、同列和斜向的棋子,请问一共有多少种不冲突的放置方法?
我们用state元组,表示每一行皇后所在的列。比如state[3]==0,表示第四行的皇后在第一列。
我们的基本方法,是逐个不冲突地放置皇后。nextX和nextY表示待测皇后位置:
# 检测冲突
def ifconflict(state,nextX): # 检测冲突
nextY = len(state) # state随确定的皇后数目增加而扩展 并且是逐行确定
for i in range(nextY):
if abs(state[i]-nextX) in (0, nextY-i): # 在同一行,或在对角线位置
return True
return False # 不存在合适的
# 递归条件和基线条件
def queens(num=8,state=()): # 默认为8,state初始化为空tuple 递归时就不是空的了
for pos in range(num): # 遍历考虑所有位置
if not ifconflict(state, pos): # 如果待选位置和目前状态不冲突,那么就进入state
if len(state)==num-1: # 基线条件,即达到递归最后一层了,开始返回
yield (pos,)
else: # 递归条件,即还需要内层yield一个结果回来,再yield出去
for result in queens(num, state + (pos,)):
yield (pos,)+result
能用return吗?不能,因为内层函数不能一次返回多个result,否则还得一个一个处理。yield一次只有一个,因此便于处理。
通过list,可以把所有结果遍历,所以不影响最后计数。
现在我们看,8x8的棋盘上一共有多少解:
len(list(queens(8)))
92
十、开箱即用
1、模块
模块就是程序
任何PY程序都可作为模块导入。
假设我们有一个helloworld.py文件(直接print)放在Downloads文件夹下,那么helloworld就是模块名。
我们首先告诉解释器,除了常规路径,还有以下路径可以查找模块(注意路径要完整):
import sys
sys.path.append('/home/xing/Downloads')
之后我们就可以导入helloworld模块了:
import helloworld
Hello,world!
我们看到,当导入模块时,其中的代码被执行了。
模块是用来下定义的
如果第二次导入时,程序是不会执行的。因为,模块不是用来执行的,而是用来定义变量、函数、类等的。
并且,如果修改helloworld.py文件,模块不会发生任何变化,即使再次导入。
不重复导入的原因,是为了放置不同模块彼此导入,导致死循环。
模块值得被创建的原因,在于模块像类一样,有自己的作用域。
这意味着,在模块中定义的类、函数及对其赋值的变量,都成为了模块的属性。
比如在相同路径的helloagain.py中,我们定义了一个hello函数(在函数中print)。现在调用它:
import helloagain
helloagain.hello()
hello again!
在模块中添加测试代码
我们创建一个hellotest.py,其中包含测试代码:
# hellotest.py
def hello():
print('Hello world!')
def test():
hello()
if __name__ == '__main__': # 如果在主程序中(如交互界面提示符)调用,认为是测试;如果在程序中调用,不应测试。
test() # 执行测试代码
import hellotest
hellotest.test()
Hello world!
路径
我们可以查看sys.path,即PY解释器搜索模块的地址:
import sys,pprint
pprint.pprint(sys.path)
['',
'/usr/lib/python35.zip',
'/usr/lib/python3.5',
'/usr/lib/python3.5/plat-x86_64-linux-gnu',
'/usr/lib/python3.5/lib-dynload',
'/home/xing/.local/lib/python3.5/site-packages',
'/usr/local/lib/python3.5/dist-packages',
'/usr/lib/python3/dist-packages',
'/usr/local/lib/python3.5/dist-packages/IPython/extensions',
'/home/xing/.ipython',
'~/Desktop',
'~/Desktop',
'~/Desktop',
'/home/xing/Desktop',
'/home/xing/Desktop',
'/home/xing/Downloads',
'/home/xing/Downloads']
其中site-packages是最佳路径,是专门用来存放模块的。
另一种做法,也是标准做法,是把路径添加到环境变量PYTHONPATH中。
环境变量不是PY解释器的一部分,而是操作系统的一部分。
包
包是组织模块的模块,也是一种目录。
作为特殊的模块,其一定包含__init__.py文件,其内容就是包的内容。
比如,一个名为constants的包,起__init__.py文件中有语句PI=3.14。那么我们就可以这么做:
import constants
print(constants.PI)
其余见P178。
2、探索模块
首先,import没有异常,说明该模块存在。比如导入copy模块。
模块包含什么
我们使用dir函数:
import copy
dir(copy)
['Error',
'PyStringMap',
'_EmptyClass',
'all',
'builtins',
'cached',
'doc',
'file',
'loader',
'name',
'package',
'spec',
'_copy_dispatch',
'_copy_immutable',
'_copy_with_constructor',
'_copy_with_copy_method',
'_deepcopy_atomic',
'_deepcopy_dict',
'_deepcopy_dispatch',
'_deepcopy_list',
'_deepcopy_method',
'_deepcopy_tuple',
'_keep_alive',
'_reconstruct',
'builtins',
'copy',
'deepcopy',
'dispatch_table',
'error',
'name',
't',
'weakref']
其中有一些是内部变量,因此我们可以过滤掉这些最好不能用的:
[n for n in dir(copy) if not n.startswith('_')]
['Error',
'PyStringMap',
'builtins',
'copy',
'deepcopy',
'dispatch_table',
'error',
'name',
't',
'weakref']
我们还可以使用__all__变量。这个变量描述的是该模块的公有接口:
copy.__all__
['Error', 'copy', 'deepcopy']
我们定义模块时,也可以设置该变量。当我们用from copy import * 引入时,将只引入以上三者。
当然还可以使用help:
help(copy.copy)
Help on function copy in module copy:
copy(x)
Shallow copy operation on arbitrary Python objects.See the module's __doc__ string for more info.
实际上,第一句话引用的是copy.copy.__doc__文档字符串。
文档和源代码
我们可以通过文档,或者源代码获取更多的信息:
print(range.__doc__) # 获取文档
print(copy.__file__) # 获取源代码 记得一定不要修改源代码!!!这会破坏文件。
3、标准库
PY中自带了很多有用的标准库,见P181-。这里介绍一些有趣的。
集合
我们先介绍集合。在旧版本中,集合是通过模块sets中的Set类实现的。新版本中,集合由内置类set实现,而无需导入模块sets。
我们来看看其特殊操作:
set(range(5))
{0, 1, 2, 3, 4}
# 空集合不能用{}创建,否则结果是字典
type(set())
set
{1,1,1,2,2,2,3,3,4,8,9} # 自动去重
{1, 2, 3, 4, 8, 9}
a={1,2,3}
b={2,3,4}
print(a.union(b)) #集合的并集方法
print(a|b) # 直接并
{1, 2, 3, 4}
{1, 2, 3, 4}
c=a&b # 求并集
print(c)
print(a.intersection(b))
print(c.issubset(a)) # 是否为子集
print(c<=a)
print(a.issuperset(c)) # 是否为超集
{2, 3}
{2, 3}
True
True
True
print(a.difference(b)) # 求补
print(a-b)
print(a.symmetric_difference(b)) # 对等求差,看结果就知道了
print(a^b)
{1}
{1}
{1, 4}
{1, 4}
集合是可变的,因此不能作为字典中的key。
集合只能包含不可变量,然而我们常常遇到集合的嵌套。
此时,我们需要借助forzenset类型:
a.add(frozenset(b))
print(a)
{1, 2, 3, frozenset({2, 3, 4})}
堆heap
与集合不同,PY中没有独立的堆类型,只有包含堆函数的模块,名为heapq。其包含6个函数,见P188。
此外,在collections模块中,还有双端队列,见P190。
random
模块random包含生成伪随机数的函数,有助于编写模拟程序或生成随机输出的程序。
由于生成的是伪随机数,因此其背后的系统是可预测的。为了真正随机,我们应该使用os中的urandom函数。
模块random中的重要函数见P192。测试:
from random import *
a=10
b=20
n=2
c=range(5)
d=list(range(5))
print(random(),'# 返回[0,1]之间的伪随机数')
print(uniform(a,b),'# 返回[a,b]之间服从均匀分布的随机实数')
print(randrange(a,b),'# 返回[a,b]之间的随机整数')
print(randrange(a,b,2),'# 返回[a:2:b]之间的随机整奇数')
print(choice(c),'# 随机选一个')
shuffle(d) # 必须是可变量
print(d,'# 随机打乱元素,并且每一种排列的出现概率相同')
print(sample(c,n),'# 随机选指定个')
0.9414482178933056 # 返回[0,1]之间的伪随机数
12.33755908330283 # 返回[a,b]之间服从均匀分布的随机实数
17 # 返回[a,b]之间的随机整数
14 # 返回[a:2:b]之间的随机整奇数
2 # 随机选一个
[2, 1, 4, 3, 0] # 随机打乱元素,并且每一种排列的出现概率相同
[1, 4] # 随机选指定个
re
模块re提供了对正则表达式的支持。正则表达式很强大,也很有用,参见P198-。
十一、文件
我们现在要通过文件和流,让程序和外界的交流更丰富。
1、读取和写入
open函数位于自动导入的模块io中。如果文件不在当前目录,则需要指定路径。
open函数的第二个参数默认为'r',若文件不存在,是无法读取的。我们可以设其为'w',当文件不存在时可以创建它。
其他参数设置见P214。
f=open('test.txt','w')
f.write('Hello,') #返回的是字符数
6
f.write(' World!')
7
f.close()
f=open('test.txt','r')
f.read(4) # 读4个字符
'Hell'
f.read() # 读剩下所有字符
'o, World!'
2、使用管道重定向输出
我们刚刚在test.txt中写了2个单词。现在我们再写一个wordcount.py,用管道的方式统计字数。wordcount如下:
import sys
text=sys.stdin.read()
words=text.split()
wordcount=len(words)
print('Wordcount:',wordcount)
定位到当前路径,在命令行输入:cat test.txt | python3 wordcount.py,即可得到统计结果2。
3、关闭文件
记得要调用方法close将文件关闭。对于写入过的文件,一定要将其关闭。
因为PY可能缓冲你写入的数据,如果程序崩溃了,那么数据是不会写入文件中的。因此要及时关闭文件以保存。
如果要重置缓存,让修改写入文件,但又不想关闭文件,可以使用方法flush。以下是两种方法:
# 打开文件
try:
# 写文件
finally:
file.close() # 即使异常,也要关闭
# 更专用的方法:上下文管理器with语句:
with open("test.txt") as testfile:
# 写文件
#当with结束后,自动关闭文件,即使异常也是。
Reading | 《Python基础教程》第1次阅读的更多相关文章
-
【Python】Python基础教程系列目录
Python是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言. 在现在的工作及开发当中,Python的使用越来越广泛,为了方便大家的学习,Linux大学 特推出了 <Python基 ...
-
Python 基础教程
Python 基础教程 Python是一种解释型.面向对象.动态数据类型的高级程序设计语言. Python由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991年. 像P ...
-
Python基础教程系列目录,最全的Python入门系列教程!
Python是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言. 在现在的工作及开发当中,Python的使用越来越广泛,为了方便大家的学习,Linux大学 特推出了 <Python基 ...
-
Python基础教程总结(一)
引言: 一直都听说Python很强大,以前只是浏览了一些博客,发现有点像数学建模时使用的Matlab,就没有深入去了解了.如今Python使用的地方越来越多,最近又在学习机器学习方面的知识,因此想系统 ...
-
python基础教程(2)
Python 基础教程 Python 是一种解释型.面向对象.动态数据类型的高级程序设计语言. 执行Python程序 对于大多数程序语言,第一个入门编程代码便是 "Hello World!& ...
-
(Python基础教程之十二)Python读写CSV文件
Python基础教程 在SublimeEditor中配置Python环境 Python代码中添加注释 Python中的变量的使用 Python中的数据类型 Python中的关键字 Python字符串操 ...
-
Python基础教程 (第2+3 版)打包pdf|内附网盘链接提取码
<Python基础教程 第3版>包括Python程序设计的方方面面:首先,从Python的安装开始,随后介绍了Python的基础知识和基本概念,包括列表.元组.字符 ...
-
改写《python基础教程》中的一个例子
一.前言 初学python,看<python基础教程>,第20章实现了将文本转化成html的功能.由于本人之前有DIY一个markdown转html的算法,所以对这个例子有兴趣.可仔细一看 ...
-
.Net程序员之Python基础教程学习----列表和元组 [First Day]
一. 通用序列操作: 其实对于列表,元组 都属于序列化数据,可以通过下表来访问的.下面就来看看序列的基本操作吧. 1.1 索引: 序列中的所有元素的下标是从0开始递增的. 如果索引的长度的是N,那么所 ...
-
python基础教程笔记—即时标记(详解)
最近一直在学习python,语法部分差不多看完了,想写一写python基础教程后面的第一个项目.因为我在网上看到的别人的博客讲解都并不是特别详细,仅仅是贴一下代码,书上内容照搬一下,对于当时刚学习py ...
随机推荐
-
Mininet的内部实现原理简介
原文发表在我的博客主页,转载请注明出处. 前言 之前模拟仿真网络一直用的是Mininet,包括写了一些关于Mininet安装,和真实网络相连接,Mininet简历拓扑的博客,但是大多数都是局限于具体步 ...
-
python安装requests (win7 &; centos7)
下载地址: http://pypi.python.org/pypi/requests/只有tart.gz包 解压后,进入目录,安装命令: python setup.py install 会出现:Imp ...
-
C# Socket编程 同步以及异步通信
套接字简介:套接字最早是Unix的,window是借鉴过来的.TCP/IP协议族提供三种套接字:流式.数据报式.原始套接字.其中原始套接字允许对底层协议直接访问,一般用于检验新协议或者新设备问题,很少 ...
-
python 补充-decode和encode
1. decode与encode转码 在Python3中默认编码就是uncode,encode转成Byte类型 在Python2中默认编码就是ascii window下默认编码是GBK decode( ...
-
jQuery中 end(); 的用法
jQuery中的end()方法的意思 选取某个元素,查找选取其子元素,然后再回过来选取这个元素.用例子说明了一下: 比如HTML代码: <p><span>Hello</s ...
-
Learning Theory
Empiricial Risk Minimization 统计学习理论是整个机器学习到框架.试想我们学习的目的是什么呢?当然是为了具备用合理的方式处理问题的能力.统计学习理论要解决的问题就是基于数据找 ...
-
JAVA中运用数组的四种排序方法
JAVA中在运用数组进行排序功能时,一般有四种方法:快速排序法.冒泡法.选择排序法.插入排序法. 快速排序法主要是运用了Arrays中的一个方法Arrays.sort()实现. 冒泡法是运用遍历数组进 ...
-
教你一招用 IDE 编程提升效率的骚操作!
阅读本文大概需要 3 分钟. IDEA 有个很牛逼的功能,那就是后缀补全(不是自动补全),很多人竟然不知道这个操作,还在手动敲代码. 这个功能可以使用代码补全来模板式地补全语句,如遍历循环语句(for ...
-
elf 学习
现在我们使用 readelf 命令来查看 elfDome.out 的文件头 readelf -l elfDemo.out 使用 readelf 来查看程序头: readelf -S elfDemo.o ...
-
数据类型&;字符串得索引及切片
一:数据类型 1):int 1,2,3用于计算 2):bool ture false 用于判断,也可做为if的条件 3):str 用引号引起来的都是str 存储少量数据,进行 ...