# python 基础

# 一、基础知识

  1. 注释:python 的注释使用的#开头的。注释是为了向阅读代码的人解释或说明这段程序是干嘛用的,以及这些变量是什么含义等等。不要嫌麻烦,因为等你几个月不碰代码再回过头来看,你可能都看不懂这写的是个啥。
print('hello world') # 注意到 print 是一个函数
# 注意到 print 是一个函数
print('hello world')
1
2
3
  1. 数字:它主要分为两种类型——整数浮点数。整数例如2,浮点数例如3.23。没有单独的 long 类型,int 类型可以值任何大小的整数。

  2. 字符串是字符的序列。字符串可以用单引号'xxx'或者双引号"xxx"来指定。而三引号是来指定多行字符串的,并且在三引号之间自由地使用单引号与双引号。TODO,格式化方法。

'Quote me on this'

"What's your name?"

'''这是一段多行字符串。这是它的第一行。
This is the second line.
"What's your name?," I asked.
He said "Bond, James Bond."
'''
1
2
3
4
5
6
7
8
9
  1. 字面常量:也叫字面量、文字常量,表示一个字面意义上的、不能被改变的值或内容。就表示它本身的含义,例如数字2就是数量上的“二”,或者“This is a string”这种字面本身意思的字符串。

  2. 转义序列:反斜杠\用于转义,例如'What\'s your name?',还有\\指定反斜杠本身,\n表示新的一行开始,\t是制表符,单独一个反斜杠放在末尾表示在下一行继续但不会添加新行。如果不希望\转义成特殊字符,想要展示原始字符串(常用于正则表达式),那就在字符串前面添加rR

'This is the first line\nThis is the second line'
# This is the first line
# This is the second line

"This is the first sentence. \
This is the second sentence."
# This is the first sentence. This is the second sentence.

r"Newlines are indicated by \n"
# Newlines are indicated by \n
1
2
3
4
5
6
7
8
9
10
  1. 变量标识符:变量可以存储任何信息并且也能用一些方法操作它们。变量的命名就是用的标识符

    • 标识符的第一个字符必须是字母表中的字母(大写 ASCII 字符或小写 ASCII 字符或 Unicode 字符)或下划线(_)。
    • 标识符的其它部分可以由字母、下划线(_)、数字(0~9)组成。
    • 标识符名称区分大小写。例如, mynamemyName 并不相同。要注意到前者是小写字母 n 而后者是大写字母 N
    • 有效标识符名称的例子有 iname_2_3 ,无效标识符名称的例子有2thingsthis is spaced outmy-name>a1b2_c3
  2. 数据类型:变量可以保存不同类型(数据类型)的值。常用的有:数字(整数、浮点数、复数、布尔值)、字符串、列表、元组、集合、字典。

  3. 对象:在 Python 中,一切皆对象, 包括数字、字符串与函数,Python 是强(Strongly)面向对象的。

  4. 逻辑行物理行:物理行就是我们编写所看到的一行内容,逻辑行是实际运行的单个语句。Python 期望每一行使用一句独立的语句,如果你非要一个物理行里写多个逻辑行语句,那就使用分号(;)确保逻辑行的结束(强烈建议不要使用分号,一行只写一个语句)。

i = 5
print(i)
# 等同于
i = 5;
print(i);
# 等同于
i = 5; print(i);
1
2
3
4
5
6
7
  1. 缩进:在逻辑行的开头留下空白区(使用空格或制表符)用以确定各逻辑行的缩进级别。同一缩进级别的语句被称为块,Python 基本上不会用到花括号{}
i = 5
# 错误如下!注意,在行的开头处有一个空格
 print('Value is', i)
print('I repeat, the value is', i)
1
2
3
4

# 二、运算符与表达式

表达式可以拆分成运算符(Operators)与操作数(Operands),例如2 + 3。运算符是进行某些操作的,最常见的就是+-*/,被操作的数据就是操作数了,操作数可以是变量或者字面常量。

  • + (加)
    • 两个对象相加。
    • 3+5 则输出 8'a' + 'b' 则输出 'ab'
  • - (减)
    • 从一个数中减去另一个数,如果第一个操作数不存在,则假定为零。
    • -5.2 将输出一个负数, 50 - 24 输出 26
  • * (乘)
    • 给出两个数的乘积,或返回字符串重复指定次数后的结果。
    • 2 * 3 输出 6'la' * 3 输出 'lalala'
  • ** (乘方)
    • xy 次方。
    • 3 ** 4 输出 81 (即 3 * 3 * 3 * 3 )。
  • / (除)
    • x 除以 y
    • 13 / 3 输出 4.333333333333333
  • // (整除)
    • x 除以 y 并对结果向下取整至最接近的整数。注意,如果操作数之一为浮点数,则返回值必为浮点数。
    • 13 // 3 输出 4
    • -13 // 3 输出 -5
    • 9//1.81 得到 4.0
  • % (取模)
    • 返回除法运算后的余数
    • 13 % 3 输出 1-25.5 % 2.25 输出 1.5
  • << (左移)
    • 将数字的位向左移动指定的位数。(每个数字在内存中以二进制数表示,即 0 和 1)
    • 2 << 2 输出 82 用二进制数表示为 10
    • 将二进制的 10 向左移两位得到二进制的 1000 ,它也表示十进制的 8
  • >> (右移)
    • 将数字的位向右移动指定的位数。
    • 11 >> 1 输出 5 。
    • 11 在二进制中表示为 1011 ,右移一位后输出 101 这一结果,表示十进制中的 5
  • & (按位与)
    • 对数字进行按位与操作。
    • 5 & 3 输出 1
  • | (按位或)
    • 对数字进行按位或操作。
    • 5 | 3 输出 7
  • ^ (按位异或)
    • 对数字进行按位异或操作。
    • 5 ^ 3 输出 6
  • ~ (按位取反)
  • < (小于)
    • 返回 x 是否小于 y。所有的比较运算符返回的结果均为 TrueFalse 。请注意这些名称之中的大写字母。
    • 5 < 3 输出 False3 < 6 输出 True
    • 比较可以任意组成组成链接: 3 < 5 < 7 返回 True
  • > (大于)
    • 返回 x 是否大于 y
    • 5 > 3 返回 True
  • <= (小于等于)
    • 返回 x 是否小于或等于 y
    • x = 3; y = 6; x <= y 返回 True
  • >= (大于等于)
    • 返回 x 是否大于或等于 y
    • x = 4; y = 3; x >= 3 返回 True
  • == (等于)
    • 比较两个对象是否相等。
    • x = 2; y = 2; x == y 返回 True
    • x = 'str'; y = 'stR'; x == y 返回 False
    • x = 'str'; y = 'str'; x == y 返回 True
  • != (不等于)
    • 比较两个对象是否不相等。
    • x = 2; y = 3; x != y 返回 True
  • not (布尔“非”)
    • 如果 xTure ,则返回 False 。如果 xFalse ,则返回 True
    • x = Ture; not x 返回 False 。
  • and (布尔“与”)
    • 如果 xFalse ,则 x and y 返回 False ,否则返回 y 的计算值。
    • xFalse 时, x = False; y = True; x and y 将返回 False 。短路计算。
  • or (布尔“或”)
    • 如果 xTrue ,则返回 True ,否则它将返回 y 的计算值。
    • x = Ture; y = False; x or y 将返回 Ture 。在这里短路计算同样适用。

