Vinn's Studio

PythonTips

Word count: 6.1kReading time: 26 min
2021/03/18 Share

Python 中的一些小提示。

1. 关于引用 (Reference)

1.1. 引用 (Reference) 与复制 (Copy)

1.1.1. 引用:浅复制 (Shallow Copy)

浅复制只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。Python 中的引用就是一种浅复制。

在将一个变量赋值给另一个变量时,若变量必须保存可变数据类型的值(例如列表或字典),Python 就会使用引用

1
2
3
4
5
6
7
8
source = [0, 1, 2, 3, 4, 5]
target = source

source[1] = "hello"
target[3] = None

print(source)
print(target)

此处 target = source 只会将指向 source 中列表的引用赋值到 target,而不是列表值的本身,这就意味存储在 targetsource 中的值(此处的两个就是两个引用)现在指向了同一个列表,因此,此时对其中任意一个变量的进行操作都会影响到那个被指向的列表,从而同时改变两个变量的值:

1
2
[0, 'hello', 2, None, 4, 5]
[0, 'hello', 2, None, 4, 5]
注意:

此处的操作指对变量的的操作,不包括对变量本身的完全重新赋值。显然,只有对变量的值(此处的就是引用)进行的修改才会影响到引用指向的内容,如果直接给这个变量名赋一个全新的值,则它将使用一块新的内存,直接覆盖掉这个引用,而不是对引用指向的内容进行修改:

1
2
3
4
5
6
7
source = [0, 1, 2, 3, 4, 5]
target = source

source = ['a', 'b', 'c', 'd', 'e']

print(source)
print(target)

此处对 source 进行了完全重新的赋值(而不是对 source 的值进行一些操作),因此,并不会对 target 造成影响:

1
2
['a', 'b', 'c', 'd', 'e']
[0, 1, 2, 3, 4, 5]

若希望对变量本身的完全重新赋值不使用新内存,而是在原来的内存空间上修改(即保留引用),则可以使用 list[:]

1
2
3
4
5
6
7
source = [0, 1, 2, 3, 4, 5]
target = source

source[:] = ['a', 'b', 'c', 'd', 'e']

print(source)
print(target)

在这种情况下,在 source 原内存上的修改也会影响到 target

1
2
['a', 'b', 'c', 'd', 'e']
['a', 'b', 'c', 'd', 'e']

1.1.2. 深复制 (Deep Copy)

深复制会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会影响原对象。

在将一个变量赋值给另一个变量时,若变量需要保存的是不可变数据类型的值(例如字符串、整型或元组),Python 变量就会深复制值本身:

1
2
3
4
5
6
7
source = 42
target = source

source = 100

print(source)
print(target)

将 42 赋值给变量 source,然后复制 source 的值并将其赋值给变量 target;当稍后将 source 中的值改变成 100 时,并不会影响 target 中的值(这是因为这两个变量是不同的变量,保存了不同的对象),因此输出如下:

1
2
100
45

1.2. Python 复制模块

对于可变数据类型(例如列表或字典),如果不想使用引用(浅复制),而是使用深复制进行赋值,可以使用 copy 库中的函数:

  • copy.copy():对于简单对象(指不存在嵌套结构的对象),该函数可以实现深复制,但是对于一个复杂对象(指存在嵌套结构的列表或字典)的子对象,则只能实现浅复制
  • copy.deepcopy():对于所有对象,都能实现深复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import copy

source = [1, 2, [3, 4]]

target0 = source
target1 = copy.copy(source)
target2 = copy.deepcopy(source)

print(target1 == target2) # 查看 target1 与 target2 的值是否相等
print(target1 is target2) # 查看 target1 与 target2 是否是同一个对象
print()

# 修改原列表的一个元素
source[1] = 'two'
print(target0)
print(target1)
print(target2)
print()

# 修改原列表的嵌套子列表中的一个元素
source[2][0] = 'three'
print(target0)
print(target1)
print(target2)

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# target1 与 target2 的值相等,但是不是同一个对象(它们分别是原列表 source 的一个复制)
True
False

# 修改原列表的一个元素,直接赋值的 target0 使用了引用,因此受到影响;其他两个变量则不受影响
[1, 'two', [3, 4]]
[1, 2, [3, 4]]
[1, 2, [3, 4]]

# 修改原列表的嵌套子列表中的一个元素,只有完全深复制的 target2 不受影响
[1, 'two', ['three', 4]]
[1, 2, ['three', 4]]
[1, 2, [3, 4]]

