0%

python400集2

python基础入门

序列

python 中常用的序列结构有:

字符串、列表、元组、字典、集合

列表简介

列表:用于存储任意数目、任意类型的数据集合。

列表是内置可变序列,是包含多个元素的有序连续的内存空间。列表定义的标准语法格式: a = [10,20,30,40]

其中,10,20,30,40 这些称为:列表 a 的元素。 列表中的元素可以各不相同,可以是任意类型。比如:

a = [10,20,’abc’,True] 列表对象的常用方法汇总如下,方便大家学习和查阅。

方法 要点 描述
list.append(x) 增加元素 将元素 x 增加到列表 list 尾部
list.extend(aList) 增加元素 将列表 alist 所有元素加到列表 list 尾部
list.insert(index,x) 增加元素 在列表 list 指定位置 index 处插入元素 x
list.remove(x) 删除元素 在列表 list 中删除首次出现的指定元素 x
list.pop([index]) 删除元素 删除并返回列表 list 指定为止 index 处的元素,默认是 最后一个元素
list.clear() 删除所有元素 删除列表所有元素,并不是删除列表对象
list.index(x) 访问元素 返回第一个 x 的索引位置,若不存在 x 元素抛出异常
list.count(x) 计数 返回指定元素 x 在列表 list 中出现的次数
len(list) 列表长度 返回列表中包含元素的个数
list.reverse() 翻转列表 所有元素原地翻转
list.sort() 排序 所有元素原地排序
list.copy() 浅拷贝 返回列表对象的浅拷贝

Python 的列表大小可变,根据需要随时增加或缩小。

字符串和列表都是序列类型,一个字符串是一个字符序列,一个列表是任何元素的序列。我 们前面学习的很多字符串的方法,在列表中也有类似的用法,几乎一模一样。

列表的创建

基本语法[]创建

list()创建: 使用 list()可以将任何可迭代的数据转化成列表。

1
2
3
>>> a = list("gaoqi,sxt")
>>> a
['g', 'a', 'o', 'q', 'i', ',', 's', 'x', 't']

range()创建整数列表

range()可以帮助我们非常方便的创建整数列表,这在开发中及其有用。语法格式为: range([start,] end [,step])

start 参数:可选,表示起始数字。默认是 0
end 参数:必选,表示结尾数字。
step 参数:可选,表示步长,默认为 1
python3 中 range()返回的是一个 range 对象,而不是列表。我们需要通过 list()方法将其 转换成列表对象。

1
2
>>> list(range(3,15,2))
[3, 5, 7, 9, 11, 13]

推导式生成列表

使用列表推导式可以非常方便的创建列表,在开发中经常使用。

1
2
3
 a = [x*2 for x in range(100) if x%9==0] #通过 if 过滤元素 
>>> a
[0, 18, 36, 54, 72, 90, 108, 126, 144, 162, 180, 198]

列表元素的增加和删除

当列表增加和删除元素时,列表会自动进行内存管理,大大减少了程序员的负担。但这 个特点涉及列表元素的大量移动,效率较低。除非必要,我们一般只在列表的尾部添加元素 或删除元素,这会大大提高列表的操作效率。

append()方法

原地修改列表对象,是真正的列表尾部添加新的元素,速度最快,推荐使用。 a.append(80)

+运算符操作

并不是真正的尾部添加元素,而是创建新的列表对象;将原列表的元素和新列表的元素依次 复制到新的列表对象中。这样,会涉及大量的复制操作,对于操作大量元素不建议使用。a = a+[50]

extend()方法

将目标列表的所有元素添加到本列表的尾部,属于原地操作,不创建新的列表对象。

1
2
3
4
5
6
>>> a = [20,40]
>>> id(a)
46016072
>>> a.extend([50,60])
>>> id(a)
46016072

insert()插入元素

使用 insert()方法可以将指定的元素插入到列表对象的任意制定位置。这样会让插入位置后 面所有的元素进行移动,会影响处理速度。涉及大量元素时,尽量避免使用。类似发生这种 移动的函数还有:remove()、pop()、del(),它们在删除非尾部元素时也会发生操作位置后 面元素的移动。

a.insert(2,100)#在index2处加入100

乘法扩展

使用乘法扩展列表,生成一个新列表,新列表元素时原列表元素的多次重复。

1
2
3
4
5
b = a*3 
>>> a
['sxt', 100]
>>> b
['sxt', 100, 'sxt', 100, 'sxt', 100]