变量 = 变量 运算 表达式会演变成变量 运算 = 表达式,例如a = a * 3可以演变成a *= 3

下面是 Python 的运算优先级表(引用Python 参考手册 (opens new window))。

  1. (expressions...), [expressions...], {key: value...}, {expressions...} :显示绑定或数组、显示列表、显示字典、显示设置
  2. x[index], x[index:index], x(arguments...), x.attribute :下标、切片、调用、属性引用
  3. ** :求幂
  4. +x, -x, ~x :正、负、按位取反
  5. *, /, //, % :乘、除、整除、取余
  6. +, - :加与减
  7. <<, >> :移动
  8. & :按位与
  9. ^ :按位异或
  10. | :按位或
  11. in, not in, is, is not, <, <=, >, >=, !=, == :比较,包括成员资格测试(Membership Tests)和身份测试(Identity Tests)。
  12. not x :布尔“非”
  13. and :布尔“与”
  14. or :布尔“或”
  15. if - else :条件表达式
  16. lambda :Lambda 表达式

运算符通常由左至右结合,这意味着具有相同优先级的运算符将从左至右的方式依次进行求值(上面同行的就是同一个优先级)。为了让阅读代码的人更快的理解代码,在一些不常见的优先级运算上,我们可以使用括号来加强阅读性(当然,括号也能强行修改运算优先级)。

# 三、控制流

  1. if 语句:如果条件为真(True),我们将运行一个语句块(if 块),否则(False)我们将运行另一个语句块(elif 块或 else 块)。

在 if 语句的结尾处有一个冒号 —— 我们是在向 Python 表明后面跟着一个语句块。并且用elif简化了其他语言的else if。elif 和 else 同样都必须有一个冒号在其逻辑行的末尾,后面跟着与它们相应的语句块。

不等于可以用!=,而多个语句同时满足用and,满足其中一个条件用or,判断一个元素是否存在于一个列表中可以用in(不存在就是not in)。

number = 23
guess = int(input('Enter an integer : '))
if guess == number:
  # 新块从这里开始
  print('Congratulations, you guessed it.')
  print('(but you do not win any prizes!)')
  # 新块在这里结束
elif guess < number:
  # 另一代码块
  print('No, it is a little higher than that')
  # 你可以在此做任何你希望在该代码块内进行的事情
else:
  print('No, it is a little lower than that')
  # 你必须通过猜测一个大于(>)设置数的数字来到达这里。

print('Done')
# 这最后一句语句将在
# if 语句执行完毕后执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

注意:Python 中不存在 switch 语句,在某些情况下,使用一部字典能够更快速地完成相同的操作。

  1. while 语句能够让你在条件为真的前提下重复执行某块语句,它是循环语句的一种。
number = 23
running = True
while running:
  guess = int(input('Enter an integer : '))
  if guess == number:
    print('Congratulations, you guessed it.')
    # 这将导致 while 循环中止
    running = False
  elif guess < number:
    print('No, it is a little higher than that.')
  else:
    print('No, it is a little lower than that.')

# 在这里你可以做你想做的任何事
print('Done')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. for 循环

for...in 语句是另一种循环语句,对任意序列进行迭代(例如列表或字符串),条目的迭代顺序与它们在序列中出现的顺序一致。

words = ['cat', 'window', 'defenestrate']
for w in words:
  print(w, len(w)) # len函数返回字符串的长度
1
2
3

range(stop) 函数常用于遍历数字序列(从0开始,stop结束,不包括stop)。可以不从0开始,range(start, stop[, step])按指定幅度递增(递增幅度称为 '步进',支持负数)。range()返回的是可迭代对象,可以用于 for 语句,也可以用于sum()等函数,例如sum(range(4))就是0 + 1 + 2 + 3

# 输出 0 到 4,相当于 js中的for (let i = 0; i < 5; i++)
for i in range(5):
  print(i)

# [5, 10),默认每次增加1
list(range(5, 10)) # [5, 6, 7, 8, 9]

# [0, 10),默认每次增加3
list(range(0, 10, 3)) # [0, 3, 6, 9]

# [-10, -100),默认每次增加-30
list(range(-10, -100, -30)) # [-10, -40, -70]

sum(range(4)) # 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. break 和 continue

break 语句用以中断循环语句,也就是中止整个循环语句的执行,即使循环条件没有变更为 False ,或队列中的项目尚未完全迭代依旧如此。continue 语句用以告诉 Python 跳过当前循环块中的剩余语句,并继续该循环的下一次迭代。

while True:
  s = input('Enter something : ')
  if s == 'quit':
    break
  print('Length of the string is', len(s))
print('Done')
1
2
3
4
5
6
while True:
  s = input('Enter something : ')
  if s == 'quit':
    break
  if len(s) < 3:
    print('Too small')
    continue
  print('Input is of sufficient length')
  # 自此处起继续进行其它任何处理
1
2
3
4
5
6
7
8
9

官网教程的for-else语句,其实就是当循环正常结束时会走for-else语句的else分支(如果把else换成then估计就好理解了),什么叫循环正常结束呢?也就是进入了循环但不是因为break结束的,或者根本就没有进入这个循环。注意,《Effective Python》不建议使用for-elsewhile-else(并且和try-else相反,影响阅读性)。