实例一:函数传递引用

当函数被调用时:

  • 可变数据类型(例如列表或字典)的实际参数以引用的形式传入函数中(即函数中的形式参数是实际参数的引用),针对这些形式参数的操作会反应到函数外的实际参数;
  • 不可变数据类型(例如字符串、整型或元组)的实际参数则会以复制的形式传入函数中(即函数中的形式参数是实际参数的复制),针对这些形式参数的操作不会影响函数外的实际参数;
1
2
3
4
5
6
7
8
9
10
11
def eggs(para1, para2):
para1.append('bacon')
para2 += 'bacon'

former = [1, 2, 'toast']
latter = 'Cheese'

eggs(former, latter)

print(former)
print(latter)

此处 eggs() 函数分别对形式参数 para1para2 做了一些操作,不同之处在于:

  • para1 的值来自实际参数 former,这是一个列表,因此传入到 para1 的是一个指向列表 [1, 2, 'toast']引用,所以 eggs() 进行的操作会反馈到 former 上;
  • para2 的值来自实际参数 latter,这是一个字符串,因此传入到 para2 的是字符串 'Cheese' 的一个复制,所以 eggs() 进行的操作不会影响 latter
1
2
[1, 2, 'toast', 'bacon']
Cheese

实例二:二维数组赋值问题

如果使用如下代码声明一个二维数组:

1
myArray = [[0] * 5] * 9

则可以得到:

1
2
3
4
5
6
7
8
9
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]

如果想要对这个二维数组某个元素进行赋值:

1
myArray[1][2] = 1

会发现整列都会被赋值:

1
2
3
4
5
6
7
8
9
[[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]]

这是因为 [[0] * 5] * 9 表示的是 9 个指向 [0]*5 这个列表的引用,所以当修改其中的某一个值时,整个列表都会被改变。

换一种初始化方式可以解决这个问题:

1
myArray = [[0] * 5 for _ in range(9)]

则可以对这个二维数组某个元素进行赋值:

1
myArray[1][2] = 1

结果为:

