Reading | 《Python基础教程》第1次阅读

时间:2022-12-11 14:44:05

目录

一、基础知识

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基础教程系列目录&comma;最全的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&vert;内附网盘链接提取码

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

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

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

  9. &period;Net程序员之Python基础教程学习----列表和元组 &lbrack;First Day&rsqb;

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

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

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

随机推荐

  1. Mininet的内部实现原理简介

    原文发表在我的博客主页,转载请注明出处. 前言 之前模拟仿真网络一直用的是Mininet,包括写了一些关于Mininet安装,和真实网络相连接,Mininet简历拓扑的博客,但是大多数都是局限于具体步 ...

  2. python安装requests (win7 &amp&semi; centos7)

    下载地址: http://pypi.python.org/pypi/requests/只有tart.gz包 解压后,进入目录,安装命令: python setup.py install 会出现:Imp ...

  3. C&num; Socket编程 同步以及异步通信

    套接字简介:套接字最早是Unix的,window是借鉴过来的.TCP/IP协议族提供三种套接字:流式.数据报式.原始套接字.其中原始套接字允许对底层协议直接访问,一般用于检验新协议或者新设备问题,很少 ...

  4. python 补充-decode和encode

    1. decode与encode转码 在Python3中默认编码就是uncode,encode转成Byte类型 在Python2中默认编码就是ascii window下默认编码是GBK decode( ...

  5. jQuery中 end&lpar;&rpar;&semi; 的用法

    jQuery中的end()方法的意思 选取某个元素,查找选取其子元素,然后再回过来选取这个元素.用例子说明了一下: 比如HTML代码: <p><span>Hello</s ...

  6. Learning Theory

    Empiricial Risk Minimization 统计学习理论是整个机器学习到框架.试想我们学习的目的是什么呢?当然是为了具备用合理的方式处理问题的能力.统计学习理论要解决的问题就是基于数据找 ...

  7. JAVA中运用数组的四种排序方法

    JAVA中在运用数组进行排序功能时,一般有四种方法:快速排序法.冒泡法.选择排序法.插入排序法. 快速排序法主要是运用了Arrays中的一个方法Arrays.sort()实现. 冒泡法是运用遍历数组进 ...

  8. 教你一招用 IDE 编程提升效率的骚操作!

    阅读本文大概需要 3 分钟. IDEA 有个很牛逼的功能,那就是后缀补全(不是自动补全),很多人竟然不知道这个操作,还在手动敲代码. 这个功能可以使用代码补全来模板式地补全语句,如遍历循环语句(for ...

  9. elf 学习

    现在我们使用 readelf 命令来查看 elfDome.out 的文件头 readelf -l elfDemo.out 使用 readelf 来查看程序头: readelf -S elfDemo.o ...

  10. 数据类型&amp&semi;字符串得索引及切片

    一:数据类型 1):int     1,2,3用于计算 2):bool    ture  false  用于判断,也可做为if的条件 3):str     用引号引起来的都是str 存储少量数据,进行 ...