4.Python3标准库--算法

mac2022-06-30  26

(一)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

最新回复(0)