列表元素的删除 del 删除

删除列表指定位置的元素。del a[1]

pop()方法

pop()删除并返回指定位置元素,如果未指定位置则默认操作列表最后一个元素。

a.pop(1)#返回index 1的元素

remove()方法

删除首次出现的指定元素,若不存在该元素抛出异常。a.remove(20)

列表元素访问和计数

我们可以通过索引直接访问元素。索引的区间在[0, 列表长度-1]这个范围。超过这个范围则 会抛出异常。

index()获得指定元素在列表中首次出现的索引

index()可以获取指定元素首次出现的索引位置。语法是:index(value,[start,[end]])。其中, start 和 end 指定了搜索的范围。

1
>>> a.index(30,5,7) #从索引位置 5 到 7 这个区间,第一次出现 30 元素的位置

count()获得指定元素在列表中出现的次数

count()可以返回指定元素在列表中出现的次数。 a.count(20)

len()返回列表长度

len()返回列表长度,即列表中包含元素的个数。 len(a)

成员资格判断

判断列表中是否存在指定的元素,我们可以使用 count()方法,返回 0 则表示不存在,返回 大于 0 则表示存在。但是,一般我们会使用更加简洁的 in 关键字来判断,直接返回 True 或 False。100 not in a

  1. 切片操作

    我们在前面学习字符串时,学习过字符串的切片操作,对于列表的切片操作和字符串类似。 切片是 Python 序列及其重要的操作,适用于列表、元组、字符串等等。切片的格式如下:

    切片 slice 操作可以让我们快速提取子列表或修改。标准格式为: [起始偏移量 start:终止偏移量 end[:步长 step]]

    注:当步长省略时顺便可以省略第二个冒号

    操作和说明 示例 结果
    [:] 提取整个列表 [10,20,30][:] [10,20,30]
    [start:]从 start 索引开始到 结尾 [10,20,30][1:] [20,30]
    [:end]从头开始知道 end-1 [10,20,30][:2] [10,20]
    [start:end]从 start 到 end-1 [10,20,30,40][1:3] [20,30]
    [start:end:step] 从 start 提 取到 end-1,步长是 step [10,20,30,40,50,60,70][1:6: 2] [20, 40, 60]
示例 说明 结果
[10,20,30,40,50,60,70][-3:] 倒数三个 [50,60,70]
10,20,30,40,50,60,70][-5:-3] 倒数第五个到倒数 第三个(包头不包尾) [30,40]
[10,20,30,40,50,60,70][::-1] 步长为负,从右到左 反向提取 [70, 60, 50, 40, 30, 20, 10]

切片操作时,起始偏移量和终止偏移量不在[0,字符串长度-1]这个范围,也不会报错。起始 偏移量小于 0 则会当做 0,终止偏移量大于“长度-1”会被当成”长度-1”

列表的遍历

for obj in listObj: print(obj)

复制列表所有的元素到新列表对象

我们可以通过如下简单方式,实现列表元素内容的复制: list1 = [30,40,50]

list2 = [] + list1

*列表排序 *

修改原列表,不建新列表的排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> a = [20,10,30,40]
>>> id(a)
46017416
>>> a.sort() #默认是升序排列
>>> a
[10, 20, 30, 40]
>>> a = [10,20,30,40]
>>> a.sort(reverse=True) #降序
>>> a
[40, 30, 20, 10]
>>> import random
>>> random.shuffle(a) #打乱排序
>>> a
[20, 40, 30, 10]

建新列表排序

我们也可以通过内置函数 sorted()进行排序,这个方法返回新列表,不对原列表做修改。

a = sorted(a)

c = sorted(a,reverse=True) #降序

reversed()返回迭代器

内置函数 reversed()也支持进行逆序排列,与列表对象 reverse()方法不同的是,内置函数 reversed()不对原列表做任何修改,只是返回一个逆序排列的迭代器对象。

1
2
3
4
5
6
7
8
9
10
11
12
>>> a = [20,10,30,40]
>>> c = reversed(a)
>>> c
<list_reverseiterator object at 0x0000000002BCCEB8>
>>> list(c)
[40, 30, 10, 20]
>>> list(c)
[]
···
我们打印输出 c 发现提示是:list_reverseiterator。也就是一个迭代对象。同时,我们使用 list(c)进行输出,发现只能使用一次。第一次输出了元素,第二次为空。那是因为迭代对象 在第一次时已经遍历结束了,第二次不能再使用。

