1. 字典
字典的创建
最常见的创建方式:
d = {"k1": "v1", "k2": "v2"}
再向字典中添加一对键值:
d["k3"] = "v3"
字典还可以用dict()创建,这种方式中,键是不加引号的:
d = dict(k1=v1, k2=v2, k3=v3)
dict里面也可以是列表,列表的每一个元素是元组:
d = dict([("k1", "v1"), ("k2", "v2"), ("k3", "v3")])
还可以用zip函数创建字典
d = dict(zip(["k1", "k2", "k3"], ["v1", "v2", "v3"]))
还可以用dict的fromkeys方法创建字典,第一个参数是一个可迭代序列,代表字典的key,第二个参数是初始化字典所有key的值(如果不传,则所有值为None):
d = dict.fromkeys("Fish", 250)
使用pop方法删除指定而key:
d.pop("s")
pop一个不存在的键会抛出异常:KeyError
可以用popitem()随机删除一个键,Python 3.7之后,则是删除最后一个加入字典的键
使用del可以删除字典的键或者整个字典:
del d["I"]
del d
如果只是想清空字典的内容,使用clear方法:
d.clear()
如果想修改字典的多个键值对,可使用update方法:
d.update({"i": 250, "s": 250})
d.update(h=70, F=666)
如果需要设置某个键的值,如果存在该键,则不更新,如果没有,则设置为该值,此时需要使用setdefault()方法:
d.setdefault('c', 'code')
如果需要遍历字典的每一个key-value或者每一个key或者每一个value,则可以使用items()、keys()、values()方法,这三个方法返回的是字典的视图对象。使用list可以将key和value的视图对象转换为列表:
list(d.keys()) # 相当于list(d)
list(d.values())
字典也可以使用推导式,使用推导式将key和value反转过来:
b = {v:k for k,v in d.items()}
c = {v:k for k,v in d.items() if v > 100}
d = {x: ord(x) for x in "FishC"}
2. 集合
集合的基础创建方法、特征、使用方法等不在此介绍,这里介绍一些进阶的操作。
检测两个集合之间是否毫不相关,使用isdisjoint方法:
s = set("FishC")
s.isdisjoint(set("Python"))
结果是False,因为他们有共同的h。disjoint方法不必传入集合,只要传入可迭代对象即可:
s = set("FishC")
s.isdisjoint("Java")
检测一个集合是否为另一个集合/可迭代对象的子集:
s.issubset("FishC.com.cn")
检测一个集合是否为另一个集合/可迭代对象的超集(父集):
s.issuperset("Fish")
计算并集、交集、差集、对称差集:
s.union({1, 2, 3}) # 并集
s.intersection("Fish") # 交集
s.difference("Fish") # 差集
s.symmetric_difference("Python") # 对称差集,相当于并集 - 交集
除了使用集合的方法确定子集和超集,以及计算并集、交集、差集、对称差集,也可以使用运算符确定子集和超集,以及计算并集、交集、差集、对称差集:
s >= set("Fish") # 是否是超集
s < set("Fish.com.cn") # 是否是子集
s | set("Python") # 并集
s & set("Python") # 交集
s - set("Python") # 差集
s ^ set("Python") # 对称差集
值得注意的是,使用方法时,方法的参数只要是可迭代对象即可,但是使用运算符,运算符两边都要为集合。
Python中的不可变集合:frozenset
s = set("FishC")
t = frozenset("FishC")
尝试使用update方法更新集合的元素:
s.update([1, 1], "23") # 不报错
t.update([1, 1], "23") # 报错
update方法传入的是可迭代对象,是更新一系列的值,如果只想添加单个元素,可以使用add方法:
s.add("45")
注意,上述代码会将45作为一个整体字符串插入集合,而不是插入4和5。
删除集合的元素:remove和discard:
s.remove("xxx")
s.discard("xxx")
两者的区别在于,如果元素不存在,remove方法会报错,discard方法不会报错
随机从集合弹出一个元素:pop
s.pop()
注意只有可哈希的对象才能作为字典的key和集合的元素,比如字符串,数字,元祖,但是列表不可以作为字典的key和集合的元素,列表是可变数据类型,是不可哈希的。所以嵌套集合一般是不可以的,因为集合也是可变的数据类型。如果非要嵌套集合,可以使用frozenset,即集合里嵌套frozenset。
3. 函数的参数
python函数的传参分为位置参数和关键字参数两种,比如如下函数定义:
使用位置参数调用:
使用关键字参数调用:
如果要混合使用关键字参数和位置参数,位置参数必须在关键字参数之前!
另外默认参数必须放在非默认参数的后面(函数定义时需要满足此规则)。
函数定义时,有时会看到参数为/,这表示/左边的参数必须是位置参数,不能是关键字参数:
/右边的参数可以是位置参数,也可以是关键字参数。
*则相反,*右边的参数必须是关键字参数,*左边的参数可以是位置参数,也可以是关键字参数:
如果可变参数(一般是*args表示)后面还有参数,则传参时必须用关键字参数,否则后面传的参数也会被认为是可变参数:
4. 函数的嵌套与闭包
函数如果嵌套,内层函数是无法直接修改外层函数的变量,这有点像一个函数内无法直接修改全局变量一样:
def funA():x = 520def funB():x = 880print("In funB, x = ", x)funB()print("In funA, x = ", x)
调用funA,运行结果如下:
类似于引入global变量,这里引入nonlocal变量,就可以在funB中修改funA的变量了:
def funA():x = 520def funB():nonlocal xx = 880print("In funB, x = ", x)funB()print("In funA, x = ", x)
对于函数的嵌套,如果内层函数(一段代码处理逻辑)使用到了外层函数的变量(相当于外层函数的局部变量),然后外层函数的返回值恰好是内层函数本身,那么调用外层函数就可以获得内层函数的引用,并且里面相当于间接的永久保存了外层函数的局部变量,这就是闭包。
如上图代码中,funA调用完毕之后,其中的局部变量x的生命周期理论上已经结束,但是再次调用funny(),却能够打印x的值。这就是闭包的好处:外层函数的作用域可以通过内层函数间接永久保存下来。
如果内层函数使用nonlocal语句修改外层函数的变量,那就更有意思了(坐标移动):
5. 装饰器
装饰器的原理就是将函数作为参数传递给另一个参数,举个例子:
import time
def time_master(func):print("开始运行程序")start = time.time()func()stop = time.time()print("结束程序运行")print(f"一共耗费了{(start - stop):.2f}秒")def myfun()time.sleep(2)print("Hello")time_master(myfunc())
如果每次想要知道函数的运行时间,都要显示地调用time_master,显然比较麻烦,如果能放函数执行时自动统计运行时间,就很方便了,此时就需要用到装饰器:
import time
def time_master(func):def call_func():print("开始运行程序")start = time.time()func()stop = time.time()print("结束程序运行")print(f"一共耗费了{(start - stop):.2f}秒")return call_func()@time_master
def myfun()time.sleep(2)print("Hello")myfunc()
装饰器的原理就是将被装饰的函数作为参数传进装饰器函数中,相当于:
myfunc = timemaster(myfunc)
多个装饰器可以装饰同一个函数,执行顺序是从下至上:
运行结果是65。
给装饰器传参数,需要再加一层嵌套,第一层函数传递参数,第二层函数传递函数:
6. 生成器
定义生成器很简单,在函数中使用yield代替return语句就可以了:
def counter():i = 0while i <=/ 5:yield ii += 1
调用函数不会得到i的值,会得到一个生成器,如果要获取生成器的值,使用for语句:
for i in counter():print(i)
生成器可以看成一个特殊的迭代器,每调用一次产生一个数据,并暂停,可以使用next函数获取其值:
使用生成器产生斐波那契数列:
def fbn():x = 0y = 1While True:yield xx, y = y, x + yf = fnb()
next(f)
next(f)
next(f)
next(f)
next(f)
元祖的推导式是一个生成器,所以元祖的推导式也叫生成器表达式:
t = (i ** 2 for i in range(10))
7. 函数文档
使用help函数查看函数的功能帮助:
help函数的输出其实是函数的文档,用""" """"包裹:
也可以使用函数名.__doc__查看函数文档(带有\n换行符,建议用print查看):
通过函数名.__annotations__查看参数类型注释:
8. 偏函数和@wraps装饰器
偏函数就是让函数的多个参数分开传递,每次传递一个参数就可以得到一个新的函数:
使用装饰器可以丰富函数的功能,但是会有副作用,改变函数的__name__属性:
查看myfunc.__name__,结果竟然是call_func,本质是因为使用了装饰器之后,等同于myfunc = time_master(myfunc),所以是call_func。为了解决这个问题,需要使用@wraps装饰器: