流畅的Python第五章,一等函数笔记
Python中又一个名称叫一等对象,满足以下条件的程序实体:
1、在运行时创建
2、能赋值给变量或数据结构中的元素
3、能作为参数传给函数
4、能作为函数的返回结果
所以Python中,正数、字符串、字典与函数都是一等对象。
5.1把函数当做对象:
把函数当做对象,通过简单的__doc__可以输出函数的说明。
In [55]: def demo(a,b): ...: '''返回a,b''' ...: return a,b ...: In [56]: demo.__doc__ Out[56]: '返回a,b' In [57]:
通过高阶函数把函数传递进去。
def fact(n): '''returns n!''' return 1 if n < 2 else n * fact(n - 1) print(list(map(fact, range(10))))
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
fact就是传递进去的函数。
5.2高阶函数
接受函数为参数,或者把函数作为结果返回的函数为高阶函数(hight-order function)。
map,filter,reduce,包括sorted都是高阶,sorted的key可以接收函数.
按照这个定义,我们的闭包函数,装饰器函数,都可以称为高阶函数。
map,filter和reduce的现代替换品
map与filter因为列表生成式的使用,基本很多需要使用他们的地方都可以用列表生成式。
def fact(n): '''returns n!''' return 1 if n < 2 else n * fact(n - 1) ''' 接收函数为参数,或者把函数作为结果返回的函数是高阶函数。 所以,map,filter,reduce,sorted(因为key能接收函数) ''' print(fact(10)) print(list(map(fact, range(10)))) # map需要传入函数fact print([fact(i) for i in range(10)]) # 直接列表生成式,每个参数直接使用了函数fact,产生的返回值放入列表。 print(list(map(fact, filter(lambda n : n % 2, range(10))))) # 在map的函数后面的可迭代对象进行了filter的过滤,需要能被2整除 # filter第一个只返回:后面条件成立的数值。 def condition(n): if n % 2 != 0: return n print(list(map(fact, filter(condition, range(10))))) print([fact(i) for i in range(10) if i % 2]) # 列表生成式第一个写函数或者参数,第二个写循环体,第三个写条件。 # 书中直接写if i % 2应该就是! % 2不能为0,这个写法更加精简。所以以后条件如果返回只要是真,写入就可以,生成的时候就会执行。 print([fact(i) for i in range(10) if i % 2 !=0]) # 如果我写,我一半写成这样。
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第五章/高级函数.py 3628800 [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880] [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880] [1, 6, 120, 5040, 362880] [1, 6, 120, 5040, 362880] [1, 6, 120, 5040, 362880] Process finished with exit code 0
reduce现在用的很少,一般用在求和上面。
from functools import reduce from operator import add def s_add(x, y): return x + y print(reduce(add, range(100))) print(reduce(s_add, range(100))) print(sum(range(100)))
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第五章/t5.2.py 4950 4950 4950 Process finished with exit code 0
归约函数all,any是蛮好的函数.
all(iterable) 里面的可迭代对象全部为真返回真。(有点像and)
any(iterable) 有一个真就返回真。 (有点像or)
a1 = '1232434' a2 = [1, 2, 3, 0] a3 = {'name': 'sidian', 'age': None} a4 = (None, 9, 8) print(a1, all(a1), any(a1)) print(a2, all(a2), any(a2)) print(a3, all(a3), any(a3)) print(a4, all(a4), any(a4))
1232434 True True [1, 2, 3, 0] False True {'name': 'sidian', 'age': None} True True (None, 9, 8) False True
5.3匿名函数
lambda是Python表达式内创建匿名函数。然而Python简单的语法限制了lambda函数的定义题只能使用纯表达式。换句话说,lambda函数的定义体中不能赋值,也不能使用while和try等Python语句。
lambda(init:return),lambda函数通过分号相隔,前面是输入参数,后面是返回参数。
lambda语法是语法糖,更def一样,会创建可以调用的函数对象。
5.4可调用对象
可调用对象就是能用()的对像(里面有__call__的方法),可以用callable来测试是否是可调用对象,Python数据模型里面有7种可调用对象。
1、用户定义的函数
比如使用def或者lambda创建的对象。
2、内置函数
使用C语言显示的函数,比如len或time.strftime
3、内置方法
使用C语言实现的方法,比如dict.get
4、方法
在类的实体中定义的函数
5、类
在实例化类的使用,首先调用的是__call__,然后调用__new__创建对象,最后__init__来初始化对象。
6、类的实例
在类里面定义了__call__,那么实例就可以成为调用对象。
7、生成器
调用生成器函数可以返回一个生成器对象。
5.5 用户定义的可调用类型。
通过类里面给予定义函数__call__
import random class BingCage: def __init__(self, items): self._items = list(items) # 设置成私有变量 random.shuffle(self._items) def pick(self): try: return self._items.pop() except IndexError: raise LookupError('pick from empty BingoCage') def __call__(self, *args, **kwargs): # 让对象拥有call方法,能直接被调用 return self.pick() bingo = BingCage('abc') for i in range(5): try: print(bingo.pick()) except LookupError as e: print(e)
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第五章/t_5.5.py b c a pick from empty BingoCage pick from empty BingoCage Process finished with exit code 0
5.6 函数内省
函数有很多属性,我们通过dir来查看。
dir(lambda x : x[2]) Out[57]: ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
首先说一个,函数对象有__dict__属性,所以可以通过.或者setattr来进行属性赋值,这个我以前还真不知道。(但一般很少对函数对象进行属性赋值)
def demo(): ...: ... ...: In [63]: demo.abc = 'abc' In [64]: demo.__dict__ Out[64]: {'abc': 'abc'} In [65]: setattr(demo,'name','sidian') In [66]: demo.name Out[66]: 'sidian' In [67]: demo.__dict__ Out[67]: {'abc': 'abc', 'name': 'sidian'} In [68]:
下面列出函数独有,但对象没有的属性,我准备跟类也对比一下。
class C: ... def func(): ... c = C() print(set(dir(C)) - set(dir(func))) print(set(dir(func)) - set(dir(C))) print(set(dir(c)) - set(dir(func))) print(set(dir(func)) - set(dir(c)))
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第五章/t_5.6.py {'__weakref__'} {'__annotations__', '__get__', '__globals__', '__call__', '__name__', '__qualname__', '__code__', '__kwdefaults__', '__defaults__', '__closure__'} {'__weakref__'} {'__annotations__', '__get__', '__globals__', '__call__', '__name__', '__qualname__', '__code__', '__kwdefaults__', '__defaults__', '__closure__'} Hello sidian Process finished with exit code 0
从中可以看出来,对象或者类比函数对了一个属性__weakref__,我查了一些资料好像是关于垃圾回收相关,具体资料很少。
https://stackoverflow.com/questions/36787603/what-exactly-is-weakref-in-python这个链接有一些英文的答案。
但函数比对象多了很多属性。
__closure_是闭包函数里面取值的。
__kwdefaults__是查看*后面的关键字默认参数
5.7从实际参数到仅限关键字参数。
def tag(name, *content, cls=None, **attrs): '''生成一个或多个HTML标签''' if cls is not None: attrs['class'] = cls if attrs: attr_str = ''.join(' %s="%s" ' % (attr, value) for attr, value in sorted(attrs.items())) else: attr_str = '' if content: return '\n'.join(f'<{name}{attr_str}>{c}</{name}>' for c in content) else: return "<%s%s />" % (name, attr_str) if __name__ == '__main__': print(tag('br')) print(tag('p', 'hello', 'world')) print(tag('p', 'hello', 'world', cls='sidebar')) print(tag(content='testing', name='img')) print(tag(**{'name': 'img', 'title': 'Sunset Boulevard', "src": 'sunset.jpg', 'cls': 'framed'}))
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第五章/t5-7.py <br /> <p>hello</p> <p>world</p> <p class="sidebar" >hello</p> <p class="sidebar" >world</p> <img content="testing" /> <img class="framed" src="sunset.jpg" title="Sunset Boulevard" /> Process finished with exit code 0
上面的代码还是比较简单的,就是cls成为了传参关键字参数,如果不用cls关键字传参,它永远都是默认值None
如果不需要*后面的参数,可以直接写一个*
In [836]: def f(a, *, b): ...: return a,b ...: ...: In [837]: f(1,b=2) Out[837]: (1, 2) In [838]: f(1, 2) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-838-c9c271413adf> in <module> ----> 1 f(1, 2) TypeError: f() takes 1 positional argument but 2 were given
上面的代码,b成了关键字必填写参数。
5.8获取关于参数信息。
def clip(text, max_len=8): ''' :param text: 在max_len前面或后面的第一个空格处截断文本 ''' end = None if len(text) > max_len: space_before = text.rfind(' ', 0, max_len) # 从右向左找,rfind,对应max_len前面的第一个空格 if space_before >= 0: end = space_before else: space_after = text.find(' ', max_len) # 找max_len的后面了 if space_after >= 0: end = space_after if end is None: # 没有找到,很聪明定义了一个None的开关 end = len(text) return text[:end].rstrip() string = '进入空格该文件的目录, 空格运行后 没有相关提示,报其他错了。' print(clip(string)) print(clip.__code__.co_varnames)
这个一个截取字段串的代码:
('text', 'max_len', 'end', 'space_before', 'space_after') In [6]: from t_5_8 import clip In [7]: clip.__code__.co_varnames Out[7]: ('text', 'max_len', 'end', 'space_before', 'space_after') In [8]: clip.__code__.co_argcount Out[8]: 2 In [9]: clip.__defaults__ Out[9]: (8,) In [10]: clip.__code__.co_name Out[10]: 'clip' In [11]:
这是通过一些函数方法取出来的参数,感觉不是很好,后面通过inspect 来取出函数的参数。
In [16]: from inspect import signature In [17]: sig = signature(clip) In [18]: sig Out[18]: <Signature (text, max_len=8)> In [19]: str(sig) Out[19]: '(text, max_len=8)' In [20]: dir(sig) Out[20]: ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '_bind', '_bound_arguments_cls', '_hash_basis', '_parameter_cls', '_parameters', '_return_annotation', 'bind', 'bind_partial', 'empty', 'from_builtin', 'from_callable', 'from_function', 'parameters', 'replace', 'return_annotation'] In [21]: for name, param in sig.parameters.items(): ...: print(param.kind,':', name,'=',param.default) ...: POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'> POSITIONAL_OR_KEYWORD : max_len = 8
for name, param in tag.parameters.items(): ...: print(param.kind,':', name,'=',param.default) ...: ...: POSITIONAL_OR_KEYWORD : name = <class 'inspect._empty'> VAR_POSITIONAL : content = <class 'inspect._empty'> KEYWORD_ONLY : cls = None VAR_KEYWORD : attrs = <class 'inspect._empty'> In [25]: tag.parameters.items() Out[25]: odict_items([('name', <Parameter "name">), ('content', <Parameter "*content">), ('cls', <Parameter "cls=None">), ('attrs', <Parameter "**attrs">)]) In [26]: tag.parameters['name'] Out[26]: <Parameter "name"> In [27]: dir(tag.parameters['name']) Out[27]: ['KEYWORD_ONLY', 'POSITIONAL_ONLY', 'POSITIONAL_OR_KEYWORD', 'VAR_KEYWORD', 'VAR_POSITIONAL', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '_annotation', '_default', '_kind', '_name', 'annotation', 'default', 'empty', 'kind', 'name', 'replace'] In [28]: tag.parameters['name'].name Out[28]: 'name' In [29]: tag.parameters['content'].name Out[29]: 'content' In [30]: tag.parameters.annotation --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-30-ca6845388021> in <module> ----> 1 tag.parameters.annotation AttributeError: 'mappingproxy' object has no attribute 'annotation' In [31]: tag.parameters Out[31]: mappingproxy({'name': <Parameter "name">, 'content': <Parameter "*content">, 'cls': <Parameter "cls=None">, 'attrs': <Parameter "**attrs">}) In [32]: tag.parameters['content'].annotation Out[32]: inspect._empty
从inspect.signature对象中我们可以方便的看到函数的行参,通过str可以
从对象的parameters字典中,里面的value有下面这些普通的属性。
'annotation',
'default',
'empty',
'kind',
'name',
'replace'
通过调用kind属性获取不同的值。
POSITIONAL_OR_KEYWORD : name = <class 'inspect._empty'> 可以通过定位参数和关键字参数传入的行参
VAR_POSITIONAL : content = <class 'inspect._empty'> 可以定位参数的元祖
KEYWORD_ONLY : cls = None 仅限关键字参数
VAR_KEYWORD : attrs = <class 'inspect._empty'> 关键字参数字典
inspect.signature其实还有一个bind的方法,它可以模拟实参传递的过程,如果参数不对,会报错。
In [36]: tag Out[36]: <Signature (name, *content, cls=None, **attrs)> In [37]: my_tag = {'name': 'img', 'title': 'Sunset Boulevard', "src": 'sunset.jpg', 'cls': 'framed'} In [38]: bound_args = tag.bind(**my_tag) In [39]: bound_args Out[39]: <BoundArguments (name='img', cls='framed', attrs={'title': 'Sunset Boulevard', 'src': 'sunset.jpg'})> In [40]: for name, value in bound_args.arguments.items(): ...: print(name,'=',value) ...: name = img cls = framed attrs = {'title': 'Sunset Boulevard', 'src': 'sunset.jpg'} In [41]: del my_tag['name'] In [42]: bound_args = tag.bind(**my_tag) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-42-137b62789a71> in <module> ----> 1 bound_args = tag.bind(**my_tag) /usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/inspect.py in bind(*args, **kwargs) 3013 if the passed arguments can not be bound. 3014 """ -> 3015 return args[0]._bind(args[1:], kwargs) 3016 3017 def bind_partial(*args, **kwargs): /usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/inspect.py in _bind(self, args, kwargs, partial) 2928 msg = 'missing a required argument: {arg!r}' 2929 msg = msg.format(arg=param.name) -> 2930 raise TypeError(msg) from None 2931 else: 2932 # We have a positional argument to process TypeError: missing a required argument: 'name'
上面通过对象的bind属性,模拟传参的的过程,当参数中缺少必要参数时,报错。
5.9 函数注解。
def clip(text:str, max_len:'int' =8) -> str: ''' :param text: 在max_len前面或后面的第一个空格处截断文本 ''' end = None if len(text) > max_len: space_before = text.rfind(' ', 0, max_len) # 从右向左找,rfind,对应max_len前面的第一个空格 if space_before >= 0: end = space_before else: space_after = text.find(' ', max_len) # 找max_len的后面了 if space_after >= 0: end = space_after if end is None: # 没有找到,很聪明定义了一个None的开关 end = len(text) return text[:end].rstrip()
In [63]: from t_5_8 import clip In [64]: clip.__annotations__ Out[64]: {'text': str, 'max_len': 'int', 'return': str} In [65]:
还是蛮有意思的,这样的写法感觉非常便于维护。
在参数后面添加:后面就是注释,然后在)于:之间通过->和一个表达式,添加返回的内容形式。
当然你也可以从inspect.signature获取annotation
In [65]: clip = signature(clip) In [66]: clip Out[66]: <Signature (text: str, max_len: 'int' = 8) -> str> In [67]: clip.return_annotation Out[67]: str In [68]: str(clip) Out[68]: "(text: str, max_len: 'int' = 8) -> str" In [74]: for param in sig.parameters.values(): ...: note = repr(param.annotation).ljust(13) ...: print(note, ':', param.name, '=', param.default) ...: <class 'inspect._empty'> : text = <class 'inspect._empty'> <class 'inspect._empty'> : max_len = 8
5.10 支持函数式编程的包
主要讲了一些operator以及functools里面的一些实用模块,确实非常实用。
from functools import reduce from operator import mul def fact0(n): return reduce(lambda a, b: a* b, range(1, n)) def fact1(n): return reduce(mul, range(1, n)) print(fact0(100), fact1(100),sep='\n')
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第五章/t_5_10.py 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000
operator里面会让你就连写lambda的机会都没有。
from functools import reduce from operator import mul from operator import itemgetter def fact0(n): return reduce(lambda a, b: a* b, range(1, n)) def fact1(n): return reduce(mul, range(1, n)) print(fact0(100), fact1(100),sep='\n') print() metro_data = [ ('Tokyo', 'JP', 36.933, (35.33453, 13.93434)), ('Delhi NCR', 'IN', 21.935, (28.34345345, 88.123523)), ('Mexico City', 'MX', 20.142, (19.4546456, -99.34132343)) ] for city in sorted(metro_data, key=itemgetter(1)): # 从对象中[]取出1号参数 print(city) print() cc_name = itemgetter(1,0) # 从一个对象中通过[]取出里面的参数 for city in metro_data: print(cc_name(city))
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第五章/t_5_10.py 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000 ('Delhi NCR', 'IN', 21.935, (28.34345345, 88.123523)) ('Tokyo', 'JP', 36.933, (35.33453, 13.93434)) ('Mexico City', 'MX', 20.142, (19.4546456, -99.34132343)) ('JP', 'Tokyo') ('IN', 'Delhi NCR') ('MX', 'Mexico City') Process finished with exit code 0
itemgetter也是不给你任何机会写lambda
其实itemgetter(1) 跟lamdba x:x[1]一样,当然itrmgetter(1, 0 )于lambda x: (x[0],x[1])效果也一样。
还有一个attrgetter效果其实也差不多,只不过这个是通过.来获取属性。
from operator import attrgetter LatLong = namedtuple('LatLong', 'lat long') Metropolis = namedtuple('Metropolis', 'name cc pop coord') metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long)) for name, cc, pop, (lat, long) in metro_data] print(metro_areas[0]) print(metro_areas[0].coord.lat) # for city in sorted(metro_areas, key=attrgetter('coord.lat')): # 通过对象里面的coord对象的lat进行排序 for city in sorted(metro_areas, key= lambda x: x.coord.lat): # 效果一样。 print(city)
本节还介绍了一个methodcaller的函数,有点意思。
它可以把对象的方法变成函数
In [82]: from operator import methodcaller In [83]: upper_call = methodcaller('upper') In [84]: upper_call('sdfghjk') Out[84]: 'SDFGHJK' In [85]: replcae_call = methodcaller('replace', ' ', '_') In [86]: replcae_call('123 456 789') Out[86]: '123_456_789'
非常有意思的一个工具。
5.10.2实用functools.partial冻结参数。
In [87]: from operator import mul In [88]: from functools import partial In [89]: triple = partial(mul,3) In [90]: triple(7) Out[90]: 21 In [91]: [triple(i) for i in range(10)] Out[91]: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
非常实用的一个函数,返回的也是一个函数,但这个函数已经带有默认值了,默认值就是你后面的参数。我尝试把默认值填满试试。
In [92]: triple = partial(mul,1,2) In [93]: triple() Out[93]: 2
果然可以
In [94]: from t5_7 import tag In [95]: tag Out[95]: <function t5_7.tag(name, *content, cls=None, **attrs)> In [96]: picture = partial(tag,'img',cls='pic-frame') In [97]: picture(src='wumpus.jpg') Out[97]: '<img class="pic-frame" src="wumpus.jpg" />' In [98]: picture.func() --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-98-c94e1f1add40> in <module> ----> 1 picture.func() TypeError: tag() missing 1 required positional argument: 'name' In [99]: picture.func Out[99]: <function t5_7.tag(name, *content, cls=None, **attrs)> In [100]: picture.args Out[100]: ('img',) In [101]: picture.keywords Out[101]: {'cls': 'pic-frame'}
从partial返回的函数对象中可以看到,它的属性中含有原函数,感觉它更像一个简单的装饰器。
还有一个functoos.partialmethod函数用法于partail一样,但是用在方法上的。