4.Python3标准库--算法
Posted traditional
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了4.Python3标准库--算法相关的知识,希望对你有一定的参考价值。
(一)functools:管理函数的工具
import functools ‘‘‘ functools模块提供了一些工具来管理或扩展和其他callable对象,从而不必完全重写 ‘‘‘
1.修饰符
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")的话,肯定会报错的 ‘‘‘
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
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
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)调用的话,会自动将实例传进去。 ‘‘‘
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.比较
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__()和另外一个富比较方法的实现,这个修饰符会自动增加其余的方法。 ‘‘‘
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.缓存
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(" 第二次调用") 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(" 第三次调用") 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
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.泛型函数
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.合并和分解迭代器
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(" ", 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.转换输入
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.生成新值
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.过滤
以上是关于4.Python3标准库--算法的主要内容,如果未能解决你的问题,请参考以下文章
使用标准库Ruby将数据标记到Elasticsearch批量中