for n in range(2, 10):
  for x in range(2, n):
    if n % x == 0:
      print(n, 'equals', x, '*', n//x)
      break
  else:
    # loop fell through without finding a factor
    print(n, 'is a prime number')
1
2
3
4
5
6
7
8

# 四、函数

函数(Functions)是指可重复使用的程序片段。可以通过关键字def来定义,该关键字后跟一个函数的标识符名称,再跟一对圆括号(包括一些变量的名称),再以冒号结尾。

def say_hello():
  # 该块属于这一函数
  print('hello world')
# 函数结束

say_hello() # 调用函数
say_hello() # 再次调用函数
1
2
3
4
5
6
7
  1. 函数的参数(实参、形参、默认值、关键字参数、可变参数)

定义函数时会有一对圆括号,如果函数不需要从调用处拿取数据,那这对圆括号中就不需要写参数。如果,调用处传入了数据(实参),那么函数定义处的圆括号就要对应写上入参(形参),多个入参用逗号分隔。

def print_max(a, b):
  if a > b:
    print(a, 'is maximum')
  elif a == b:
    print(a, 'is equal to', b)
  else:
    print(b, 'is maximum')

# 直接传递字面值(字面量、字面常量,叫法有很多)
print_max(3, 4)

x = 5
y = 2

# 以参数的形式传递变量
print_max(x, y)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

函数的入参是可以使用默认值的,常用于外界调用处没有传参过来的场景(入参可选)。函数的入参默认值是字面量常数,它一旦定义赋值就不能更改了。还要注意的是,带默认值的可选入参必须放到参数列表的末尾,例如def func(a=5, b) 是无效的。

def say(message, times=1):
  print(message * times)

say('Hello')
say('World', 5)
1
2
3
4
5

另外,当参数很多,不想一一对应进行传递(默认顺序的位置实参),可以使用关键字实参,也就是在实参那里指定传递哪个名称的参数(这个名称必须是形参名),无需考虑顺序,它也经常搭配默认值来使用。

def func(a, b=5, c=10):
  print('a is', a, 'and b is', b, 'and c is', c)

func(3, 7) # 正常一一对应
func(25, c=24) # 第一个参数是位置对应的,第2个参数只想传c
func(c=50, a=100) # 两个参数在位置和顺序上都不是对应的
1
2
3
4
5
6

有时候你可能想要定义一个能接收任意个数参数的函数,可以通过使用星号*来实现这个功能。*一个星号是创建了一个空元组用来接收位置实参**两个星号是创建了一个空字典用来接收关键字实参。一般地,位置实参会在关键字实参前面。

# a虽然有默认值,但它接收第一个实参;
# *numbers接收的是位置实参也就是1 2 3;
# **phonebook接收的是关键字实参也就是Jack=1123,John=2231,Inge=1560
def total(a=5, *numbers, **phonebook):
    print('a', a)

    # 遍历元组中的所有项
    for single_item in numbers:
        print('single_item', single_item)

    # 遍历字典中的所有项
    for first_part, second_part in phonebook.items():
        print(first_part,second_part)

print(total(10,1,2,3,Jack=1123,John=2231,Inge=1560))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python function_varargs.py
a 10
single_item 1
single_item 2
single_item 3
Inge 1560
John 2231
Jack 1123
None
1
2
3
4
5
6
7
8
9
  1. 局部变量

如果在函数中定义了一个变量,那么这个变量只在这个函数中能被访问,也就是它的作用域是这个函数块,这种只能在局部块中被访问的变量就是局部变量。在局部变量定义后,它就会“屏蔽”外界的同名变量(定义前屏蔽不了)

x = 50
def func(x):
  print('x is', x) # 函数内的x定义前还不能屏蔽外界的x。这与js中var x = 2不一样,js有声明提前
  x = 2
  print('Changed local x to', x) # 已屏蔽外界的x
func(x)
print('x is still', x)
1
2
3
4
5
6
7
  1. global 语句

在上面的例子了,我就想操作函数外面的全局变量 x,从 50 变为 2,那么我们应该如何做?可以使用 global 语句,告知函数我想使用全局变量 x(让 x 不成为一个局部变量),然后用x = 2进行重新赋值。

x = 50

def func():
  global x

  print('x is', x)
  x = 2
  print('Changed global x to', x)

func()
print('Value of x is', x)
1
2
3
4
5
6
7
8
9
10
11
  1. return 语句

return 语句用于从一个函数 返回,在跳出这个函数时可以将值传递到外界(可选的)。一个函数没有 return 语句,或者 return 语句没有搭配任何一个值,那么它们相当于 return NoneNone,这个变量尚未被赋值或值已被清空)。

def maximum(x, y):
    if x > y:
        return x
    elif x == y:
        return 'The numbers are equal'
    else:
        return y

print(maximum(2, 3))
1
2
3
4
5
6
7
8
9
  1. 文档字符串

函数的第一行逻辑行的字符串就是文档字符串,它能够帮助我们更好地记录程序并让其更加易于理解。它书写习惯:首行首字母大写,结尾有句号;第二行为空行;第三行以后为详细的描述。

def print_max(x, y):
    '''Prints the maximum of two numbers.

    The two values must be integers.'''
    # 如果有必要,将参数转为整数
    x = int(x)
    y = int(y)

    if x > y:
        print(x, 'is maximum')
    else:
        print(y, 'is maximum')

print_max(3, 5)
print(print_max.__doc__)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 五、列表

数据结构(Data Structures)是能够将一些数据聚合在一起的一种结构。换句话说,它们是用来存储一系列相关数据的集合。Python 中有四种内置的数据结构——列表(List)、元组(Tuple)、字典(Dictionary)和集合(Set)。

列表是一种用于保存一系列有序元素的集合,通常使用方括号([])将这些“有序元素”包裹起来,然后用逗号来隔开这些“有序元素”。用len()函数可以获取列表的长度,也就是元素个数。

bicycles = ['trek', 'cannondale', 'redline', 'specialized']
len(bicycles) # 4
print(bicycles) # ['trek', 'cannondale', 'redline', 'specialized']
1
2
3

# 5.1 通过索引访问

可以通过索引下标)来访问列表中特定位置的元素(有序),与大多数语言一样,索引是从 0 开始的。注意,这个索引支持负数,它表示从右往左来访问列表中的元素。但这个负数索引不是从-0开始的(毕竟和0是一个意思),那么要访问右侧的元素那就是-1开始的。

bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles[1]) # cannondale
print(bicycles[3]) # specialized
print(bicycles[-1]) # specialized
print(bicycles[-2]) # redline
1
2
3
4
5

当访问超过列表的范围时就会报错

bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles[4]) # IndexError: list index out of range
1
2

# 5.2 修改、添加和删除元素

列表是动态、可变的,我们可以随意修改、添加、删除里面的元素。

  1. 修改元素:先用索引获取到特定元素,然后用赋值语句来修改它。
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles) # ['honda', 'yamaha', 'suzuki']

motorcycles[0] = 'ducati'
print(motorcycles) # ['ducati', 'yamaha', 'suzuki']
1
2
3
4
5
  1. 添加元素

    • append():在列表末尾添加新元素。

      motorcycles = ['honda', 'yamaha', 'suzuki']
      print(motorcycles)
      
      motorcycles.append('ducati')
      print(motorcycles)
      
      1
      2
      3
      4
      5
    • inser(index, item):在列表的任何位置添加新元素。

      motorcycles = ['honda', 'yamaha', 'suzuki']
      
      motorcycles.insert(0, 'ducati')
      print(motorcycles) # ['ducati', 'honda', 'yamaha', 'suzuki']
      
      1
      2
      3
      4
  2. 删除元素

  • del语句:先用索引获取到特定元素,然后配合del关键字删除该项。任何位置

    motorcycles = ['honda', 'yamaha', 'suzuki']
    print(motorcycles)
    
    del motorcycles[0]
    print(motorcycles)
    
    1
    2
    3
    4
    5
  • pop(index):删除列表中任意位置处的元素。index可以不写,那就表示删除列表末尾那一项元素。和del方式的区别就是pop()能获取到删除后的这一项的值。

    motorcycles = ['honda', 'yamaha', 'suzuki']
    
    first_owned = motorcycles.pop(0)
    print(first_owned) # honda
    
    1
    2
    3
    4
  • remove(item):根据元素的值来删除列表中对应的元素,如果有多个,只能删除找到的第一个(可以用循环)。