···

列表相关的其他内置函数汇总 max 和 min

用于返回列表中最大和最小值。max(a)

sum

对数值型列表的所有元素进行求和操作,对非数值型列表运算则会报错。

元组 tuple

列表属于可变序列,可以任意修改列表中的元素。元组属于不可变序列,不能修改元组中的 元素。因此,元组没有增加元素、修改元素、删除元素相关的方法。

因此,我们只需要学习元组的创建和删除,元组中元素的访问和计数即可。元组支持如 下操作:

\1. 索引访问
\2. 切片操作
\3. 连接操作
\4. 成员关系操作
\5. 比较运算操作
\6. 计数:元组长度 len()、最大值 max()、最小值 min()、求和 sum()等。

元组的创建

\1. 通过()创建元组。小括号可以省略。

如果元组只有一个元素,则必须后面加逗号。这是因为解释器会把(1)解释为整数 1,(1,) 解释为元组。

a = (10,20,30) 或者 a = 10,20,30

\2. 通过 tuple()创建元组 tuple(可迭代的对象)

tuple()可以接收列表、字符串、其他序列类型、迭代器等生成元组。 list()可以接收元组、字符串、其他序列类型、迭代器等生成列表。

元组的元素访问和计数

\1. 元组的元素不能修改

\2. 元组的元素访问和列表一样,只不过返回的仍然是元组对象。

\3. 列表关于排序的方法 list.sorted()是修改原列表对象,元组没有该方法。如果要对元组排 序,只能使用内置函数 sorted(tupleObj),并生成新的列表对象。 sorted(a)

zip

zip(列表 1,列表 2,…)将多个列表对应位置的元素组合成为元组,并返回这个 zip 对象。

1
2
3
4
5
6
>>> a = [10,20,30]
>>> b = [40,50,60]
>>> c = [70,80,90]
>>> d = zip(a,b,c)
>>> list(d)
[(10, 40, 70), (20, 50, 80), (30, 60, 90)]

生成器推导式创建元组

从形式上看,生成器推导式与列表推导式类似,只是生成器推导式使用小括号。列表推 导式直接生成列表对象,生成器推导式生成的不是列表也不是元组,而是一个生成器对象。

我们可以通过生成器对象,转化成列表或者元组。也可以使用生成器对象的next() 方法进行遍历,或者直接作为迭代器对象来使用。不管什么方式使用,元素访问结束后,如 果需要重新访问其中的元素,必须重新创建该生成器对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> s = (x*2 for x in range(5))
>>> s
<generator object <genexpr> at 0x0000000002BDEB48>
>>> tuple(s)
(0, 2, 4, 6, 8)
>>> list(s) #只能访问一次元素。第二次就为空了。需要再生成一次
[]
>>> s
<generator object <genexpr> at 0x0000000002BDEB48>
>>> tuple(s)
()
>>> s = (x*2 for x in range(5))
>>> s.__next__()
0
>>> s.__next__()
2
>>> s.__next__()
4

元组总结

\1. 元组的核心特点是:不可变序列。
\2. 元组的访问和处理速度比列表快。
\3. 与整数和字符串一样,元组可以作为字典的键,列表则永远不能作为字典的键使用。

字典介绍

字典是“键值对”的无序可变序列,字典中的每个元素都是一个“键值对”,包含:“键 对象”和“值对象”。可以通过“键对象”实现快速获取、删除、更新对应的“值对象”。

列表中我们通过“下标数字”找到对应的对象。字典中通过“键对象”找到对应的“值 对象”。“键”是任意的不可变数据,比如:整数、浮点数、字符串、元组。但是:列表、 字典、集合这些可变对象,不能作为“键”。并且“键”不可重复。

“值”可以是任意的数据,并且可重复。

字典的创建

\1. 我们可以通过{}、dict()来创建字典对象。

\2. 通过 zip()创建字典对象

1
2
3
>>> k = ['name','age','job']
>>> v = ['gaoqi',18,'techer']
>>> d = dict(zip(k,v))

\3. 通过 fromkeys 创建值为空的字典

1
2
3
>>> a = dict.fromkeys(['name','age','job']) 
>>> a
{'name': None, 'age': None, 'job': None}

字典元素的访问

\1. 通过 [键] 获得“值”。若键不存在,则抛出异常。 a[‘name’]

