(一)functools:管理函数的工具
+ View Code
1.修饰符
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
39
40
41
42
43
44
45
from
functools
import
partial
'''
functools模块提供的主要工具就是partial类,可以用来包装一个有默认参数的callable对象。
得到的对象本身就是callable,可以把它看作是原来的参数。
'''
# 举个栗子
def
foo(name, age, gender):
print
(name, age, gender)
p
=
partial(foo,
"mashiro"
,
16
)
p(
"female"
)
# mashiro 16 female
'''
可以看到p相当于是已经绑定了name和age的foo函数,name我们在传参的时候只需要传入一个gender就可以了
这个函数的源码实现比较复杂,但是如果以简单的装饰器的方式实现就很清晰了
'''
def
my_partial(f, name, age):
def
inner(gender):
return
f(name, age, gender)
return
inner
p
=
my_partial(foo,
"satori"
,
16
)
p(
"female"
)
# satori 16 female
'''
可以看到,当我调用my_partial(foo, "satori", 16)的时候,返回了inner函数
此时的p相当于是inner,当我再调用p("female")的时候,等价于调用inner("female")
然后将两次传入的参数,按照顺序组合起来传递给foo函数,如果不限制参数的话就是:
def my_partial(f, *args1, **kwargs1):
def inner(*args2, **kwargs2):
from collections import ChainMap
args = args1 + args2
kwargs = dict(ChainMap(kwargs1, kwargs2))
return f(*args, **kwargs)
return inner
所以一定要和原函数的参数顺序保持一致,如果我传入p = my_partial("mashiro", 16),此时"mashiro"会传给name,16传给age
我再调用p(name="xxx")的话,肯定会报错的
'''
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
from
functools
import
partial
import
functools
'''
默认情况下,partial对象没有__name__属性的,如果没有这些属性,那么被修饰的函数会很难调试。
'''
def
foo():
# fucking
pass
print
(foo.__name__)
# foo
p
=
partial(foo)
try
:
print
(p.__name__)
except
AttributeError as e:
print
(e)
# 'functools.partial' object has no attribute '__name__'
# 那么如何添加呢?首先增加到包装器的属性在WRAPPER_ASSIGNMENTS中定义,另外WRAPPER_UPDATES列出了要修改的值
print
(
"assign:"
, functools.WRAPPER_ASSIGNMENTS)
# assign: ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
print
(
"update:"
, functools.WRAPPER_UPDATES)
# update: ('__dict__',)
# 添加,表示从原函数将属性赋值或增加到partial对象
functools.update_wrapper(p, foo)
print
(p.__name__)
# foo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from
functools
import
partial
'''
可以把partial看成是一个简单的装饰器,装饰器不仅可以装饰函数,还可以装饰类,只要是callable对象,说白了只要是能加上()的都可以
这就是Python的魅力,非常的动态。比如列表进行extend, 其实不仅仅可以extend一个列表,还可以是元组,甚至是字典,只要是iterable对象都可以。
'''
class
A:
def
__init__(
self
, name, age, gender):
self
.name
=
name
self
.age
=
age
self
.gender
=
gender
def
print_info(
self
):
print
(f
"name: {self.name}, age: {self.age}, gender: {self.gender}"
)
p
=
partial(A,
"mashiro"
,
16
)
a
=
p(
"female"
)
# 这两步等价于 a = A("mashiro", 16, "female")
a.print_info()
# name: mashiro, age: 16, gender: female
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
from
functools
import
partial, partialmethod
'''
partial返回一个可以直接使用的callable,partialmethod返回的callable则可以用做对象的非绑定方法
'''
# 举个例子
def
standalone(
self
):
print
(f
"self = {self}"
)
class
A:
method1
=
partial(standalone)
method2
=
partialmethod(standalone)
a
=
A()
try
:
a.method1()
except
TypeError as e:
# 由于standalone需要一个参数self,我们这里没有传,因此报错
print
(e)
# standalone() missing 1 required positional argument: 'self'
# 但是我们调用method2呢?
a.method2()
# self = <__main__.A object at 0x0000000002964588>
'''
得到了一个A的实例对象。
所以,partial在哪里调用时没有区别的,必须手动显示地传递,该是几个就是几个。
但是在类中如果使用partialmethod定义的话,那么在使用实例(a)调用的话,会自动将实例传进去。
'''
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
from
functools
import
wraps
'''
我们在知道在使用装饰器装饰完函数的时候,属性会变。比如:
'''
def
deco(func):
def
inner(
*
args,
*
*
kwargs):
return
func(
*
args,
*
*
kwargs)
return
inner
@deco
def
foo():
pass
# 函数从下到下执行,加上@deco等价于,foo = deco(foo) = inner,也就是说此时的foo不再是foo了,已经是inner了
print
(foo.__name__)
# inner
# 那么如何在装饰的时候,还保证原来函数的信息呢
def
deco(func):
@wraps
(func)
# 只需要加上这一个装饰器即可,会自动对所修饰的函数应用update_wrapper
def
inner(
*
args,
*
*
kwargs):
return
func(
*
args,
*
*
kwargs)
return
inner
@deco
def
bar():
pass
# 可以看到原来函数的信息并没有改变
print
(bar.__name__)
# bar
2.比较
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
import
functools
'''
在Python2中,类可以一个__cmp__()方法,它会根据这个对象小于、等于、或者大于所比较的元素而分别返回-1、0、1.
Python2.1中引入了富比较(rich comparision)的方法。
如:__lt__(),__gt__(),__le__(),__eq__(),__ne__(),__gt__()和__ge__(),可以完成一个比较操作并返回一个bool值。
Python3已经废弃了__cmp__()方法。
另外,functools提供了一些工具,从而能更容易地编写符合新要求的类,即符合Python3中新的比较需求。
'''
@functools
.total_ordering
class
A:
def
__init__(
self
, val):
self
.val
=
val
def
__eq__(
self
, other):
return
self
.val
=
=
other.val
def
__gt__(
self
, other):
return
self
.val > other.val
a1
=
A(
1
)
a2
=
A(
2
)
print
(a1 < a2)
'''
这个类必须提供__eq__()和另外一个富比较方法的实现,这个修饰符会自动增加其余的方法。
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import
functools
'''
由于Python3废弃了老式的比较函数,sort()之类的函数中也不再支持cmp参数。
对于使用了比较函数的较老的程序,可以使用cmp_to_key()将比较函数转换为一个比对键的函数,这个键用于确定元素在最终序列中的位置
'''
def
compare_obj(a, b):
if
a < b:
return
-
1
elif
a > b:
return
1
else
:
return
0
l
=
[
1
,
5
,
2
,
11
,
2
,
44
,
54
,
5
,
1
]
print
(
sorted
(l, key
=
functools.cmp_to_key(compare_obj)))
# [1, 1, 2, 2, 5, 5, 11, 44, 54]
3.缓存
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import
functools
'''
lru_cache()修饰符将一个函数包装在一个"最近最少使用的"缓存中。函数的参数用来建立一个散列键,然后映射到这个结果。
后续调用如果有相同的参数,就会从这个缓存中获取值而不会再次调用这个函数。
这个修饰符还会为函数增加方法来检查缓存的状态(cache_info)和清空缓存(cache_clear)
'''
@functools
.lru_cache()
# 里面可以执行参数maxsize,默认是128
def
foo(a, b):
print
(f
"foo({a} * {b})"
)
return
a
*
b
print
(
"第一次调用"
)
for
i
in
range
(
2
):
for
j
in
range
(
2
):
foo(i, j)
print
(foo.cache_info())
print
(
"\n第二次调用"
)
for
i
in
range
(
3
):
for
j
in
range
(
3
):
foo(i, j)
print
(foo.cache_info())
print
(
"清除缓存"
)
foo.cache_clear()
print
(foo.cache_info())
print
(
"\n第三次调用"
)
for
i
in
range
(
2
):
for
j
in
range
(
2
):
foo(i, j)
print
(foo.cache_info())
'''
第一次调用
foo(0 * 0)
foo(0 * 1)
foo(1 * 0)
foo(1 * 1)
CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)
第二次调用
foo(0 * 2)
foo(1 * 2)
foo(2 * 0)
foo(2 * 1)
foo(2 * 2)
CacheInfo(hits=4, misses=9, maxsize=128, currsize=9)
清除缓存
CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)
第三次调用
foo(0 * 0)
foo(0 * 1)
foo(1 * 0)
foo(1 * 1)
CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)
'''
# 我们观察一下第二次调用,3 * 3应该是9次,为什么只有5次,因为第一次调用有4次执行过了,放到缓存里,因此不需要执行了
4.reduce
1
2
3
4
5
6
7
8
9
10
11
12
13
import
functools
'''
reduce这个函数无需介绍,在Python2中是内置的,但是在Python3中被移到functools下面
'''
l
=
range
(
100
)
print
(functools.
reduce
(
lambda
x, y: x
+
y, l))
# 4950
print
(functools.
reduce
(
lambda
x, y: x
+
y, l,
10
))
# 4960
print
(functools.
reduce
(
lambda
x, y: x
+
y, l,
100
))
# 5050
l
=
[
1
,
2
,
3
,
4
,
5
]
print
(functools.
reduce
(
lambda
x, y: x
*
y, l))
# 120
5.泛型函数
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
import
functools
'''
在类似Python的动态类型语言中,通常需要基于参数的类型完成稍有不同的操作,特别是在处理元素列表与单个元素的差别时。
直接检查参数的类型固然很简单,但是有些情况下,行为差异可能被隔离到单个的函数中。
对于这些情况,functools提供了singledispatch修饰符来注册一组泛型函数,可以根据函数第一个参数的类型自动切换
'''
@functools
.singledispatch
def
myfunc(arg):
print
(f
"default myfunc {arg}"
)
@myfunc
.register(
int
)
def
myfunc1(arg):
print
(f
"myfunc1 {arg}"
)
@myfunc
.register(
list
)
def
myfunc2(arg):
print
(f
"myfunc2 {arg}"
)
myfunc(
"string"
)
# default myfunc string
myfunc(
123
)
# myfunc1 123
myfunc([
"1"
,
"2"
])
# myfunc2 ['1', '2']
'''
可以看到使用signledispatch包装的是默认实现,在未指定其他类型特定函数的时候就用这个默认实现。
然后使用包装的函数这里是myfunc,通过register(数据类型)进行注册,根据所传参数的类型,从而执行对应的函数
'''
(二)itertools:迭代器函数
1.合并和分解迭代器
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import
itertools
'''
chain函数可以接收多个可迭代对象(或者迭代器)作为参数,最后返回一个迭代器。
它会生成所有输入迭代器的内容,就好像这些内容来自一个迭代器一样。
类似于collections下的ChainMap,可以合并多个字典。chain可以合并多个可迭代对象
'''
c
=
itertools.chain([
1
,
2
,
3
],
"abc"
, {
"k1"
:
"v1"
,
"k2"
:
"v2"
})
print
(c)
# <itertools.chain object at 0x00000000029745F8>
for
i
in
c:
print
(i, end
=
" "
)
# 1 2 3 a b c k1 k2
print
()
# 还可以使用chain.from_iterable,参数接收多个可迭代对象组成的一个可迭代对象
c
=
itertools.chain.from_iterable([[
1
,
2
,
3
],
"abc"
, {
"k1"
:
"v1"
,
"k2"
:
"v2"
}])
for
i
in
c:
print
(i, end
=
" "
)
# 1 2 3 a b c k1 k2
# 函数zip则是把多个迭代器对象组合到一个元组中
name
=
[
"古明地觉"
,
"椎名真白"
,
"雪之下雪乃"
]
where
=
[
"东方地灵殿"
,
"樱花张的宠物女孩"
,
"春物"
]
z
=
zip
(name, where)
print
(
"\n"
, z)
# <zip object at 0x0000000001DC03C8>
print
(
list
(z))
# [('古明地觉', '东方地灵殿'), ('椎名真白', '樱花张的宠物女孩'), ('雪之下雪乃', '春物')]
# zip英文意思是拉链,很形象,就是把对应元素给组合起来
# 但如果两者长度不一致怎么办?
name
=
[
"古明地觉"
,
"椎名真白"
,
"雪之下雪乃"
,
"xxx"
]
where
=
[
"东方地灵殿"
,
"樱花张的宠物女孩"
,
"春物"
]
print
(
list
(
zip
(name, where)))
# [('古明地觉', '东方地灵殿'), ('椎名真白', '樱花张的宠物女孩'), ('雪之下雪乃', '春物')]
# 可以看到,不一致的时候,当一方结束之后就停止匹配。
# 如果想匹配长的,那么可以使用zip_longest,这个函数不像zip一样是内置的,它在itertools下面
print
(
list
(itertools.zip_longest(name, where)))
# [('古明地觉', '东方地灵殿'), ('椎名真白', '樱花张的宠物女孩'), ('雪之下雪乃', '春物'), ('xxx', None)]
# 可以看到没有的默认赋值为None了,当然我们也可以指定填充字符
print
(
list
(itertools.zip_longest(name, where, fillvalue
=
"你输入的是啥啊"
)))
# [('古明地觉', '东方地灵殿'), ('椎名真白', '樱花张的宠物女孩'), ('雪之下雪乃', '春物'), ('xxx', '你输入的是啥啊')]
# isslice返回一个迭代器,按照索引从迭代器返回所选择的元素
num
=
range
(
20
)
# 从index=5的地方选到index=10(不包含)的地方
s
=
itertools.islice(num,
5
,
10
)
print
(
list
(s))
# [5, 6, 7, 8, 9]
# 从开头选到index=5的地方
s
=
itertools.islice(num,
5
)
print
(
list
(s))
# [0, 1, 2, 3, 4]
# 从index=5的地方选择到index=15的地方,步长为3
s
=
itertools.islice(num,
5
,
15
,
3
)
print
(
list
(s))
# [5, 8, 11, 14]
'''
所以除了迭代器之外,
如果只传一个参数,比如5,表示从index=0选到index=5(不包含)的地方
如果传两个参数,比如5和10,表示从index=5选到index=10(不包含)的地方
如果传三个参数,比如5和10和2,表示从index=5选到index=10(不包含)的地方,步长为2
'''
# 那么支不支持负数索引呢?答案是不支持的,因为不知道迭代器有多长,除非全部读取,可是那样的话干嘛不直接转化为列表之后再用[:]这种形式呢?
# 之所以使用isslice这种形式,就是为了在不全部读取的情况下,也能选择出我们想要的部分,所以这种方式只支持从前往后,不能从后往前读。
# tee()函数根据一个原输入迭代器返回多个独立、和原迭代器一模一样的迭代器(默认为两个)
r
=
[
1
,
2
,
3
,
4
,
5
]
i1, i2
=
itertools.tee(r)
print
(
list
(i1))
# [1, 2, 3, 4, 5]
print
(
list
(i2))
# [1, 2, 3, 4, 5]
2.转换输入
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
import
itertools
'''
内置的map()函数返回一个迭代器,它对输入迭代器中的值调用一个函数并返回结果。
输入迭代中的元素全部被消费时,map()函数就会停止
'''
l
=
[
1
,
2
,
3
]
map_l
=
map
(
lambda
x:
str
(x)
+
"a"
, l)
print
(
list
(map_l))
# ['1a', '2a', '3a']
l1
=
[(
0
,
5
), (
1
,
6
), (
2
,
7
)]
'''
注意map里面的函数只能有一个参数,因此不可以写成以下格式
map_l1 = map(lambda x, y: x*y, l1)
但是可以这样
'''
map_l1
=
map
(
lambda
x: x[
0
]
*
x[
1
], l1)
print
(
list
(map_l1))
# [0, 6, 14]
# 但是itertools下的startmap()是支持的
l2
=
[(
1
,
2
,
3
), (
4
,
5
,
6
), (
7
,
8
,
9
)]
# 注意里面的函数的参数的参数个数是由我们后面传入对象决定的,这里每个元组显然有三个元素,所以需要三个参数
map_l1
=
itertools.starmap(
lambda
x, y, z: f
"{x} + {y} + {z} = {x+y+z}"
, l2)
print
(
list
(map_l1))
# ['1 + 2 + 3 = 6', '4 + 5 + 6 = 15', '7 + 8 + 9 = 24']
# map的话只能通过lambda x: x[0], x[1], x[2]这样的形式
# starmap只能对类似于[(), (), ()]这种值进行处理,比如[1, 2, 3]使用starmap是会报错的,但是[(1, ), (2, ), (3, )]不会报错
3.生成新值
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
import
itertools
'''
count(start=0, step=1)函数返回一个迭代器,该迭代器能够无限地生成连续的整数。
接收两个参数:起始(默认为0)和步长(默认为1)
等价于:
def count(firstval=0, step=1):
x = firstval
while 1:
yield x
x += step
'''
'''
cycle(iterable)返回一个迭代器,会无限重复里面的内容,直到内存耗尽
'''
c2
=
itertools.cycle(
"abc"
)
print
(
list
(itertools.islice(c2,
0
,
10
)))
# ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a']
'''
repeat(obj, times=None),无限重复obj,除非指定times。
'''
print
(
list
(itertools.repeat(
"abc"
,
3
)))
# ['abc', 'abc', 'abc']
4.过滤
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
import
itertools
l
=
[
1
,
2
,
3
,
4
,
5
]
drop_l
=
itertools.dropwhile(
lambda
x: x <
3
, l)
print
(
list
(drop_l))
# [3, 4, 5]
take_l
=
itertools.takewhile(
lambda
x: x <
3
, l)
print
(
list
(take_l))
# [1, 2]
filter_l
=
filter
(
lambda
x: x <
3
, l)
print
(
list
(filter_l))
# [1, 2]
filterfalse_l
=
itertools.filterfalse(
lambda
x: x <
3
, l)
print
(
list
(filterfalse_l))
# [3, 4, 5]
'''
filter和takewhile一样,过滤出条件为True的值
filterfalse和dropwhile一样,过滤出条件为False的值
'''
# compress则提供了另一种过滤可迭代对象内容的方法。
# 举个栗子
condition
=
[
True
,
False
,
True
,
True
,
False
]
data
=
[
1
,
2
,
3
,
4
,
5
]
print
(
list
(itertools.compress(data, condition)))
# [1, 3, 4]
# 或者
condition
=
[
1
,
0
,
"x"
,
"x"
, {}]
print
(
list
(itertools.compress(data, condition)))
# [1, 3, 4]
5.合并输入
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import
itertools
'''
accumulate函数处理输入迭代器,得到一个类似于斐波那契的结果
'''
print
(
list
(itertools.accumulate(
range
(
5
))))
# [0, 1, 3, 6, 10]
print
(
list
(itertools.accumulate(
"abcde"
)))
# ["a", "ab", "abc", "abcd", "abcde"]
# 所以这里的相加还要看具体的含义
try
:
print
(
list
(itertools.accumulate([[
1
,
2
], (
3
,
4
)])))
except
TypeError as e:
print
(e)
# can only concatenate list (not "tuple") to list
# 这里就显示无法将列表和元组相加
# 当然也可以自定义
data
=
[
1
,
2
,
3
,
4
,
5
]
method
=
lambda
x, y: x
*
y
print
(
list
(itertools.accumulate(data, method)))
# [1, 2, 6, 24, 120]
# 可以看到这里的结果就改变了
'''
product则是会将多个可迭代对象组合成一个笛卡尔积
'''
print
(
list
(itertools.product([
1
,
2
,
3
], [
2
,
3
])))
# [(1, 2), (1, 3), (2, 2), (2, 3), (3, 2), (3, 3)]
'''
permutations函数从输入迭代器生成元素,这些元素以给定长度的排列形成组合。默认会生成所以排列的全集
'''
data
=
[
1
,
2
,
3
,
4
]
print
(
list
(itertools.permutations(data)))
'''
[(1, 2, 3, 4), (1, 2, 4, 3), (1, 3, 2, 4), (1, 3, 4, 2), (1, 4, 2, 3), (1, 4, 3, 2),
(2, 1, 3, 4), (2, 1, 4, 3), (2, 3, 1, 4), (2, 3, 4, 1), (2, 4, 1, 3), (2, 4, 3, 1),
(3, 1, 2, 4), (3, 1, 4, 2), (3, 2, 1, 4), (3, 2, 4, 1), (3, 4, 1, 2), (3, 4, 2, 1),
(4, 1, 2, 3), (4, 1, 3, 2), (4, 2, 1, 3), (4, 2, 3, 1), (4, 3, 1, 2), (4, 3, 2, 1)]
'''
print
(
list
(itertools.permutations(data,
2
)))
# [(1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 2), (3, 4), (4, 1), (4, 2), (4, 3)]
# permutations只要顺序不同就看做一种结果,combinations则保证只要元素相同就是同一种结果
data
=
"abcd"
print
(
list
(itertools.combinations(data,
3
)))
# [('a', 'b', 'c'), ('a', 'b', 'd'), ('a', 'c', 'd'), ('b', 'c', 'd')]
# 尽管combinations不会重复单个的输入元素,但是有时候可能也需要考虑包含重复元素的组合。
# 对于这种情况,可以使用combination_with_replacement
print
(
list
(itertools.combinations_with_replacement(data,
3
)))
'''
[('a', 'a', 'a'), ('a', 'a', 'b'), ('a', 'a', 'c'), ('a', 'a', 'd'), ('a', 'b', 'b'),
('a', 'b', 'c'), ('a', 'b', 'd'), ('a', 'c', 'c'), ('a', 'c', 'd'), ('a', 'd', 'd'),
('b', 'b', 'b'), ('b', 'b', 'c'), ('b', 'b', 'd'), ('b', 'c', 'c'), ('b', 'c', 'd'),
('b', 'd', 'd'), ('c', 'c', 'c'), ('c', 'c', 'd'), ('c', 'd', 'd'), ('d', 'd', 'd')]
'''
(三)operator:内置操作符的函数接口
1
2
3
4
5
6
7
8
import
operator
'''
使用迭代器编程时,有时需要为简单的表达式创建小函数。
operator模块提供了一些函数,可以对应标准API中内置的算术、比较和其他操作。
注意:operator中提供的操作,都可以通过lambda函数实现,就我个人而言更喜欢lambda函数
'''
1.逻辑操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import
operator
'''
有些函数可以用来确定一个值得相应布尔值,将其取反以创建相反的布尔值,以及比较对象以查看它们是否相等
'''
a
=
-
1
# 为真
b
=
5
# 为真
# not为关键字,所以是not_,判断值是否为假。由于a=-1为真,不为假,所以是False
print
(operator.not_(a))
# False
# truth,判断值是否为真
print
(operator.truth(b))
# True
# is_, 等价于a is b
print
(operator.is_(a, b))
# False
# is_not,等价于a is not b
print
(operator.is_not(a, b))
# True
2.比较操作符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import
operator
'''
支持所有的富比较操作符
'''
a
=
-
1
b
=
5
for
func
in
(
"lt"
,
"le"
,
"gt"
,
"ge"
,
"eq"
,
"ne"
):
# <, <=, >, >=, ==, !=
print
(f
"{func}(a, b): {getattr(operator, func)(a, b)}"
)
'''
lt(a, b): True
le(a, b): True
gt(a, b): False
ge(a, b): False
eq(a, b): False
ne(a, b): True
'''
3.算术操作符
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import
operator
'''
支持处理数字值的算术操作符
'''
a
=
-
1
b
=
5.0
c
=
2
d
=
6
print
(
"abs(a): "
, operator.
abs
(a))
print
(
"neg(a): "
, operator.neg(a))
print
(
"neg(b): "
, operator.neg(b))
print
(
"pos(a): "
, operator.pos(a))
print
(
"pos(b): "
, operator.pos(b))
print
(
"add(a, b): "
, operator.add(a, b))
print
(
"floordiv(a, b): "
, operator.floordiv(a, b))
print
(
"floordiv(a, c): "
, operator.floordiv(a, c))
print
(
"mod(a, b): "
, operator.mod(a, b))
print
(
"mul(a, b): "
, operator.mul(a, b))
print
(
"pow(c, d): "
, operator.
pow
(c, d))
print
(
"sub(b, a): "
, operator.sub(b, a))
print
(
"truediv(a, b): "
, operator.truediv(a, b))
print
(
"truediv(d, c): "
, operator.truediv(d, c))
print
(
"and_(c, d): "
, operator.and_(c, d))
print
(
"invert(c): "
, operator.invert(c))
print
(
"lshift(c, d): "
, operator.lshift(c, d))
print
(
"or_(c, d): "
, operator.or_(c, d))
print
(
"rshift(d, c): "
, operator.rshift(d, c))
print
(
"xor(c, d): "
, operator.xor(c, d))
'''
a = -1
b = 5.0
c = 2
d = 6
abs(a): 1
neg(a): 1
neg(b): -5.0
pos(a): -1
pos(b): 5.0
add(a, b): 4.0
floordiv(a, b): -1.0
floordiv(a, c): -1
mod(a, b): 4.0
mul(a, b): -5.0
pow(c, d): 64
sub(b, a): 6.0
truediv(a, b): -0.2
truediv(d, c): 3.0
and_(c, d): 2
invert(c): -3
lshift(c, d): 128
or_(c, d): 6
rshift(d, c): 1
xor(c, d): 4
'''
4.序列操作符
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
39
40
41
42
43
44
45
46
47
48
49
50
import
operator
'''
处理序列的操作符可以分为四组:建立序列,搜索元素,访问内容,以及从序列删除元素
'''
a
=
[
1
,
2
,
3
]
b
=
[
'a'
,
'b'
,
'c'
]
print
(
"a ="
, a)
print
(
"b ="
, b)
print
(
"concat(a, b): "
, operator.concat(a, b))
print
(
"contains(a, 1): "
, operator.contains(a,
1
))
print
(
"contains(b, 'd'): "
, operator.contains(b,
'd'
))
print
(
"countOf(a, 1): "
, operator.countOf(a,
1
))
print
(
"countOf(b, 'd'): "
, operator.countOf(b,
'd'
))
print
(
"indexOf(a, 5): "
, operator.indexOf(a,
1
))
print
(
"getitem(b, 1): "
, operator.getitem(b,
1
))
print
(
"getitem(b slice(1, 3): "
, operator.getitem(b,
slice
(
1
,
3
)))
print
(
"setitem(b, 1, 'd'): "
, end
=
' '
)
operator.setitem(b,
1
,
'd'
)
print
(b)
print
(
"setitem(a, slice(1, 3), [4, 5]): "
, end
=
' '
)
operator.setitem(a,
slice
(
1
,
3
), [
4
,
5
])
print
(a)
print
(
"delitem(b, 1)"
, end
=
' '
)
operator.delitem(b,
1
)
print
(b)
print
(
"delitem(a, slice(1, 3): "
, end
=
' '
)
operator.delitem(b,
slice
(
1
,
3
))
print
(a)
# 其中的一些操作(setitem()和delitem())会原地修改序列,返回的是None
'''
a = [1, 2, 3]
b = ['a', 'b', 'c']
concat(a, b): [1, 2, 3, 'a', 'b', 'c']
contains(a, 1): True
contains(b, 'd'): False
countOf(a, 1): 1
countOf(b, 'd'): 0
indexOf(a, 5): 0
getitem(b, 1): b
getitem(b slice(1, 3): ['b', 'c']
setitem(b, 1, 'd'): ['a', 'd', 'c']
setitem(a, slice(1, 3), [4, 5]): [1, 4, 5]
delitem(b, 1) ['a', 'c']
delitem(a, slice(1, 3): [1, 4, 5]
'''
# 个人觉得这些都没有什么乱用,可以直接实现的,没必要使用这个库
5.原地操作符
略
6.排序
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
39
40
41
42
43
44
45
46
47
48
49
50
51
import
operator
'''
这一节书上没有,是我自己加的。个人觉得这个库只有在这一方面会有用。
'''
l
=
[
[
1
,
3
],
[
7
,
4
],
[
6
,
2
],
[
3
,
5
]
]
# 现在我要将这个列表进行排序,怎么排呢?里面里面列表的第一个元素的顺序从小到大排
# 也就是说,排完序之后应该是这样的, [[1, 3], [3, 5], [6, 2], [7, 4]]
l.sort(key
=
operator.itemgetter(
0
))
# 按照key来排序,itemgetter(0),表示获取各自索引为0的元素,进行比较
print
(l)
# [[1, 3], [3, 5], [6, 2], [7, 4]]
# 但是我们可以使用lambda函数来实现,而且还可以实现更加复杂的效果
l1
=
[
[
1
,
3
],
[
7
,
4
],
[
6
,
2
],
[
3
,
5
]
]
l1.sort(key
=
lambda
x: x[
0
])
print
(l1)
# [[1, 3], [3, 5], [6, 2], [7, 4]]
l2
=
[
-
3
,
-
5
,
3
,
-
9
,
8
,
2
]
# 对l2我想这样排序,首先按照正负数排序,负数排左边,正数排右边。
# 然后按照绝对值得大小排, 绝对值大的排左边,小的排右边
# 也就是说排完之后应该是这样的,[-9, -5, -3, 8, 3, 2]
l2.sort(key
=
lambda
x: (x >
0
, ~
abs
(x)))
print
(l2)
# [-9, -5, -3, 8, 3, 2]
# 因此可以看到这个x代表的就是序列里面的元素
# 如果是字典的话,那么x就是字典里面的key
d
=
{
"a"
:
4
,
"c"
:
3
,
"b"
:
2
}
# 可是字典没有sort,我们如何验证呢?
import
heapq
# 按照value选择两个最大的
# 参数:选择几个,从哪里选择,按照什么规则去选择
print
(heapq.nlargest(
2
, d, key
=
lambda
x: d[x]))
# ['a', 'c']
'''
个人总结一下哈,我个人觉得这个库是真的没有什么用,完全可以使用其他的方法代替,而且更容易理解。
当然也可能是我能力不够,这个库的更高级的用法我没有看到(雾)
'''
(四)contextlib:上下文管理器工具
1
2
3
4
5
6
import
contextlib
'''
contextlib模块包含的工具用于处理上下文管理器和with语句
'''
1.上下文管理器API
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
import
contextlib
'''
上下文管理器(context manager)负责管理一个代码块中的资源,会在进入代码块时创建资源,然后再退出代码后清理这个资源。
比如:文件就支持上下文管理器API,可以确保文件读写后关闭文件。
with open("xxx") as f:
f.read()
'''
# 那么这是如何实现的呢?我们可以手动模拟一下
class
Open
:
def
__init__(
self
, filename, mode
=
'r'
, encoding
=
None
):
self
.filename
=
filename
self
.mode
=
mode
self
.encoding
=
encoding
def
__enter__(
self
):
print
(
"__enter__,有了这个就可以使用with Open() as xx语句,这里的xx就是我return的内容"
)
return
self
def
read(
self
):
print
(f
"文件进行读操作,读取文件:{self.filename}, 模式:{self.mode}, 编码:{self.encoding}"
)
def
__exit__(
self
, exc_type, exc_val, exc_tb):
print
(
"__exit__,我是用来清理资源的,当操作执行完毕之后就会执行我,比如:关闭文件"
)
with
Open
(
"1.xxx"
) as f:
f.read()
'''
__enter__,有了这个就可以使用with Open() as xx语句,这里的xx就是我return的内容
文件进行读操作,读取文件:1.xxx, 模式:r, 编码:None
__exit__,我是用来清理资源的,当操作执行完毕之后就会执行我,比如:关闭文件
'''
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import
contextlib
# 相较于try:finally语法,结合上下文管理器和with语句是一种更加紧凑的写法,因为__exit__这个方法总是会被调用的,即使产生了异常
class
Open
:
def
__init__(
self
, filename, mode
=
'r'
, encoding
=
None
):
self
.filename
=
filename
self
.mode
=
mode
self
.encoding
=
encoding
def
__enter__(
self
):
return
123
def
__exit__(
self
, exc_type, exc_val, exc_tb):
# 注意到这里有三个参数,使用pycharm的时候,会很智能地自动帮我们加上去
print
(exc_type)
print
(exc_val)
print
(exc_tb)
return
True
with
Open
(
"1.xx"
) as f:
print
(f)
'''
123
None
None
None
'''
with
Open
(
"1.xx"
) as f:
print
(f)
1
/
0
print
(
123
)
print
(
456
)
print
(
789
)
print
(
"你猜我会被执行吗?"
)
'''
123
<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x0000000009EDD848>
你猜我会被执行吗?
'''
'''
可以看到当我们程序没有出错的时候,打印的值全为None。一旦with语句里面出现了异常,那么会立即执行__exit__函数。
里面的参数就是:异常的类型,异常的值,异常的信息栈。
因此:当with语句结束之后会调用__exit__函数,如果with语句里面出现了错误则会立即调用__exit__函数。
但是__exit__函数返回了个True是什么意思呢?
当with语句里面出现了异常,理论上是会报错的,但是由于要执行__exit__函数,所以相当于暂时把异常塞进了嘴里。
如果__exit__函数最后返回了一个布尔类型为True的值,那么会把塞进嘴里的异常吞下去,程序不报错正常执行。如果返回布尔类型为False的值,会在执行完__exit__函数之后再把异常吐出来,引发程序崩溃。
这里我们返回了True,因此程序正常执行,最后一句话被打印了出来。
但是1/0这句代码后面的几个print却没有打印,为什么呢?
因为上下文管理执行是有顺序的,
with Open("1.xxx") as f:
code1
code2
先执行Open函数的__init__函数,再执行__enter__函数,把其返回值给交给f,然后执行with语句里面的代码,最后执行__exit__函数。
只要__exit__函数执行结束,那么这个with语句就算结束了。
而with语句里面如果有异常会立即进入__exit__函数,因此异常语句后面的代码是无论如何都不会被执行的。
'''
2.上下文管理器作为函数修饰符
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import
contextlib
'''
类ContextDecorator增加了对常规上下文管理器类的支持,因此不仅可以作为上下文管理器,也可以作为函数修饰符
'''
class
Context(contextlib.ContextDecorator):
def
__init__(
self
, how_used):
self
.how_used
=
how_used
print
(f
"__init__({self.how_used})"
)
def
__enter__(
self
):
print
(f
"__enter__({self.how_used})"
)
return
self
def
__exit__(
self
, exc_type, exc_val, exc_tb):
print
(f
"__exit__({self.how_used})"
)
# 一旦继承了ContextDecorator这个类,那么便可以作为装饰器去装饰。
# 说白了Context("as decorator")返回了Context的一个实例对象,理论上是不可调用的,但是父类实现了call方法。
# 因此如果我们自己实现了一个定义了__call__方法的类,让Context去继承,也是可以达到类似的效果
@Context
(
"as decorator"
)
def
func(message):
print
(message)
'''
__init__(as decorator)
'''
# 当我执行执行func的时候,此时的func已经不再是原来的那个func了
# func = self(func),从而调用Context的__call__方法,可是Context没有这个方法,那么它的父类肯定有。
# 我们看看contextlib.ContextDecorator的源码,去掉了注释
'''
class ContextDecorator(object):
def _recreate_cm(self):
return self
def __call__(self, func):
@wraps(func)
def inner(*args, **kwds):
with self._recreate_cm():
return func(*args, **kwds)
return inner
可以看到首先@Context("as decorator")会执行Context的__init__方法,打印__init__(as decorator),得到实例对象self
此时等价于@self, -->func = self(func),执行__call__方法,-->func = inner
当执行inner的时候,会先调用__enter__方法,然后执行inner,最后执行__exit__方法
'''
func(
"doing work i the wrapped function"
)
# 这个无需解释
with Context(
"as contetx manager"
):
print
(
"doing work in the context"
)
'''
__init__(as contetx manager)
__enter__(as contetx manager)
doing work in the context
__exit__(as contetx manager)
'''
3.从生成器到上下文管理器
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import
contextlib
'''
采用传统方式创建上下文管理器并不难,只需要包含一个__enter__方法和一个__exit__方法的类即可。
不过某些时候,如果只有很少的上下文需要管理,那么完整地写出所以代码便会成为额外的负担。
在这些情况下,可以使用contextmanager修饰符将一个生成器函数转换为上下文管理器。
代码结构:
@contextlib.contextmanager
def foo():
print(123)
yield 456
print(789)
with foo() as f:
print(f)
123
456
789
只要给函数加上这个装饰器,那么便可以使用with as 语句。
当中的yield相当于将代码块分隔为两个战场,yield上面的代码相当于__exter__会先执行,然后将yield的值交给f,然后执行yield下面的代码块
'''
@contextlib
.contextmanager
def
bar(name, age):
print
(f
"name is {name}, age is {age}"
)
yield
list
print
(
"我是一匹狼,却变成了狗"
)
with bar(
"mashiro"
,
16
) as b:
print
(b(
"abcde"
))
'''
name is mashiro, age is 16
['a', 'b', 'c', 'd', 'e']
我是一匹狼,却变成了狗
'''
# 先执行yield上面的内容,然后yield list,那么b = list,最后执行yield下面的内容
# contextmanager返回的上下文管理器排成子ContextDecorator,所以也可以被用作函数修饰符
@bar
(
"satori"
,
16
)
def
foo():
print
(
"猜猜我会在什么地方输出"
)
foo()
'''
name is satori, age is 16
猜猜我会在什么地方输出
我是一匹狼,却变成了狗
'''
# bar中含有yield,肯定是一个生成器,所以直接@bar("satori", 16)是不会输出的。当我执行foo的时候,还会先执行bar里面yield上面的内容,
# 然后执行foo代码的内容,最后执行yield下面的内容
4.关闭打开的句柄
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import
contextlib
'''
file类直接支持上下文管理器API,但另外一些表示打开句柄的对象却并不支持。
为了确保关闭句柄,要是用closing为它创建一个上下文管理器
'''
class
Door:
def
__init__(
self
):
print
(
"__init__()"
)
self
.status
=
"open"
def
close(
self
):
print
(
"close()"
)
self
.status
=
"closed"
with contextlib.closing(Door()) as door:
print
(f
"{door.status}"
)
# 先不急看结果,先来分析一下。首先contextlib.closing本身就是一个上下文管理器
'''
class closing(AbstractContextManager):
def __init__(self, thing):
self.thing = thing
def __enter__(self):
return self.thing
def __exit__(self, *exc_info):
self.thing.close()
'''
# 可以看到当我with contextlib.closing(Door()) as door的时候,直接将Door的实例对象传入了closing这个类中
# 然后enter返回了self.thing也就是我们传进去的Door的实例对象,__enter__返回self.thing交给door
# 然后执行我们的逻辑,最后__exit__函数再调用self.thing.close函数,所以我们定义的类中一定要实现close函数
# 执行结果
'''
__init__()
open
close()
'''
# 先执行__init__函数,再执行我们自己的逻辑,打印"open",最后执行close函数,将状态改为"closed"。
# 怎么证明这一点呢?
print
(door.status)
# closed
# 如果出现了异常怎么办呢?不用怕,依旧会执行close语句.
# 由于contextlib.closing的__exit__函数并没有返回布尔类型为True的值,所以最后还是会抛出异常,我们手动捕获一下
try
:
with contextlib.closing(Door()) as boy_next_door:
print
(
123
)
1
/
0
print
(
456
)
except
Exception:
pass
print
(boy_next_door.status)
'''
__init__()
123
close()
closed
'''
# 最后还是打印了"closed",所以还是执行了close()方法
5.忽略异常
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
contextlib
'''
很多情况下,忽略库产生的异常很有用,因为这个错误可能会显示期望的状态已经被实现,否则该错误就可以被忽略。
要忽略异常,最常用的办法就是利用一个try except语句。
但是在我们此刻的主题中,try except也可以被替换成contextlib.suppress(),以更显示地抑制with块中产生的异常
'''
def
foo():
print
(
123
)
1
/
0
print
(
456
)
with contextlib.suppress(ZeroDivisionError):
foo()
print
(
789
)
'''
123
'''
# 最终只输出了123,可以看到不仅1/0中下面的456没有被打印,连foo()下面的789也没有被打印
# 可以传入多个异常
with contextlib.suppress(ZeroDivisionError, BaseException, Exception):
foo()
'''
123
'''
# 出现异常之后,会将异常全部丢弃
6.重定向到输出流
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
import
contextlib
import
io
import
sys
'''
设计不当的代码可能会直接写sys.stdout或sys.stderr,而没有提供参数来配置不同的输出目标。
可以用redirect_stdout和redirect_stderr上下文管理器从这些函数中捕获输出,因为无法修改这个函数的源代码来接收新的输出参数
'''
def
func(a):
sys.stdout.write(f
"stdout :{a}"
)
sys.stderr.write(f
"stderr :{a}"
)
capture
=
io.StringIO()
'''
我们执行func本来是要往sys.stdout和sys.stderr里面写的
但这是在with语句contextlib.redirect_stdout(capture), contextlib.redirect_stderr(capture)下面,
因此可以理解往sys.stdout和sys.stderr里面写的内容就被捕获到了,然后会将捕获到的内容输入到capture里面,因为我们指定了capture
'''
with contextlib.redirect_stdout(capture), contextlib.redirect_stderr(capture):
func(
"蛤蛤蛤蛤"
)
print
(capture.getvalue())
# stdout :蛤蛤蛤蛤stderr :蛤蛤蛤蛤
'''
redirect_stdout和redirect_stderr会修改全局状态,替换sys模块中的对象,可以想象gevent里面的patch_all会将Python里面socket,ssl等都换掉。
因此要使用这两个函数,必须要注意。这些函数并不保证线程安全,所以在多线程应用中调用这些函数可能会有不确定的结果。
如果有其他希望标准输出流关联到终端设备,那么redirect_stdout和redirect_stderr将会干扰和影响那些操作。
'''
7.动态上下文管理器栈
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
39
40
41
42
43
44
45
46
47
48
49
50
import
contextlib
'''
大多数上下文管理器都一次处理一个对象,如单个文件或数据库句柄。
在这些情况下,对象是提前已知的,并且使用上下文管理器的代码可以建立这一对象上。
另外一些情况下,程序可能需要在一个上下文中创建未知数目的对象,控制流退出这个上下文时所有这些对象都要清理,ExitStack就是用来处理这些更动态的情况。
ExitStack实例会维护清理回调的一个栈数据结构,这些回调显示地填充在上下文中,在控制流退出上下文时会以逆序调用所有注册的回调。
结果类似于有多个嵌套的with语句,只不过它们是动态建立的。
'''
# 可以使用多种方法填充ExitStack,比如
@contextlib
.contextmanager
def
make_context(i):
print
(f
"{i}: entering"
)
yield
{}
print
(f
"{i}: exiting"
)
def
variable_stack(n, msg):
with contextlib.ExitStack() as stack:
for
i
in
range
(n):
stack.enter_context(make_context(i))
print
(msg)
variable_stack(
2
,
"inside stack"
)
'''
contextlib.ExitStack()相当于创建了上下文管理器栈
stack.enter_context将上下文管理器放入到栈中,注意此时已经执行了
会先输出:
0: entering
1: entering
等于是把yield之后的结果压入栈中
然后执行后面的代码,所以会打印出msg
当里面的代码执行完毕之后,会继续执行栈里面的数据,但是栈是后入先出的。i=1后入栈,所以先执行
随意最后输出:
1: exiting
0: exiting
'''
# 输出结果
''''
0: entering
1: entering
inside stack
1: exiting
0: exiting
'''
转载于:https://www.cnblogs.com/valorchang/p/11395767.html
转载请注明原文地址: https://mac.8miu.com/read-57821.html