1
2
3
4
5
6
7
8
9
[[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]

2. 字符串中引用变量

连字符 +

1
2
3
4
5
name = 'Vinn'  
print('my name is ' + name)

# 输出为
# my name is Vinn

占位字符 %

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
name = 'Vinn'  
age = 25
price = 4500.225

# %s -- 为字符串占位
print('my name is %s'%(name))
# %d -- 为整型占位
print('i am %d'%(age)+' years old')
# %f -- 为浮点型占位(不控制位数)
print('my price is %f'%(price))
# %.2f -- 为浮点型占位(保留 2 位小数)
print('my price is %.2f'%(price))

# 输出为
# my name is Vinn
# i am 25 years old
# my price is 4500.225000
# my price is 4500.23

方法 .format()

1
2
3
4
5
6
7
8
name = 'Vinn'  
age = 25
price = 4500.225
info = 'my name is {my_name}, i am {my_age} years old, my price is {my_price}'.format(my_name=name,my_age=age,my_price=price)
print(info)

# 输出为
# my name is zhangsan, i am 25 years old, my price is 4500.225

标记字符串

  • 字符串前加 u:表示该字符串以 Unicode 格式进行编码,一般用在中文字符串前面,防止因为源码储存格式问题,导致再次使用时出现乱码;
  • 字符串前加 r:取消转义字符,表示该字符串中 \ 不再具有转义能力(\n\t 等不再具有特殊含义);
  • 字符串前加 b:表示该字符串是 Bytes 类型(网络编程中,服务器和浏览器只认 Bytes 类型数据);
  • 字符串前加 f:表示在该字符串内,{} 中的内容被视为 python 表达式(可以在此引用变量);
1
2
3
4
5
6
7
str1 = u"我是含有中文字符组成的字符串。" # 防止中文乱码
str2 = r"\n\n\n\n" # 输出 str2 时不会发生换行
str3 = b"<h1>Hello World!</h1>" # str3 是一个 Bytes 对象

name = 'Vinn'
birth = 1997
str4 = f"My name is {name}. I am {2021 - birth} years old in 2021." # 输出为:My name is Vinn. I am 24 years old in 2021.

3. 帮助函数

dir()

dir() 不带参数时,返回当前工作空间内的所有变量、方法和定义的类型列表;带参数时,返回参数的属性、函数或方法的列表。可用于查看某个模块/库包含的所有函数,某个数据类型/变量包含的所有方法:

1
2
3
4
5
6
7
8
# 语法:其中 object 可以是对象、变量、类型等
dir(object)

# examples
dir() # 查看当前工作空间的所有变量、方法和定义的类型列表
dir(list) # 查看 list 类型的所有方法
dir([1,2,3]) # 查看 list 类型的所有方法(用实例化的具体变量作为参数)
dir(torch) # 查看 torch 模块的所有函数

help()

help() 用于查看模块/函数/数据类型的详细说明:

1
2
3
4
5
6
7
8
9
# 语法:object 为具体的模块、数据类型、函数等
help(object)

# examples
help(sys) # 查看 sys 模块的帮助文档
help(sys.exit) # 查看 sys 模块的 exit() 函数的帮助文档
help(set) # 查看 set 类型的帮助文档
help({1,2,3}) # 查看 set 类型的帮助文档(用实例化的具体变量作为参数)
help({1,2,3}.pop) # 查看 set 类型的 .pop() 方法的帮助文档

属性文档

每个对象都会有一个 __doc__ 属性,用于描述该对象的作用。该属性的内容来源与:

  • 对于文件:一个文件的任意一条可执行的代码之前的内容;
  • 对于类:在类定义语句之后,任意一条可执行代码之前的内容;
  • 对于函数:在函数定义语句之后,任意一条可执行代码之前的内容;
1
2
3
4
5
6
7
8
9
10
 class Drunk:
'This is an example for the document of Class.'
def __init__(self, name, day):
'This is an example for the document of function/method.'
self.name = name
self.day = day

example = Drunk('David', 'Today')
print(example.__doc__)
print(example.__init__.__doc__)

输出为:

1
2
This is an example for the document of Class.
This is an example for the document of function/method.

4. 常用函数

Counter()

Counter() 函数用于遍历列表中的所有元素,并记录所有元素的出现个数,返回一个类似字典 Dict 的 Counter 类对象(事实上,Counter 是 Dict 的子类):

1
2
3
4
5
from collections import Counter

a = [6, 'Sunday', 3, 3, 5, 7, 7, 7]

Counter(a)

输出为:

1
Counter({6: 1, 'Sunday': 1, 3: 2, 5: 1, 7: 3})

注意:

  • 在字典 Dict 中查找不存在的键,程序会抛出 KeyError 的异常(理解为不存在的元素不在字典里,因此报错);
  • 但是由于 Counter 本质是用于统计的计数器,因此,在 Counter 中查找一个不存在的元素,程序不会返回异常,而是返回 0(可以理解为不存在的元素出现了 0 次);

Counter 额外支持字典中没有的三个方法:.elements().most_common(n) 以及 .subtract(iterable-or-mapping)

.elements()

该方法基于 Counter 类对象生成对应的原始序列(即按照 Counter 的计数,列出所有元素),返回一个迭代器,可以通过 list () 或者其它方式得到该迭代器的具体内容:

1
2
3
4
5
6
from collections import Counter

c = Counter({'a':1, 'b':2, 'c':3, 'd':0, 'e':-1}) # 将出现次数设置为 0 和负值,也不影响

print(c.elements())
print(list(c.elements()))

输出为:

1
2
<itertools.chain object at 0x7fc357a6f280>
['a', 'b', 'b', 'c', 'c', 'c']

注意:在 Counter 中是允许计数为 0 或者负值的,不过通过上面代码可以看出 .elements() 没有将 0 和负值对应的元素值打印出来。

.most_common()

.most_common(n) 是 Counter 最常用的方法,该方法取出出现次数 Top n 的元素,并返回一个由 (元素, 出现次数) 元祖所组成的列表(按出现次数降序排序):

1
2
3
4
5
6
7
from collections import Counter

c = Counter({'a':1, 'b':2, 'c':3})

print(c.most_common()) # 默认参数
print(c.most_common(2)) # n = 2
print(c.most_common(-1)) # n = -1

输出为:

1
2
3
[('c', 3), ('b', 2), ('a', 1)] # 不输入 n,默认返回所有元素
[('c', 3), ('b', 2)] # 输入 n 小于等于元素种类时,返回出现次数 Top n 的元素
[] # 输入 n = -1,则返回空

.subtract()

subtract([iterable_or_mapping]) 方法其实就是将两个 Counter 对象中的元素对应的计数相减:(当某个 Counter 中对应的元素不存在时,默认将其计数设置为 0,相减后可能产生负数)

1
2
3
4
5
6
7
from collections import Counter

c = Counter({'a':1, 'b':2, 'c':3})
d = Counter({'a':1, 'b':3, 'c':2, 'd':2})
c.subtract(d)

print(c)

输出为:

1
Counter({'c': 1, 'a': 0, 'b': -1, 'd': -2})

集合运算符

对 Counter 可以直接进行集合运算:

1
2
3
4
5
6
7
8
9
10
>>> c = Counter(a=3, b=1)
>>> d = Counter(a=1, b=2)
>>> c + d # add two counters together: c[x] + d[x]
Counter({'a': 4, 'b': 3})
>>> c - d # subtract (keeping only positive counts)
Counter({'a': 2})
>>> c & d # intersection: min(c[x], d[x])
Counter({'a': 1, 'b': 1})
>>> c | d # union: max(c[x], d[x])
Counter({'a': 3, 'b': 2})

map()

该函数会对给定序列中的每个元素调用一次指定函数,并以迭代器的形式返回结果序列(可以通过 list () 或者其它方式得到该迭代器的具体内容):

1
2
3
4
5
6
7
8
# 语法:function 为某个函数;iterable 为一个或多个序列
map(function, iterable, ...)

# 使用自定义函数进行
def square(x): return x**2

map(square, [1,2,3,4,5]) # 输出为一个迭代器
list(map(square, [1,2,3,4,5])) # 输出为映射结果

输出为:

1
2
<map at 0x7ff11493eee0>
[1, 4, 9, 16, 25]

注意:经常和 lambda 匿名函数配合使用:

1
2
3
# 使用 lambda 匿名函数
map(lambda x:x**2, [1,2,3,4,5]) # 输出为一个迭代器
list(map(lambda x:x**2, [1,2,3,4,5])) # 输出为映射结果

输出为:

1
2
<map at 0x7ff1149589d0>
[1, 4, 9, 16, 25]

enumerate()

该函数会将一个可遍历的数据对象(列表、元祖、字符串)的每个元素与该元素的索引组合成一个元祖,并返回以迭代器的形式返回这些元祖组成的列表(可以通过 list () 或者其它方式得到该迭代器的具体内容):

1
2
3
4
5
6
7
8
9
# 语法:sequence 为一个序列/迭代器,或其他支持迭代的对象;start 参数表示第一个索引的值
enumerate(sequence, start = 0)

# example
seasons = ['Spring', 'Summer', 'Fall', 'Winter']

enumerate(seasons) # 输出为一个迭代器
list(enumerate(seasons)) # 输出为序列结果
list(enumerate(seasons, start = 1)) # 将 1 作为第一个索引,输出新的序列结果

输出为:

1
2
3
<enumerate at 0x7ff114907e80>
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

注意:经常与 for 循环配合使用:

1
2
3
# 在 for 循环中使用
for i, element in enumerate(seasons):
print(i, element)

输出为:

1
2
3
4
0 Spring
1 Summer
2 Fall
3 Winter

zip()

该函数用于将多个可迭代对象中对应位置的元素打包成元组,然后以迭代器的形式返回由这些元组组成的列表(可以通过 list () 或者其它方式得到该迭代器的具体内容):

1
2
3
4
5
6
7
8
9
10
11
12
13
# 语法:iterable 表示一个或多个迭代器
zip(iterable, ...)

# example
a = [1,2,3]
b = [4,5,6]
c = [7,8,9]

zip(a,b) # 输出为一个迭代器
list(zip(a,b)) # 输出为序列结果

zip(a,b,c) # 输出为一个迭代器
list(zip(a,b,c)) # 输出为序列结果

输出为:

1
2
3
4
5
<zip at 0x7ff11496e380>
[(1, 4), (2, 5), (3, 6)]

<zip at 0x7ff114896100>
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]