\2. 通过 get()方法获得“值”。推荐使用。优点是:指定键不存在,返回 None;也可以设 定指定键不存在时默认返回的对象。推荐使用 get()获取“值对象”。a.get(‘name’)

\3. 列出所有的键值对

1
2
>>> a.items()
dict_items([('name', 'gaoqi'), ('age', 18), ('job', 'programmer')])

\4. 列出所有的键,列出所有的值

1
2
3
4
>>> a.keys()
dict_keys(['name', 'age', 'job'])
>>> a.values()
dict_values(['gaoqi', 18, 'programmer'])

\5. len() 键值对的个数
\6. 检测一个“键”是否在字典中 用in

字典元素添加、修改、删除

\1. 给字典新增“键值对”。如果“键”已经存在,则覆盖旧的键值对;如果“键”不存在, 则新增“键值对”。a[‘age’]=16

\2. 使用 update()将新字典中所有键值对全部添加到旧字典对象上。如果 key 有重复,则直 接覆盖。

1
2
3
>>> a = {'name':'gaoqi','age':18,'job':'programmer'} 
>>> b = {'name':'gaoxixi','money':1000,'sex':'男的'}
>>> a.update(b)

\3. 字典中元素的删除,可以使用 del()方法;或者 clear()删除所有键值对;pop()删除指定 键值对,并返回对应的“值对象”;

1
2
3
4
>>> del(a['name'])
>>> a
{'age': 18, 'job': 'programmer'}
>>> b = a.pop('age')

\4. popitem() :随机删除和返回该键值对。字典是“无序可变序列”,因此没有第一个元 素、最后一个元素的概念;popitem 弹出随机的项,因为字典并没有”最后的元素”或者其 他有关顺序的概念。若想一个接一个地移除并处理项,这个方法就非常有效(因为不用首先获取键的列表)。

a.popitem()

序列解包

序列解包可以用于元组、列表、字典。序列解包可以让我们方便的对多个变量赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> x,y,z=(20,30,10)
>>> x
20
>>> y
30
>>> z
10
>>> (a,b,c)=(9,8,10)
>>> a
9
>>> [a,b,c]=[10,20,30]
>>> a
10
>>> b
20

序列解包用于字典时,默认是对“键”进行操作; 如果需要对键值对操作,则需要使用 items();如果需要对“值”进行操作,则需要使用 values();

1
2
3
4
5
6
7
8
9
10
>>> s = {'name':'gaoqi','age':18,'job':'teacher'}
>>> name,age,job=s #默认对键进行操作
>>> name
'name'
>>> name,age,job=s.items()
>>> name
('name', 'gaoqi')
>>> name,age,job=s.values()
>>> name
'gaoqi'

字典核心底层原理(重要)

字典对象的核心是散列表。散列表是一个稀疏数组(总是有空白元素的数组),数组的 每个单元叫做 bucket。每个 bucket 有两部分:一个是键对象的引用,一个是值对象的引 用。
由于,所有 bucket 结构和大小一致,我们可以通过偏移量来读取指定 bucket。

扩容

python 会根据散列表的拥挤程度扩容。“扩容”指的是:创造更大的数组,将原有内容 拷贝到新数组中。

接近 2/3 时,数组就会扩容。

用法总结:
\1. 键必须可散列

(1) 数字、字符串、元组,都是可散列的。

(2) 自定义对象需要支持下面三点:

  1. 1 支持 hash()函数
  2. 2 支持通过eq()方法检测相等性。
  3. 3 若 a==b 为真,则 hash(a)==hash(b)也为真。

\2. 字典在内存中开销巨大,典型的空间换时间。
\3. 键查询速度很快
\4. 往字典里面添加新建可能导致扩容,导致散列表中键的次序变化。因此,不要在遍历字 典的同时进行字典的修改。

集合

集合创建和删除

\1. 使用{}创建集合对象,并使用 add()方法添加元素

\2. 使用 set(),将列表、元组等可迭代对象转成集合。如果原来数据存在重复数据,则只保 留一个。

\3. remove()删除指定元素;clear()清空整个集合

集合相关操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> a = {1,3,'sxt'}
>>> b = {'he','it','sxt'}
>>> a|b #并集
{1, 3, 'sxt', 'he', 'it'}
>>> a&b #交集
{'sxt'}
>>> a-b #差集
{1, 3}
>>> a.union(b) #并集
{1, 3, 'sxt', 'he', 'it'}
>>> a.intersection(b) #交集
{'sxt'}
>>> a.difference(b) #差集
{1, 3}