motorcycles = ['honda', 'yamaha', 'suzuki', 'ducati']

motorcycles.remove('ducati')
print(motorcycles) # ['honda', 'yamaha', 'suzuki']
1
2
3
4

# 5.3 列表排序

reverse()可以让列表反转,永久性的修改列表,当然你可以再用一次reverse()进行恢复。

cars = ['bmw', 'audi', 'toyota', 'subaru']
print(cars)

cars.reverse() # ['bmw', 'audi', 'toyota', 'subaru']
print(cars) # ['subaru', 'toyota', 'audi', 'bmw']
1
2
3
4
5

如果要让列表按照一定的规则进行排序,可以使用sort()sorted()(默认是按照首字母的顺序排序的)。这两个方法的区别就是sorted()不影响列表中的原始排列顺序,列表作为sorted()的入参进行的。

cars = ['bmw', 'audi', 'toyota', 'subaru']

print(cars) # ['bmw', 'audi', 'toyota', 'subaru']
print(sorted(cars)) # ['audi', 'bmw', 'subaru', 'toyota']
print(cars) # ['bmw', 'audi', 'toyota', 'subaru']
cars.sort()
print(cars) # ['audi', 'bmw', 'subaru', 'toyota']
1
2
3
4
5
6
7

sort()sorted()可以按照我们自己定义的规则来排序,当只有一个 key 作为判断标准时可以按照如下方式进行排序:

# 获取列表的第二个元素
def takeSecond(elem):
    return elem[1]

random = [(2, 2), (3, 4), (4, 1), (1, 3)]
# 指定第二个元素排序
random.sort(key=takeSecond) # random.sort(key=lambda x:x[1])
print ('排序列表:', random) # [(4, 1), (2, 2), (1, 3), (3, 4)]
1
2
3
4
5
6
7
8
# 获取列表的元素的'b'属性
def takeSecond(elem):
    return elem['b']

random = [{ 'a': 1, 'b': 3}, { 'a': 0, 'b': 4}, { 'a': 3, 'b': 1}, { 'a': 2, 'b': 2}]
# 指定元素的'b'属性排序
random.sort(key=takeSecond) # random.sort(key=lambda x:x['b'])
print ('排序列表:', random) # [{'a': 3, 'b': 1}, {'a': 2, 'b': 2}, {'a': 1, 'b': 3}, {'a': 0, 'b': 4}]
1
2
3
4
5
6
7
8

当涉及到多个 key 时就需要functools.cmp_to_key函数辅助我们进行自定义排序:

# 给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
# 注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
# 输入:nums = [3,30,34,5,9] 输出:"9534330"
from functools import cmp_to_key
# v1比v2的索引大1,也就是说v1是后面这项。
def auxComp(v1, v2):
    # 如果“后面这项加上前面这项”要大于“前面这项加上后面这项”,那么我们是需要将它们替换一下位置,那么要返回-1
    if int(str(v1)+str(v2)) > int(str(v2)+str(v1)):
        return -1
    # “后面这项加上前面这项”小于“前面这项加上后面这项”,那就不需要替换了,我们期望输入:nums = [3,30,34,5,9] 输出:"9534330"
    elif int(str(v1)+str(v2)) < int(str(v2)+str(v1)):
        return 1
    else:
        return 0

def largestNumber(nums):
    nums.sort(key=cmp_to_key(auxComp))
    ans = ''.join([str(num) for num in nums])
    return str(int(ans))

print(largestNumber([3,30,34,5,9])) # 9534330
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 5.4 数字列表与统计

我们控制流这一章的 for 语句就讲过range(stop),它常用于遍历数字序列(从0开始,stop结束,不包括stop)。可以不从0开始,range(start, stop[, step])按指定幅度递增(递增幅度称为 '步进',支持负数)。range()返回的是可迭代对象,如果搭配list()就可以生成数字列表

# 输出 0 到 4,相当于 js中的for (let i = 0; i < 5; i++)
for i in range(5):
  print(i)

# [5, 10),默认每次增加1
list(range(5, 10)) # [5, 6, 7, 8, 9]

# [0, 10),默认每次增加3
list(range(0, 10, 3)) # [0, 3, 6, 9]

# [-10, -100),默认每次增加-30
list(range(-10, -100, -30)) # [-10, -40, -70]
1
2
3
4
5
6
7
8
9
10
11
12

数字列表的统计,也就是它的最大值、最小值、总和:max(list)min(list)sum(list)

digits = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
min(digits) # 0
max(digits) # 9
sum(digits) # 45
1
2
3
4

# 5.5 列表解析

生成从 1 到 10 的平方的这样一个数字列表,你可能会这样写:

squares = []
for value in range(1,11):
    squares.append(value**2)

print(squares)
1
2
3
4
5

实际上我们可以用列表解析,只需要一行代码就能生成这样的数字列表:

print([value**2 for value in range(1, 11)])
1

# 5.6 列表的切片

切片的意思是对列表进行一个截取(但并不影响原列表本身),只使用数组的某个索引范围内的部分,使用方法list[start:end],索引范围是[start, end)

players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[0:3]) # ['charles', 'martina', 'michael']
print(players) # ['charles', 'martina', 'michael', 'florence', 'eli'}
1
2
3

如果没有指定 start 也就是开始索引,那么就默认为是从 0 开始的;当然,如果没有指定 end 也就是结束索引,那么就默认结束到列表末尾。start 和 end 可以是负数,但是,它仍然是从左往右截取的。像[-3:-4]就不要想它从右往左截取,结果就是[]

players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[:4]) # ['charles', 'martina', 'michael', 'florence']
print(players[2:]) # ['michael', 'florence', 'eli']
print(players[-3:]) # ['michael', 'florence', 'eli']
print(players[-3:-4]) # []
print(players[4:3]) # []
1
2
3
4
5
6

遍历切片很简单的,直接写在for-in的条件上都可以:

players = ['charles', 'martina', 'michael', 'florence', 'eli']
for player in players[:3]:
    print(player.title()) # Charles Martina Michael
1
2
3

# 5.7 复制列表

学过切片了就很简单,直接list[:]就是一个新的列表,因为切片不影响原列表。不要像下面这样复制:

my_foods = ['pizza', 'falafel', 'carrot cake']

  # 这行不通,因为复制的是地址值,并不是列表对象本身。可以这样:friend_foods = my_foods[:]
friend_foods = my_foods