注意:

  • 如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同;
  • 利用 * 号操作符,可以将元组解压为列表(相当于 zip() 的逆向操作);
1
2
3
4
5
6
7
8
9
10
11
short = [1,2,3]
long = [7,6,5,4,3,2,1]

# 1. 元素个数不一致时
print(list(zip(a,b))) # 输出的列表长度与最短的对象相同

# 2. 使用 * 解压
xy = [(1,4), (2,5), (3,6)]
x, y = zip(*xy)
print(x)
print(y)

输出为:

1
2
3
4
[(1, 4), (2, 5), (3, 6)]

(1, 2, 3)
(4, 5, 6)

list.sort()

In python, the .sort() method sorts a list using Timsort algorithm which has O(n) additional space where n is the number of the elements.

list 的 .sort() 方法用于对原列表进行排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 语法:
# cmp 指定了排序算法(冒泡、插入、...);
# key 可以是一个函数,指定了排序规则(如按照元祖的第一个元素排序、按照字符串的长度排序等);
# revers 指定了排序方式,reverse == True 表示降序排序, reverse == False 升序(默认);
list.sort(cmp=None, key=None, reverse=False)

# example
# 字符串按长度升序排序
mylist = ['work', 'job', 'practice', 'me', 'how']
mylist.sort(key = len)