控制语句

三元条件运算符

Python 提供了三元运算符,用来在某些简单双分支赋值情况。三元条件运算符语法格式如 下:

条件为真时的值 if (条件表达式) else 条件为假时的值

1
print( num if int(num)<10 else "数字太大")

可迭代对象

Python 包含以下几种可迭代对象:

  1. ​ 序列。包含:字符串、列表、元组
    1. 字典
    2. . 迭代器对象(iterator)
      \4. 生成器函数(generator)
      \5. 文件对象
1
2
3
4
5
6

for x in print(x)
for x in print(x)
d.keys():#遍历字典所有的 key
d.values():#遍历字典所有的 value
for x in d.items():#遍历字典所有的"键值对"
1
【操作】利用嵌套循环打印九九乘法表
1
2
3
4
for m in range(1,10):
for n in range(1,m+1):
print("{0}*{1}={2}".format(m,n,(m*n)),end="\t")
print()

使用 zip()并行迭代

我们可以通过 zip()函数对多个序列进行并行迭代,zip()函数在最短序列“用完”时就会停止。

1
2
3
4
5
names = ("高淇","高老二","高老三","高老四") 
ages = (18,16,20,25)
jobs = ("老师","程序员","公务员")
for name,age,job in zip(names,ages,jobs):
print("{0}--{1}--{2}".format(name,age,job))

推导式创建序列

推导式是从一个或者多个迭代器快速创建序列的一种方法。它可以将循环和条件判断结合, 从而避免冗长的代码。推导式是典型的 Python 风格,会使用它代表你已经超过 Python 初 学者的水平。

列表推导式

列表推导式生成列表对象,语法如下:
[表达式 for item in 可迭代对象 ]

或者:{表达式 for item in 可迭代对象 if 条件判断}

1
2
3
4
5
6
7
8
9
10
11
12
13

>>> [x for x in range(1,5)]
[1, 2, 3, 4]
>>> [x*2 for x in range(1,5)]
[2, 4, 6, 8]
>>> [x*2 for x in range(1,20) if x%5==0 ]
[10, 20, 30]
>>> [a for a in "abcdefg"]
['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> cells = [(row,col) for row in range(1,10) for col in range(1,10)]
#可以使用两个循环
>>> for cell in cells:
print(cell)

字典推导式

row in range(1,10) for col in range(1,10)]

#可以使用两

字典的推导式生成字典对象,格式如下:
{key_expression : value_expression for 表达式 in 可迭代对象}

类似于列表推导式,字典推导也可以增加 if 条件判断、多个 for 循环。

统计文本中字符出现的次数:

1
2
3
4
\>>> my_text = ' i love you, i love sxt, i love gaoqi'
\>>> char_count = {c:my_text.count(c) for c in my_text}
\>>> char_count
{' ': 9, 'i': 4, 'l': 3, 'o': 5, 'v': 3, 'e': 3, 'y': 1, 'u': 1, ',': 2, 's': 1, 'x': 1, 't': 1, 'g': 1, 'a': 1, 'q': 1}

集合推导式

集合推导式生成集合,和列表推导式的语法格式类似:
{表达式 for item in 可迭代对象 }

或者:{表达式 for item in 可迭代对象 if 条件判断}

>>> {x for x in range(1,100) if x%9==0} {99, 36, 72, 9, 45, 81, 18, 54, 90, 27, 63}

生成器推导式(生成元组)

很多同学可能会问:“都有推导式,元组有没有?”,能不能用小括号呢?

(x for x in range(1,100) if x%9==0)
<generator object at 0x0000000002BD3048>

我们发现提示的是“一个生成器对象”。显然,元组是没有推导式的。

一个生成器只能运行一次。第一次迭代可以得到数据,第二次迭代发现数据已经没有了。

1
2
3
4
5
6
7
8
9
>>> gnt = (x for x in range(1,100) if x%9==0)
>>> for x in gnt:

print(x,end=' ')
9 18 27 36 45 54 63 72 81 90 99

>>> for x in gnt:

print(x,end=' ')

函数

Python 中函数分为如下几类: 1. 内置函数

我们前面使用的 str()、list()、len()等这些都是内置函数,我们可以拿来直接使用。

  1. 标准库函数

我们可以通过 import 语句导入库,然后使用其中定义的函数

  1. 第三方库函数