my_foods.append('cannoli') # ['pizza', 'falafel', 'carrot cake', 'cannoli']
friend_foods.append('ice cream') # ['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']

print(my_foods) # ['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']
print(friend_foods) # ['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']
1
2
3
4
5
6
7
8
9
10

# 六、元组

列表是动态的、可变的,但有时候我们需要创建一系列不可修改的元素,这种数据结构就是元组。元组使用的是圆括号而非中括号来标识的,当然,元素仍然是用逗号分隔的。即使元组中只有一个元素了,后面也必须带一个逗号

dimensions = (200, 50)
dimensions[0] = 250 # TypeError: 'tuple' object does not support item assignment

my_t = (3,) # 如果你要定义只包含一个元素的元组,必须在这个元素后面加上逗号
1
2
3
4

循环切片复制统计生成数字元组和列表是类似的,而修改添加删除排序是不被允许的。

dimensions = (200, 50, 100, 400)
for dimension in dimensions:
    print(dimension) # 200 50 100 400

print(dimensions[:2]) # (200, 50)
print(dimensions[1:]) # (50, 100, 400)
print(dimensions) # (200, 50, 100, 400)
print(dimensions[:]) # (200, 50, 100, 400)

print(max(dimensions)) # 400
print(min(dimensions)) # 50
print(sum(dimensions)) # 750

print(tuple(range(1, 11))) # (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 七、字典

字典是一系列的键值对,每个都与一个相关联,这个可以是数、字符串、列表乃至字典,也就是值可以是任意对象。

使用花括号{})来标识字典,使用逗号分隔一对对的键值对,键值对之间使用冒号隔开。注意,键必须是合法字符串,引号不能省略,这与 js 不一样。

alien_0 = {'color': 'green', 'points': 5}
1

# 7.1 访问字典中值

像列表是使用索引进行访问的,而字典是通过来访问的,字典[键名]

alien_0 = {'color': 'green', 'points': 5}
print(alien_0['points']) # 5
1
2

通过访问有一个问题,如果这个键不存在,那么就会报错。那么在不确定目标键是否存在的情况下,可以通过get(key, default)方法来访问字典中的值。

alien_0 = {'color': 'green', 'speed': 'slow'}

point_value = alien_0.get('points', '')
print(point_value) # 返回的是get的第二个参数空字符串
1
2
3
4

# 7.2 修改、添加和删除键值对

字典和列表一样是一个动态结构,可以随意修改、添加和删除键值对。

  1. 修改:先通过键访问,再让它指向新对象。
alien_0 = {'color': 'green', 'points': 5}
alien_0['points'] = 6
print(alien_0) # {'color': 'green', 'points': 6}
1
2
3
  1. 添加:用“键访问”的形式创建新的键值对,但要注意不要与已有的重名了,重名就相当于修改了。
alien_0 = {'color': 'green', 'points': 5}
print(alien_0) # {'color': 'green', 'points': 5}

alien_0['x_position'] = 0
alien_0['y_position'] = 25
print(alien_0) # {'color': 'green', 'points': 5, 'x_position': 0, 'y_position': 25}
1
2
3
4
5
6
  1. 删除:使用del 语句将已有的键值对删除。
alien_0 = {'color': 'green', 'points': 5}
print(alien_0) # {'color': 'green', 'points': 5}

del alien_0['points']
print(alien_0) # {'color': 'green'}
1
2
3
4
5

# 7.3 遍历字典

  1. 遍历所有键值对:用items()获取字典所有的键值对,for k, v in user_0.items()

    user_0 = {
        'username': 'efermi',
        'first': 'enrico',
        'last': 'fermi',
    }
    
    for key, value in user_0.items():
        # Key: username, Value: efermi. Key: first, Value: enrico. Key: last, Value: fermi.
        print(f"Key: {key}, Value: {value}.")
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  2. 遍历所有键:用keys()获取字典所有的键,for k in user_0.keys()或者for k in user_0。拓展:可以在keys()获取所有键后使用sorted()将它们排好序再操作字典。

    favorite_languages = {
        'jen': 'python',
        'sarah': 'c',
        'edward': 'ruby',
        'phil': 'python',
    }
    
    # 等价于for name in favorite_languages
    for name in favorite_languages.keys():
        # Jen Sarah Edward Phil
        print(name.title())
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  3. 遍历所有值:用values()获取字典所有的值,for v in user_0.values(),但要剔除重复值可以搭配set()方法来使用。

    favorite_languages = {
        'jen': 'python',
        'sarah': 'c',
        'edward': 'ruby',
        'phil': 'python',
    }
    
    for language in set(favorite_languages.values()):
        print(language.title())
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

# 7.4 嵌套

  1. 列表套字典

    alien_0 = {'color': 'green', 'points': 5}
    alien_1 = {'color': 'yellow', 'points': 10}
    alien_2 = {'color': 'red', 'points': 15}
    
    aliens = [alien_0, alien_1, alien_2]
    
    for alien in aliens:
        # {'color': 'green', 'points': 5} {'color': 'yellow', 'points': 10} {'color': 'red', 'points': 15}
        print(alien)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  2. 字典套列表

    favorite_languages = {
        'jen': ['python', 'ruby'],
        'sarah': ['c'],
        'edward': ['ruby', 'go'],
        'phil': ['python', 'haskell'],
    }
    
    for name, languages in favorite_languages.items():
        print(f"\n{name.title()}'s favorite languages are:")
        for language in languages:
            print(f"\t{language.title()}")
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  3. 字典套字典

users = {
    'aeinstein': {
        'first': 'albert',
        'last': 'einstein',
        'location': 'princeton',
    },
    'mcurie': {
        'first': 'marie',
        'last': 'curie',
        'location': 'paris',
    },
}