# 按照元祖的第一个元素排序(此处使用匿名函数)
mylist = [(3,5), (2,6), (4,2), (1,9), (0,8)]
mylist.sort(key = lambda x:x[0])

# 按照绝对值排序
mylist = [-10, 0, -6, 12, 9]
mylist.sort(key = abs)

输出为:

1
2
3
['me', 'job', 'how', 'work', 'practice']
[(0, 8), (1, 9), (2, 6), (3, 5), (4, 2)]
[0, -6, 9, -10, 12]

sorted()

sorted() 作为 Python 内置函数之一,其功能是对序列(列表、元组、字典、集合、还包括字符串)进行排序(使用 sorted() 函数并不会在原序列的基础上进行修改,而是会重新生成一个排好序的列表):

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
34
35
36
37
38
# 语法:
# iterable 表示指定的序列;
# key 可以是一个函数,该参数自定义排序规则(如按照元祖的第一个元素排序、按照字符串的长度排序等);
# reverse 参数指定以升序(False,默认)还是降序(True)进行排序;
# sorted() 函数会返回一个排好序的列表;
list = sorted(iterable, key=None, reverse=False)

# example
#对列表进行排序
a = [5,3,4,2,1]
print(sorted(a))

# 对元组进行排序
a = (5,4,3,1,2)
print(sorted(a))

# 对字典进行排序(默认按照 key 进行排序)
a = {4:1, 5:2, 3:3, 2:6, 1:8}
print(sorted(a.items()))

# 对集合进行排序
a = {1,5,3,2,4}
print(sorted(a))

# 对字符串本身进行排序
a = "51423"
print(sorted(a))

# 对字符串列表进行排序
chars=['http://c.biancheng.net',\
'http://c.biancheng.net/python/',\
'http://c.biancheng.net/shell/',\
'http://c.biancheng.net/java/',\
'http://c.biancheng.net/golang/']
# 默认(按照字典序)排序
print(sorted(chars))
#自定义按照字符串长度排序(此处使用匿名函数)
print(sorted(chars,key=lambda x:len(x)))

输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[(1, 8), (2, 6), (3, 3), (4, 1), (5, 2)]
[1, 2, 3, 4, 5]
['1', '2', '3', '4', '5']
['http://c.biancheng.net',
'http://c.biancheng.net/golang/',
'http://c.biancheng.net/java/',
'http://c.biancheng.net/python/',
'http://c.biancheng.net/shell/']
['http://c.biancheng.net',
'http://c.biancheng.net/java/',
'http://c.biancheng.net/shell/',
'http://c.biancheng.net/python/',
'http://c.biancheng.net/golang/']

5. bisect 包

bisect 为 python 内置模块,用于有序序列中的插入与查找:

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
import bisect

## 首先确保要操作的列表已经经过排序
mylist = [3,6,5,9,7]
mylist.sort()
print(mylist) # 输出为:[3, 5, 6, 7, 9]

## 插入操作:
# insort(mylist, num):直接将目标数字 num 插入到有序序列中正确的位置
bisect.insort(mylist, 8)
print(mylist) # 输出为:[3, 5, 6, 7, 8, 9]

# insort_left(mylist, num):将目标数字 num 插入到有序序列中正确的位置,若原序列中已存在与目标数字相等的元素,则将目标数字插入到重复元素的左边
bisect.insort_left(mylist, 8)
print(mylist) # 输出为:[3, 5, 6, 7, 8, 8, 9]

