目录

一、基础知识

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
Xing

这里引入一个创建范围的内置函数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次阅读的更多相关文章

  1. 【Python】Python基础教程系列目录

    Python是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言. 在现在的工作及开发当中,Python的使用越来越广泛,为了方便大家的学习,Linux大学 特推出了 <Python基 ...

  2. Python 基础教程

    Python 基础教程 Python是一种解释型.面向对象.动态数据类型的高级程序设计语言. Python由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991年. 像P ...

  3. Python基础教程系列目录,最全的Python入门系列教程!

    Python是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言. 在现在的工作及开发当中,Python的使用越来越广泛,为了方便大家的学习,Linux大学 特推出了 <Python基 ...

  4. Python基础教程总结(一)

    引言: 一直都听说Python很强大,以前只是浏览了一些博客,发现有点像数学建模时使用的Matlab,就没有深入去了解了.如今Python使用的地方越来越多,最近又在学习机器学习方面的知识,因此想系统 ...

  5. python基础教程(2)

    Python 基础教程 Python 是一种解释型.面向对象.动态数据类型的高级程序设计语言. 执行Python程序 对于大多数程序语言,第一个入门编程代码便是 "Hello World!& ...

  6. (Python基础教程之十二)Python读写CSV文件

    Python基础教程 在SublimeEditor中配置Python环境 Python代码中添加注释 Python中的变量的使用 Python中的数据类型 Python中的关键字 Python字符串操 ...

  7. Python基础教程 (第2+3 版)打包pdf|内附网盘链接提取码

                <Python基础教程 第3版>包括Python程序设计的方方面面:首先,从Python的安装开始,随后介绍了Python的基础知识和基本概念,包括列表.元组.字符 ...

  8. 改写《python基础教程》中的一个例子

    一.前言 初学python,看<python基础教程>,第20章实现了将文本转化成html的功能.由于本人之前有DIY一个markdown转html的算法,所以对这个例子有兴趣.可仔细一看 ...

  9. .Net程序员之Python基础教程学习----列表和元组 [First Day]

    一. 通用序列操作: 其实对于列表,元组 都属于序列化数据,可以通过下表来访问的.下面就来看看序列的基本操作吧. 1.1 索引: 序列中的所有元素的下标是从0开始递增的. 如果索引的长度的是N,那么所 ...

  10. python基础教程笔记—即时标记(详解)

    最近一直在学习python,语法部分差不多看完了,想写一写python基础教程后面的第一个项目.因为我在网上看到的别人的博客讲解都并不是特别详细,仅仅是贴一下代码,书上内容照搬一下,对于当时刚学习py ...

随机推荐

  1. Linux配置Supervisor 配置遇到的坑

    在linux中web 应用部署到线上后之后发现退出终端后网站就无法访问了 所以需要用Supervisor来守护进程,它可以保证应用一直处于运行状态,在遇到程序异常.报错等情况,导致 web 应用终止时 ...

  2. 5种必会的Java异步调用转同步的方法你会几种

    转载请注明本文地址:https://www.jianshu.com/p/f00aa6f66281 源码地址:https://gitee.com/sunnymore/asyncToSync Sunny先 ...

  3. MYSQL与TiDB的执行计划

    前言 这里采用了tpc-h一个数据库的数据量来进行查询计划的对比.并借助tpc-h中的22条查询语句进行执行计划分析. mysql采用的是标准安装,TiDB采用的是单机测试版,这里的性能结果不能说明其 ...

  4. ATS配置自定义日志

    修改records.config,开启日志自定义功能 更改日志目录,默认日志存放在/var/log/trafficserver: CONFIG proxy.config.log.logfile_dir ...

  5. leetcode3

    public class Solution { public int LengthOfLongestSubstring(string s) { var dic = new Dictionary< ...

  6. 深度学习原理与框架-猫狗图像识别-卷积神经网络(代码) 1.cv2.resize(图片压缩) 2..get_shape()[1:4].num_elements(获得最后三维度之和) 3.saver.save(训练参数的保存) 4.tf.train.import_meta_graph(加载模型结构) 5.saver.restore(训练参数载入)

    1.cv2.resize(image, (image_size, image_size), 0, 0, cv2.INTER_LINEAR) 参数说明:image表示输入图片,image_size表示变 ...

  7. GIT TEAMWORK

    Learn GIT TEAMWORK generalizations Congratulations, you now know enough to start collaborating on Gi ...

  8. servlet3.1

    Servlet3.1新增的新特性强制更改sessionId 由HttpServletRequest 的changeSessionId()方法实现 非阻塞式IO 非阻塞式IO我们应该知道Servlet底 ...

  9. Object.assign()怎么用?

    用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target). 1.以对象为参数的合并1)Object.assign方法的第一个参数是目标对象,后面的参数都是源对象 con ...

  10. java学习笔记(十):scanner输入

    可以通过 Scanner 类来获取用户的输入. 通过next()类和nextLine()类来获取字符串. 通过 Scanner 类的 next() 类来获取输入的字符串. import java.ut ...