for username, user_info in users.items():
    print(f"\nUsername: {username}")
    full_name = f"{user_info['first']} {user_info['last']}"
    location = user_info['location']

    print(f"\tFull name: {full_name.title()}")
    print(f"\tLocation: {location.title()}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 八、集合

集合(set)是简单对象的无序的集合(collection),它的元素没有顺序也不能重复。

# 九、面向对象编程

# 9.1 基本概念

面向对象编程是将要解决的问题抽象成类或对象相关的问题,例如“人开车”就可以抽象出“人类”和“车类”以及“开车”这个方法,然后就要区分是具体什么人什么车等等。

类(Class)也就是抽象出的类型(Type),而涉及到具体问题中的对象(Object)就是类的实例(Instance),比如“小明是一个人类”,“人类”就是类(Class),“小明”就是一个实例(Instance)。

类或对象有自己的变量和函数,也就是属性方法。具体还能分为实例属性和实例方法,以及类属性和类方法。

# 9.2 class、self、构造函数、实例化

使用class定义一个类,类名首字母大写,这与一般的编程语言一样。构造函数就不一样了,Python 使用的是__init(self, ...)__,四个下划线包裹init,表示这是内部方法。

至于self形参(类似其他语言的this),它是指向实例本身的一个引用;它必须位于其他形参之前,在调用处(实例化的地方)是不需要我们手动传递self的,它是类实例化时自动传递过来的。实例方法的定义处也有self形参,实例方法被调用时也不需要手动传递self

在对类进行实例化时,不需要使用new。还要切记self不需要手动传递。访问属性也很简单,用.即可,调用方法也是用这个.

# 定义(创建)一个类
class Dog:
    """一次模拟小狗的简单尝试。"""
    def __init__(self, name, age): # 必须带有self,并且在其他形参前面
        """初始化实例属性name和age。"""
        self.name = name
        self.age = age
    # 实例方法,必须带有self
    def sit(self):
        """模拟小狗收到命令时蹲下。"""
        print(f"{self.name} is now sitting.")
    # 实例方法,必须带有self
    def roll_over(self):
        """模拟小狗收到命令时打滚。"""
        print(f"{self.name} rolled over!")

# 实例化一个类
my_dog = Dog('Willie', 6)
# 访问实例属性
print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")
# 调用实例方法
my_dog.sit()
my_dog.roll_over()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 9.3 实例属性和类属性

实例属性:是每个对象或实例所单独拥有的。每个对象都拥有属于它自己的属性的副本,也就是说,它们不会被共享,也不会以任何方式与其它不同实例中的相同名称的属性产生关联。实例方法也是如此。

类属性:是可以被属于该类的所有实例访问,也就是被所有实例共享的。该类变量只拥有一个副本,当任何一个对象对类变量作出改变时,发生的变动将在其它所有实例中都会得到体现。类方法也是如此。

定义类属性不需要static,但需要与方法同级实例属性在构造函数中生成)。类方法不需要static,类方法需要加上@classmethod装饰器用以区分同级的实例方法,当然也可以是how_many = classmethod(how_many)生成类方法。使用类属性时要通过类名,当然,你也可以通过self.__class__.xxx类访问 xxx 类属性,因为每个对象都通过self.__class__属性来引用它的类。

class Robot:
    # 类属性(静态属性),统计机器人的个数
    population = 0
    # 构造函数
    def __init__(self, name):
        # 每个机器人有自己的名字
        self.name = name
        # 当机器人被生产出来的时候,机器人人口增加
        Robot.population += 1
    # 实例方法,机器人死掉
    def die(self):
        print(f"{self.name} is being destroyed!") # 看一下是哪个机器人死掉了
        Robot.population -= 1 # 总的机器人数量减一
    # 实例方法,机器人打招呼
    def say_hi(self):
        print(f"Greetings, my masters call me {self.name}.")
    # 类方法(静态方法),查看目前还有多少个机器人
    @classmethod # 装饰器
    def how_many(cls):
        print(f"We have {cls.population:d} robots.")

droid1 = Robot("R2-D2")
droid1.say_hi()
Robot.how_many()

droid2 = Robot("C-3PO")
droid2.say_hi()
Robot.how_many()

droid1.die()  # 实例方法使用实例调用
Robot.how_many() # 类方法使用类名调用
droid2.die()
Robot.how_many()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

另外,私有属性__privatevar 这样的形式,也就是双下划线作为前缀。

# 9.4 继承

如果要编写的类是另一个现成类的特殊版本,可使用继承。一个类继承另一个类时,将自动获得另一个类的所有属性和方法。原有的类称为父类,而新类称为子类。子类继承了父类的所有属性方法,同时还可以定义自己的属性和方法。

# 9.4.1 如何继承

  1. 创建子类时,父类必须包含在当前文件中,且位于子类前面
  2. 创建子类时,必须在圆括号内指定父类的名称
  3. 子类的方法__init__()内,要使用super().__init__()调用父类的构造函数,并传入父类构造函数所需的参数(切记不要手动传self)。
# 父类,必须在子类的前面
class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_descriptive_name(self):
        long_name = f"{self.year} {self.make} {self.model}"
        return long_name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it.")

    def update_odometer(self, mileage):
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")

    def increment_odometer(self, miles):
        self.odometer_reading += miles

# 子类,圆括号内必须写上父类的名称
class ElectricCar(Car):
    def __init__(self, make, model, year):
        # 在子类的构造函数中必须使用super()来调用父类的构造函数
        super().__init__(make, model, year)

my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 9.4.2 个性化以及重写

让一个类继承另一个类后,就可以添加区分子类和父类所需的新属性新方法了。

class Car:
    # --snip--

class ElectricCar(Car):
    def __init__(self, make, model, year):
        super().__init__(make, model, year)
        # ElectricCar独有的实例属性
        self.battery_size = 75
    # ElectricCar独有的实例方法
    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")
1
2
3
4
5
6
7
8
9
10
11

如果父类的方法达不到我们的预期,我们可以子类中重写同名的方法。使用继承时,可让子类保留从父类那里继承而来的精华,并剔除不需要的糟粕。

class ElectricCar(Car):
    --snip--

    # 重写父类的get_descriptive_name
    def get_descriptive_name(self):
        --snip--
1
2
3
4
5
6

# 十、模块

# 10.1 模块的使用

一个或多个函数可以重复使用,一个类或多个类也可以重复使用,它们都可以通过模块来实现的。

# 引入系统模块。
import sys

print('The command line arguments are:')
# 使用系统模块
for i in sys.argv:
    print(i)

print('\n\nThe PYTHONPATH is', sys.path, '\n')
1
2
3
4
5
6
7
8
9
$ D:/Python37/python.exe e:/BaiduSyncdisk/workspace/PythonWorkSpace/pyside-study/test.py # 每个参数都由空格隔开
The command line arguments are:
e:/BaiduSyncdisk/workspace/PythonWorkSpace/pyside-study/test.py


The PYTHONPATH is [
  'e:\\BaiduSyncdisk\\workspace\\PythonWorkSpace\\pyside-study',
  'D:\\Python37\\python37.zip',
  'D:\\Python37\\DLLs',
  'D:\\Python37\\lib',
  'D:\\Python37',
  'D:\\Python37\\lib\\site-packages'
]
1
2
3
4
5
6
7
8
9
10
11
12
13

import关键词来引入模块,然后 Python 解释器会在该模块的 path 变量中列出来的目录中寻找它,找到它并会运行模块中的代码。当我们运行 python module_using_sys.py we are arguments 时,我们通过 python 命令来运行 module_using_sys.py 模块,后面的内容则是传递给程序的参数。 Python 将命令行参数存储在 sys.argv 变量中供我们使用。

每一个模块都有一个名称,在模块中我们可以通过模块的__name__属性来判断是独立运行还是被导入的

if __name__ == '__main__':
    print('This program is being run by itself')
else:
    print('I am being imported from another module')
1
2
3
4

# 10.2 导入函数