Python 社区也提供了很多高质量的库。下载安装这些库后,也是通过 import 语句导 入,然后可以使用这些第三方库的函数

  1. 用户自定义函数

用户自己定义的函数,显然也是开发中适应用户自身需求定义的函数。今天我们学习的 就是如何自定义函数。

文档字符串(函数的注释)

程序的可读性最重要,一般建议在函数体开始的部分附上函数定义说明,这就是“文档字符 串”,也有人成为“函数的注释”。我们通过三个单引号或者三个双引号来实现,中间可以

北京尚学堂·百战程序员 实战 系统 好教育

加入多行文字进行说明。

函数也是对象,内存底层分析

Python 中,“一切都是对象”。实际上,执行 def 定义函数后,系统就创建了相应的函数 对象。

变量的作用域(全局变量和局部变量)

变量起作用的范围称为变量的作用域,不同作用域内同名变量之间互不影响。变量分为:全局变量、局部变量。

全局变量:

\1. 在函数和类定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块 结束。

  1. 全局变量降低了函数的通用性和可读性。应尽量避免全局变量的使用。
  2. 全局变量一般做常量使用。
  3. 函数内要改变全局变量的值,使用 global 声明一下

局部变量:
\1. 在函数体中(包含形式参数)声明的变量。
\2. 局部变量的引用比全局变量快,优先考虑使用。
\3. 如果局部变量和全局变量同名,则在函数内隐藏全局变量,只使用同名的局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a = 100 #全局变量
def f1():
global a #如果要在函数内改变全局变量的值,增加 global 关键字声明
print(a) #打印全局变量 a 的值,
a = 300
print(a)#局部变量a
print(locals())
print(globals())
f1()
print(a)

```结果
100
300
{}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/mengxiaowang/Documents/a.py', 'a': 300, 'f1': <function f1 at 0x7ff5602dc430>}
300
```

另一种输出,因为local变global后没有了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = 100 #全局变量
def f1():
a = 300
print(locals())#{'a': 300}
print(globals())#{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': <function a at 0x7f9388270830>}
f1()
print(a)
#输出
```
{'a': 300}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/mengxiaowang/Documents/a.py', 'a': 100, 'f1': <function f1 at 0x7fc050745430>}
100

```

局部变量和全局变量效率测试

局部变量的查询和访问速度比全局变量快,优先考虑使用,尤其是在循环的时候。 在特别强调效率的地方或者循环次数较多的地方,可以通过将全局变量转为局部变量提高运 行速度。

参数的传递

函数的参数传递本质上就是:从实参到形参的赋值操作。 Python 中“一切皆对象”, 所有的赋值操作都是“引用的赋值”。所以,Python 中参数的传递都是“引用传递”,不 是“值传递”。具体操作时分为两类:

\1. 对“可变对象”进行“写操作”,直接作用于原对象本身。

\2. 对“不可变对象”进行“写操作”,会产生一个新的“对象空间”,并用新的值填 充这块空间。(起到其他语言的“值传递”效果,但不是“值传递”)

可变对象有: 字典、列表、集合、自定义的对象等

不可变对象有: 数字、字符串、元组、function 等

浅拷贝和深拷贝

为了更深入的了解参数传递的底层原理,我们需要讲解一下“浅拷贝和深拷贝”。我们可以 使用内置函数: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
def testCopy(): '''测试浅拷贝'''
a = [10, 20, [5, 6]]
b = copy.copy(a)
print("a", a)
print("b", b)
b.append(30)
b[2].append(7)
print("浅拷贝......")
print("a", a)
print("b", b)
def testDeepCopy(): '''测试深拷贝'''
a = [10, 20, [5, 6]]
b = copy.deepcopy(a)
print("a", a)
print("b", b)
b.append(30)
b[2].append(7)
print("深拷贝......")
print("a", a)
print("b", b)
testCopy()
print("*************")
testDeepCopy()
1
2
3
4
5
6
7
8
9
10
11
12
13
结果

a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
浅拷贝......
a [10, 20, [5, 6, 7]]
b [10, 20, [5, 6, 7], 30]
*************
a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
深拷贝......
a [10, 20, [5, 6]]
b [10, 20, [5, 6, 7], 30]

传递不可变对象包含的子对象是可变的情况

