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'{c}{name}>' for c in content)
else:
return "" % (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
hello
world
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)
in
----> 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=Noneif len(text) >max_len:
space_before= text.rfind(' ', 0, max_len) #从右向左找,rfind,对应max_len前面的第一个空格
if space_before >=0:
end=space_beforeelse:
space_after= text.find(' ', max_len) #找max_len的后面了
if space_after >=0:
end=space_afterif end is None: #没有找到,很聪明定义了一个None的开关
end =len(text)returntext[: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]:
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 =
POSITIONAL_OR_KEYWORD : max_len = 8
for name, param in tag.parameters.items():
...: print(param.kind,':', name,'=',param.default)
...:
...:
POSITIONAL_OR_KEYWORD : name =
VAR_POSITIONAL : content =
KEYWORD_ONLY : cls = None
VAR_KEYWORD : attrs =
In [25]: tag.parameters.items()
Out[25]: odict_items([('name', ), ('content', ), ('cls', ), ('attrs', )])
In [26]: tag.parameters['name']
Out[26]:
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)
in
----> 1 tag.parameters.annotation
AttributeError: 'mappingproxy' object has no attribute 'annotation'
In [31]: tag.parameters
Out[31]:
mappingproxy({'name': ,
'content': ,
'cls': ,
'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 = 可以通过定位参数和关键字参数传入的行参
VAR_POSITIONAL : content = 可以定位参数的元祖
KEYWORD_ONLY : cls = None 仅限关键字参数
VAR_KEYWORD : attrs = 关键字参数字典
inspect.signature其实还有一个bind的方法,它可以模拟实参传递的过程,如果参数不对,会报错。
In [36]: tag
Out[36]:
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]:
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)
in
----> 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]: 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)
...:
: text =
: 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]:
In [96]: picture = partial(tag,'img',cls='pic-frame')
In [97]: picture(src='wumpus.jpg')
Out[97]: ''
In [98]: picture.func()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in
----> 1 picture.func()
TypeError: tag() missing 1 required positional argument: 'name'
In [99]: picture.func
Out[99]:
In [100]: picture.args
Out[100]: ('img',)
In [101]: picture.keywords
Out[101]: {'cls': 'pic-frame'}
从partial返回的函数对象中可以看到,它的属性中含有原函数,感觉它更像一个简单的装饰器。
还有一个functoos.partialmethod函数用法于partail一样,但是用在方法上的。