如果你希望直接将 argv 变量导入你的程序(为了避免每次都要输入 sys. ),那么你可以通过使用 from sys import argv 语句来实现这一点。如果没有命名冲突,是可以用from...import语句的,有命名冲突就要使用单纯的import语句了。

def say_hi():
    print('Hi, this is mymodule speaking.')

__version__ = '0.1'
1
2
3
4
from mymodule import say_hi, __version__

say_hi()
print('Version', __version__)
1
2
3
4

上面的导入有点冗余,可以使用from mymodule import *来一次性导入所有函数等,只是通常不建议这么做,可能会出现命名冲突的问题。

内置的 dir() 函数能以列表的形式返回某个对象定义的一系列标识符。如果这个对象是个模块,返回的列表中会包含模块内部所有的函数、类和变量。这个函数接收一个可选的参数。当参数是模块名时,函数会返回对应模块的标识符列表。没有参数时则会返回当前模块的标识符列表。

python
>>> import sys

# 获取 sys 模块内所有属性的标识符
>>> dir(sys)
['__displayhook__', '__doc__',
'argv', 'builtin_module_names',
'version', 'version_info']
# 这里只列出了部分输出

# 获取当前模块内属性的标识符
>>> dir()
['__builtins__', '__doc__',
'__name__', '__package__', 'sys']
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 10.3 导入类

最原始的写法就是将整个模块都导入进来,通过模块名来访问里面的类。

# 导入整个模块
import car

my_beetle = car.Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())

my_tesla = car.ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
1
2
3
4
5
6
7
8

一般是将 Car 类单独写到Car.py中,在另一个 py 文件中使用from car import Car就可以将这个 Car 类导入进来。

from car import Car

my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())

my_new_car.odometer_reading = 23
my_new_car.read_odometer()
1
2
3
4
5
6
7

如果 Car 类和 ElectricCar 类同写在一个 py 文件中,依然可以用from...import语句,Car 类和 ElectricCar 类之间需要用逗号,隔开。

from car import Car, ElectricCar

my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())

my_tesla = ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
1
2
3
4
5
6
7

上面的导入语句太长有些冗余,可以使用from module_name import *,但是,和上一节一样我们不建议这么做,可能会有命名冲突的问题。

from car import *

my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())

my_tesla = ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
1
2
3
4
5
6
7

多个类写在同一个 py 文件中,在每个类都很庞大的时候就会显得特别臃肿,所以尽可能的将它们拆分出来放在单独的 py 文件中。

from car import Car
from electric_car import ElectricCar

my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())

my_tesla = ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
1
2
3
4
5
6
7
8

# 10.4 as 别名

导入函数的名称可能与程序中现有的名称冲突,或者函数的名称太长。那么我们可指定简短而独一无二的别名:函数的另一个名称,类似于外号。导入类也可以使用别名

from pizza import make_pizza as mp

mp(16, 'pepperoni')
mp(12, 'mushrooms', 'green peppers', 'extra cheese')
1
2
3
4
from electric_car import ElectricCar as EC

my_tesla = EC('tesla', 'roadster', 2019)
1
2
3

如果导入整个模块并且模块名特别长,也可以使用别名

import pizza as p

p.make_pizza(16, 'pepperoni')
p.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
1
2
3
4

# 十一、文件和异常

# 11.1 从文件中读取数据

# 11.1.1 读取整个文件

读取整个文件,也就是一口气将文件内所有信息都拿到。

with open('pi_digits.txt') as file_object:
    contents = file_object.read()
print(contents) # print(contents.rstrip())
1
2
3
  1. open()函数将名为pi_digits.txt文件打开(当前执行的文件所在的目录中查找指定的文件)
  2. open()函数会返回一个表示文件的对象,我们使用as关键词将这个文件对象重命名为file_object
  3. 关键字with的作用就是在不再需要访问文件后将其关闭。也就是说我们无需手动调用close(),Python 会在合适的时间帮我们关闭。
  4. 我们通过文件对象的read()就能读取到文件里的内容了。

相比于原始文件,该输出唯一不同的地方是末尾多了一个空行。为何会多出这个空行呢?因为 read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除多出来的空行,可在函数调用 print()中使用 rstrip()。

# 11.1.2 逐行读取

读取文件时,常常需要检查其中的每一行:可能要在文件中查找特定的信息,或者要以某种方式修改文件中的文本。

不使用read(),使用for-in遍历文件对象file_object即可:

with open('pi_digits.txt') as file_object:
    for line in file_object:
        print(line) # print(line.rstrip())
1
2
3

这个文件中,每行的末尾都有一个看不见的换行符,而函数调用 print()也会加上一个换行符,我们需要使用 rstrip()消除多出来的一行。

# 11.1.3 with 外部使用文件内容

with 外部使用文件内容,因为 with 结束后文件流就关闭了,所以普通情况是拿不到文件内容的。可以使用readlines()将文件内容临时存储起来,在 with 结束后即使文件流关闭了,这个临时存储对象还在。这个临时存储对象实际上是一个列表。

filename = 'pi_digits.txt'

with open(filename) as file_object:
    lines = file_object.readlines() # lines是一个列表,并且它不是局部变量,它的作用域不是with这个代码块

for line in lines:
    print(line.rstrip())
1
2
3
4
5
6
7

# 11.2 写入文件

# 11.2.1 写入空文件

写入文件的代码形式和读取文件类似,有些 api 用法不一样。open()需要传入第二个参数了,也就是打开的模式。第二参是我们在上一节是省略的,其实它默认传递的是r,也就是读取模式。然后要把read()换成write()

with open('programming.txt', 'w') as file_object:
    file_object.write("I love programming.")
1
2

文件打开的模式有读取模式('r')、写入模式('w')、附加模式('a')或读写模式('r+')

写入模式('w')下,如果要写入的文件不存在,函数open()自动创建它;如果要写入的文件已经存在,Python 将在返回文件对象前清空该文件的内容,然后再写入你的内容。

# 11.2.2 写入多行

函数write()不会在写入的文本末尾添加换行符,如果你想写入多行内容,那么需要我们手动在每行的内容后面加上\n

with open('programming.txt', 'w') as file_object:
    file_object.write("I love programming.\n")
    file_object.write("I love creating new games.\n")
1
2
3

# 11.2.3 追加内容到已有文件

前面说过写入模式('w')下如果文件存在会先清空文件原有的内容,再添加新的内容。那么,我们只想追加内容到已有文件,而不覆盖以前的内容该怎么办呢?

可以使用附加模式('a'):

with open('programming.txt', 'a') as file_object:
    file_object.write("I also love finding meaning in large datasets.\n")
    file_object.write("I love creating apps that can run in a browser.\n")
1
2
3

# 11.3 异常

# 11.3.1 try-except 语句

对于可能发生异常的程序我们可以使用 try-except 代码块来处理。

try:
    print(5/0)
except ZeroDivisionError:
    print("You can't divide by zero!")
1
2
3
4

# 11.3.2 try-else 语句