# insort_right(mylist, num):将目标数字 num 插入到有序序列中正确的位置,若原序列中已存在与目标数字相等的元素,则将目标数字插入到重复元素的右边
bisect.insort_right(mylist, 8)
print(mylist) # 输出为:[3, 5, 6, 7, 8, 8, 8, 9]

## 查找操作:
# bisect(mylist, num):如果要将 num 插入到 mylist 中,则 num 应该插入的位置索引;当 mylist 中已经含有重复的 num 时,默认返回插入到重复元素右边的索引
bisect.bisect(mylist, 8) # 输出为:6

# bisect_left(mylist, num):如果要将 num 插入到 mylist 中,则 num 应该插入的位置索引;当 mylist 中已经含有重复的 num 时,则返回插入到重复元素左边的索引
bisect.bisect_left(mylist, 8) # 输出为:4

# bisect_right(mylist, num):如果要将 num 插入到 mylist 中,则 num 应该插入的位置索引;当 mylist 中已经含有重复的 num 时,则返回插入到重复元素右边的索引
bisect.bisect_right(mylist, 8) # 输出为:6

注意: bisect() 函数常用于直接替代二分查找的代码。

6. nonlocal

当一个函数中嵌套有另一个函数时,有时内部函数需要对外部函数的一些变量进行操作:

  • 如果需要操作的变量是可变数据类型的值(例如列表或字典),则由于浅复制机制,内部函数可以直接使用变量名对该变量进行操作(内部函数中的同名变量是外部函数中原始变量的引用,对其操作会直接影响外部函数中该变量的值);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def outter():
    variable = [1,2,3,4,5]

    def inner():
    variable[2] = 'NewYork'

    inner()
    print(variable)

    # 输出为:
    # [1, 2, 'NewYork', 4, 5]
  • 如果需要操作的变量是不可变数据类型的值(例如字符串、整型或元组),则由于深复制机制,内部函数不能直接使用变量名对该变量进行操作(内部函数中的同名变量是外部函数中原始变量的复制,两者并不共享内存,可以视为两个不同的变量,因此对该变量的直接操作并不会影响到原始变量的值,甚至可能会报错:variable referenced before assignment);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def outter():
    variable = 42

    def inner():
    variable += 100

    inner()
    print(variable)

    # 将会报错:
    # UnboundLocalError: local variable 'variable' referenced before assignment

为了能够在内部函数直接操作不可变数据类型的值(例如字符串、整型或元组),可以使用 nonlocal 语句:该语句只能在内部函数中使用,经过 nonlocal 声明后的外部变量可以直接由内部函数进行操作

1
2
3
4
5
6
7
8
9
10
11
12
def outter():
variable = 42

def inner():
nonlocal variable # 非局部变量声明语句
variable += 100

inner()
print(variable)

# 输出为:
# 142

7. 循环 else 语句

考虑如下结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
matrix = [[1,-2,5,-7,-8,9],
[5,6,27,98,23,7],
[65,2,-9,-78,-3],
[22,6,77,3,22,6]]
res = 0

for row in matrix:
mySum = 0
for num in row:
if num < 0:
break
mySum += num
else:
res += mySum

print(res)

# 输出为:
# 302

此处 else 语句并没有与 if 语句对齐,而是与一个 for 循环语句对齐,这表示:如果该 for 循环正常结束而非被 break,那么将执行循环下面的 else 语句,否则不执行 else 语句。因此该代码表示:求矩阵中所有不含负数的行的元素之和。

循环 else 相当于其他语言中用来检测是否触发 break 的一个布尔 flag(无需额外声明)。

CATALOG
  1. 1. 关于引用 (Reference)
    1. 1.1. 引用 (Reference) 与复制 (Copy)
      1. 1.1.1. 引用:浅复制 (Shallow Copy)
      2. 1.1.2. 深复制 (Deep Copy)
    2. 1.2. Python 复制模块
    3. 实例一:函数传递引用
    4. 实例二:二维数组赋值问题
  2. 2. 字符串中引用变量
    1. 连字符 +
    2. 占位字符 %
    3. 方法 .format()
    4. 标记字符串
  3. 3. 帮助函数
    1. dir()
    2. help()
    3. 属性文档
  4. 4. 常用函数
    1. Counter()
      1. .elements()
      2. .most_common()
      3. .subtract()
      4. 集合运算符
    2. map()
    3. enumerate()
    4. zip()
    5. list.sort()
    6. sorted()
  5. 5. bisect 包
  6. 6. nonlocal
  7. 7. 循环 else 语句