一、dict 和 set
1、字典——dict
思想:空间换时间
dict 中的 key 必须是不可变对象
(1)定义、初始化
在其他语言中也称为 map,使用键 - 值(key - value)存储,根据key通过hash算法计算value值,可进行快速查找。
## key-value
d = {'Michael':95, 'Bob':75, 'Tracy':85}
print(d['Michael'])
## 其他初始化方式
d['Adam'] = 67
print(d['Adam'])
输出结果:
95
67
key-value 存储方式:必须根据 key算出 value 的存放位置。
(2)查找中,如果 key 不存在, dict 就会报错
==》检测 key 是否在 dict 中:
- 方法一:in
- 方法二:get()方法
## 方法1:
print('Tom' in d)
## 方法2:如果 key 不存在,可以返回 None,或指定的值
print(d.get('Tom'))
print(d.get('Tom', -1))
输出结果
False
None
-1
注意:
- 返回 None 的时候 Python 的交互式命令行不显示结果。
- dict 内部存放的顺序和 key 放入的顺序是没有关系的。
(3)删除 key
pop(key) ==》key 和所对应的 value 均被删除。
(4)dict vs. list
和 list 比较, dict 有以下几个特点:
- 查找和插入的速度极快,不会随着 key 的增加而增加;
- 需要占用大量的内存,内存浪费多。
而 list 相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
2、集合——set
(1)定义
set 是一组 key 的集合,不存储 value。且 set 中的元素,没有重复。
==》可看作数学意义上的无序和无重复元素的集合
s = set([1, 1, 8, 2, 9, 2, 3, 3])
print(s)
输出结果
{1, 2, 3, 8, 9}
(2)添加元素——add(key)
s.add(4)
print(s)
输出结果
{1, 2, 3, 4, 8, 9}
(3)删除元素——remove(key)
s.remove(4)
print(s)
输出结果
{1, 2, 3, 8, 9}
(4)其他运算
s1 = set([1, 2, 3])
s2 = set([2, 3, 4])
print(s1 & s2) ## 交集
print(s1 | s2) ## 并集
输出结果
{2, 3}
{1, 2, 3, 4}
二、不可变对象
问题:
a = 'abc'
print(a.replace('a','A'))
print(a)
输出结果
Abc
abc
虽然字符串有个 replace()方法,也确实变出了’Abc’,但变量 a 最后仍是’abc’,应该怎么理解呢?
分析
重点:a 是变量,而’abc’才是字符串对象
==》a 指向的对象的内容是 ’abc‘
调用 a.replace(‘a’, ‘A’) 时,作用在字符串对象 ‘abc’ 上,replace 方法创建了一个新字符串’Abc’并返回,如果我们用变量 b 指向该新字符串,就容易理解了,变量 a 仍指向原有的字符串’abc’,但变量 b 却指向新字符串’Abc’了
==》对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
三、函数
一种代码抽象的方式。
1、调用函数
调用函数的时候,如果传入的参数数量不对或者参数类型错误,会报 TypeError 的错误。
2、数据类型转换函数
print(int('123'))
print(int(12.34))
print(float('12.34'))
print(str(1.23))
print(str(100))
print(bool(1))
print(bool(''))
输出结果
123
12
12.34
1.23
100
True
False
3、函数名
函数名,本质是指向一个函数对象的引用。
==》可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”。
a = abs
print(a(-1))
输出结果
1
4、定义函数
格式:def 函数名(参数):
,然后,在缩进块中编写函数体,函数的返回值用 return 语句返回。
如果没有 return 语句,函数执行完毕后也会返回结果,只是结果为 None。return None 可以简写为 return。
(1)空函数——pass语句
pass用于占位符,可以先让代码运行起来,之后再进行补充。
def nop():
pass
还可以放在其他语句中,eg:if 语句
5、参数检查——TypeError错误
- 参数个数不对
- 参数类型不对(内置函数可以检查出来,而自己写的会不完善==》参数类型检查)
def my_abs(x):
## 参数类型检验
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x > 0:
return x
else:
return -x
6、返回多个值
本质:函数可以同时返回多个值,但其实就是一个 tuple。
7、函数的参数
参数包括:必选参数、默认参数、可变参数和关键字参数。
==》处理复杂的参数,还可以简化调用者的代码
(1)位置参数(必选参数)
按照位置顺序依次赋给不同的参数。
(2)默认参数
注意:
- 必选参数在前,默认参数在后。
- 多参数时,将变化大的参数放在前面,变化小的参数放在后面,变化小的参数可以作为默认参数。
- 当不按顺序提供部分默认参数时,需要把参数名写上。
==》降低调用函数的难度。
def add_end(L = []):
L.append('END')
return L
print(add_end([1,2,3]))
print(add_end(['x','y','z']))
print(add_end())
print(add_end())
print(add_end())
输出结果
[1, 2, 3, 'END']
['x', 'y', 'z', 'END']
['END']
['END', 'END']
['END', 'END', 'END']
原因
- 默认参数 L 是一个变量,它指向对象[],每次调用该函数,如果改变了 L 的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了
==》默认参数必须指向不变对象
修改版本
def add_end(L = None):
if L is None:
L = []
L.append('END')
return L
print(add_end([1,2,3]))
print(add_end(['x','y','z']))
print(add_end())
print(add_end())
print(add_end())
输出结果
[1, 2, 3, 'END']
['x', 'y', 'z', 'END']
['END']
['END']
['END']
(3)可变参数
传入的参数个数是可变的。0 个 或 任意个
==》可变参数在函数调用时自动组装为一个 tuple
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
## 直接调用
print(calc(1,2))
print(calc()) ## 可以 0 个参数
## 调用已有的list或tuple元素
## *nums 表示把 nums 这个 list 的所有元素作为可变参数传进去。
nums = [1,2,3]
print(calc(*nums))
(4)关键字参数
传入 0 个或任意个含参数名的参数
==》这些关键字参数在函数内部自动组装为一个 dict。
==》用于扩展函数的功能,eg:选填参数
def person(name, age, **kw):
print('name:',name, 'age:',age, 'others:',kw)
## 直接调用
person('Michael', 30)
person('Bob', 35, city='Beijing')
person('Adam', 45, gender='M', job='Engineer')
## 调用已有的dict元素
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra)
extra 表示把 extra 这个 dict 的所有 key-value 用关键字参数传入到函数的kw 参数, kw 将获得一个 dict,注意 kw 获得的 dict 是 extra 的一份拷贝,对 kw 的改动不会影响到函数外的 extra。
(5)命名关键字参数
关键字参数检查
调用时,仍可传入不受限制的关键字参数
def person(name, age, **kw):
if 'city' in kw:
pass
if 'job' in kw:
pass
print('name:',name, 'age:',age, 'others:',kw)
person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
输出结果
name: Jack age: 24 others: {'city': 'Beijing', 'addr': 'Chaoyang', 'zipcode': 123456}
命名关键字参数——只接受特定的关键字参数
命名关键字参数需要一个特殊分隔符 *
,*
后面的参数被视为命名关键字参数。
def person(name, age, *,city, job):
print(name, age, city, job)
person('Jack', 24, city='Beijing', job='Enigneer')
## 命名关键字参数可以有缺省值
def person(name, age, *,city = 'Beijing', job):
print(name, age, city, job)
注意:
- 命名关键字参数必须传入参数名,这和位置参数不同。
- 命名关键字参数可以有缺省值;
(6)参数组合
参数定义顺序
必选参数、默认参数、可变参数/命名关键字参数和关键字参数。其中可变参数无法与命名关键字参数混合。
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
f1(1, 2)
f1(1, 2, c=3)
f1(1, 2, 3, 'a', 'b')
f1(1, 2, 3, 'a', 'b', x=99)
f2(1, 2, d=99, ext=None)
## 通过 tuple 和 dict 调用
args1 = (1,2,3,4)
kw1 = {'d':99,'x':'#'}
f1(*args1, **kw1)
args2 = (1,2,3)
kw2 = {'d':99,'x':'#'}
f2(*args2, **kw2)
输出结果
a = 1 b = 2 c = 0 args = () kw = {}
a = 1 b = 2 c = 3 args = () kw = {}
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
a = 1 b = 2 c = 3 d = 99 kw = {'x': '#'}
对于任意函数,都可以通过类似 func(*args, **kw)的形式调用它,无论它的参数是如何定义的。
四、递归函数
如果一个函数在内部调用自身本身,这个函数就是递归函数。
优点:简单、逻辑清晰
1、递归过程
注意:防止栈溢出
在计算机中,函数调用是通过栈( stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
==》解决方法:尾递归
==》尾递归:在函数返回的时候,调用自身本身,并且, return 语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
遗憾的是,大多数编程语言没有针对尾递归做优化, Python 解释器也没有做优化,所以,即使把上面的 fact(n)函数改成尾递归方式,也会导致栈溢出。