try-except-else中的else,是try成功执行后(没有发生异常)才执行这个else代码块。

filename = 'alice.txt'

try:
    with open(filename, encoding='utf-8') as f:
        contents = f.read()
except FileNotFoundError:
    print(f"Sorry, the file {filename} does not exist.")
else:
    # 计算该文件大致包含多少个单词。
    words = contents.split()
    num_words = len(words)
    print(f"The file {filename} has about {num_words} words.")
1
2
3
4
5
6
7
8
9
10
11
12

# 11.3.3 自定义异常并手动引发

可以自定义异常并且手动引发异常,使用raise语句。

class ShortInputException(Exception):
    '''用户定义的异常对象'''
    def __init__(self, length, atleast):
        Exception.__init__(self)
        self.length = length
        self.atleast = atleast

try:
    text = input('Enter something --> ')
    if len(text) < 3:
        raise ShortInputException(len(text), 3)
    # 其他程序可以在这里正常执行
except EOFError:
    print('Why did you do an EOF on me?')
except ShortInputException as ex:
    print(('ShortInputException: The input was ' +
           '{0} long, expected at least {1}')
          .format(ex.length, ex.atleast))
else:
    print('No exception was raised.')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 11.3.2 try-finally 语句

如果没有使用with语句读取文件,那么在什么时候关闭文件流就显得异常重要,try-finally能帮我们解决这个问题。

import sys
import time

f = None
try:
    f = open("poem.txt")
    # 我们通常读取文件的语句
    while True:
        line = f.readline()
        if len(line) == 0:
            break
        print(line, end='')
        sys.stdout.flush()
        print("Press ctrl+c now")
        # 让程序保持运行一段时间
        time.sleep(2)
except IOError:
    print("Could not find file poem.txt")
except KeyboardInterrupt:
    print("!! You cancelled the reading from the file.")
finally:
    if f:
        f.close()
    print("(Cleaning up: Closed the file)")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 11.3.3 异常静默

try执行失败,会走except代码块,一般会在此打印日志。如果不想打印日志或者说暂时不知道怎么处理,可以使用pass语句。

def count_words(filename):
    """计算一个文件大致包含多少个单词。"""
    try:
        --snip--
    except FileNotFoundError:
        pass
    else:
        --snip--

filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
    count_words(filename)
1
2
3
4
5
6
7
8
9
10
11
12

# 11.4 使用 json.dump()和 json.load()

函数json.dump(xxx, fileObj)接受两个实参:要存储的数据,以及可用于存储数据的文件对象。

import json

numbers = [2, 3, 5, 7, 11, 13]

filename = 'numbers.json'
with open(filename, 'w') as f:
    json.dump(numbers, f) # 将numbers列表以json的形式存入到f文件对象中
1
2
3
4
5
6
7

函数json.load(fileObj)与上面的相反,是从文件对象中读取 json 数据。

import json

filename = 'numbers.json'
with open(filename) as f:
    numbers = json.load(f)

print(numbers)
1
2
3
4
5
6
7

# 十二、标准库

# 12.1 sys 模块

sys.argv:包含了命令行参数 sys.version_info:Python 版本信息的 version_info 元组

import sys
sys.version_info
sys.version_info(major=3, minor=6, micro=0, releaselevel='final', serial=0)
sys.version_info.major == 3
True
1
2
3
4
5

# 12.2 logging 模块

logging模块收集一些调试消息或重要消息。

import os # 与操作系统进行交互的 os 模块
import platform # 获得比如操作系统等平台信息的 platform 模块
import logging # 处理日志(log)信息的 logging 模块

if platform.platform().startswith('Windows'): # 检查我们所用操作系统类型
    # 获得文件的完整位置
    logging_file = os.path.join(os.getenv('HOMEDRIVE'), # 存储信息的主驱动器
                                os.getenv('HOMEPATH'), # 用户根文件夹
                                'test.log') # 文件名
else:
    logging_file = os.path.join(os.getenv('HOME'),
                                'test.log')

print("Logging to", logging_file)

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s : %(levelname)s : %(message)s',
    filename=logging_file,
    filemode='w',
)

logging.debug("Start of the program")
logging.info("Doing something")
logging.warning("Dying now")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
python stdlib_logging.py
Logging to /Users/swa/test.log

cat /Users/swa/test.log
2014-03-29 09:27:36,660 : DEBUG : Start of the program
2014-03-29 09:27:36,660 : INFO : Doing something
2014-03-29 09:27:36,660 : WARNING : Dying now
1
2
3
4
5
6
7

# 十三、更多

# 13.1 传递元组

从函数中返回两个不同的值,使用元组的形式返回,外界接收的时候用x, y的形式,这样会非常的方便。

def get_error_details():
    return (2, 'details')

errnum, errstr = get_error_details()
print(errnum)
print(errstr)
1
2
3
4
5
6

请注意, a, b = <某些表达式> 的用法会将表达式的结果解释为具有两个值的一个元组。

>>> a = 5; b = 8
>>> a, b
(5, 8)
>>> a, b = b, a
>>> a, b
(8, 5)
1
2
3
4
5
6

# 13.2 特殊方法

__init__(self, ...):在返回新创建可以使用的对象之前调用此方法。 __del__(self):在对象被销毁之前调用(具有不可预测时机,所以避免使用它)。 __str__(self):当我们使用 print 函数或使用 str() 时调用。 __lt__(self, other):使用小于( less than )运算符(<)时调用。 同样,所有运算符都有特殊的方法(+> 等)。 __getitem__(self, key):使用 x[key] 索引操作时调用。 __len__(self):当内置的 len() 函数用于序列对象时调用。

# 13.3 单个语句块

如果你的语句块只包括单独的一句语句,那么你可以在同一行指定它,例如条件语句与循环语句。不建议这么做。

flag = True
if flag: print('Yes')
...
Yes
1
2
3
4

# 13.4 Lambda 格式

lambda 语句用于创建新的函数对象。 基本上, lambda 采用一个参数,后跟一个表达式。 Lambda 成为函数的函数体。 新函数返回此表达式的值。

points = [{'x': 2, 'y': 3},
          {'x': 4, 'y': 1}]
points.sort(key=lambda i: i['y'])
print(points) # [{'y': 1, 'x': 4}, {'y': 3, 'x': 2}]
1
2
3
4

# 13.5 assert 语句

assert语句用于断言某值为True。 例如,如果您非常确定您正在使用的列表中至少有一个元素并且想要检查它,并且如果不是True则引发错误,那么assert语句在这种情况下是理想的。 当assert语句失败时,会引发AssertionErrorassert语句应该是明智地使用。 大多数情况下,最好能捕获异常、处理问题或向用户显示错误消息然后退出。

mylist = ['item']
assert len(mylist) >= 1
mylist.pop()
'item'
assert len(mylist) >= 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError
1
2
3
4
5
6
7
8