#传递不可变对象时。不可变对象里面包含的子对象是可变的。则 方法内修改了这个可变对象,源对象也发生了变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a = (10,20,[5,6]) 
print("a:",id(a))
def test01(m):
print("m:",id(m))
m[2][0] = 888
print(m)
print("m:",id(m))
test01(a)
print(a)
运行结果:
a: 41611632
m: 41611632
(10, 20, [888, 6])
m: 41611632
(10, 20, [888, 6])

id相对于c中地址符&

*参数的几种类型 *

位置参数

函数调用时,实参默认按位置顺序传递,需要个数和形参匹配。按位置传递的参数,称为: “位置参数”。

默认值参数

我们可以为某些参数设置默认值,这样这些参数在传递时就是可选的。称为“默认值参数”。 默认值参数放到位置参数后面。def f1(a,b,c=10,d=20): #默认值参数必须位于普通位置参数后面

命名参数

我们也可以按照形参的名称传递参数,称为“命名参数”,也称“关键字参数”。f1(8,9,19) #位置参数 f1(c=10,a=20,b=30) #命名参数

强制命名参数

在带星号的“可变参数”后面增加新的参数,必须在调用的时候“强制命名参数”。

1
2
3
4
5
def f1(*a,b,c): 
print(a,b,c)
#f1(2,3,4) #会报错。由于 a 是可变参数,将 2,3,4 全部收集。造成 b 和 c 没有赋值。
f1(2,b=3,c=4)
执行结果: (2,) 3 4

lambda 表达式和匿名函数

lambda 表达式可以用来声明匿名函数。lambda 函数是一种简单的、在同一行中定义函数 的方法。lambda 函数实际生成了一个函数对象。
lambda 表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数 的返回值。

lambda 表达式的基本语法如下:
lambda arg1,arg2,arg3… : <表达式>

arg1/arg2/arg3 为函数的参数。<表达式>相当于函数体。运算结果是:表达式的运算结果。

g = [lambda a:a2,lambda b:b3,lambda c:c*4]

eval()函数

功能:将字符串 str 当成有效的表达式来求值并返回计算结果。

语法: eval(source[, globals[, locals]]) -> value

参数:
source:一个 Python 表达式或函数 compile()返回的代码对象 globals:可选。必须是 dictionary locals:可选。任意映射对象

1
2
3
dict1 = dict(a=100,b=200)
d = eval("a+b",dict1)
print(d)#eval 函数会将字符串当做语句来执行,因此会被注入安全隐患。比如:字符串中含有删除文 件的语句。那就麻烦大了。因此,使用时候,要慎重!!!

递归函数

递归函数指的是:自己调用自己的函数,在函数体内部直接或间接的自己调用自己。递归类 似于大家中学数学学习过的“数学归纳法”。 每个递归函数必须包含两个部分:
\1. 终止条件

表示递归什么时候结束。一般用于返回值,不再调用自己。 2. 递归步骤

把第 n 步的值和第 n-1 步相关联。

递归函数由于会创建大量的函数对象、过量的消耗内存和运算能力。在处理大量数据时,谨 慎使用。

嵌套函数(内部函数)

嵌套函数: 在函数内部定义的函数!

一般在什么情况下使用嵌套函数?

  1. 封装 - 数据隐藏外部无法访问“嵌套函数”。
  2. 贯彻 DRY(Don’t Repeat Yourself) 原则嵌套函数,可以让我们在函数内部避免重复代码。
  3. 闭包

nonlocal 关键字

nonlocal 用来声明外层的局部变量。 global 用来声明全局变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a=100
def outer():
b=10
def inner():
nonlocal b#声明外部函数的局部变量
print("inner b:",b)
b=20
global a#声明全局变量
a=1000
inner()
print("outer b:",b)
outer()
print("a:",a)
#结果
inner b: 10
outer b: 20
a: 1000

LEGB 规则

Python 在查找“名称”时,是按照 LEGB 规则查找的: Local–>Enclosed–>Global–>Built in

Local 指的就是函数或者类的方法内部
Enclosed 指的是嵌套函数(一个函数包裹另一个函数,闭包) Global 指的是模块中的全局变量
Built in 指的是 Python 为自己保留的特殊名称。

如果某个 name 映射在局部(local)命名空间中没有找到,接下来就会在闭包作用域 (enclosed)进行搜索,如果闭包作用域也没有找到,Python 就会到全局(global)命名空 间中进行查找,最后会在内建(built-in)命名空间搜索 (如果一个名称在所有命名空间 中都没有找到,就会产生一个 NameError)。