Python 一等函数

在 Python 中,函数是一等对象。编程语言理论家把“一等对象”定义为满

足下述条件的程序实体:

  • 在运行时创建

  • 能赋值给变量或数据结构中的元素

  • 能作为参数传给函数

  • 能作为函数的返回结果

把函数视作对象

  Python 函数是对象。这里我们创建了一个函数,然后调用它,读取它的 __doc__ 属性,并且确定函数对象本身是 function 类的实例。

 1 #创建一个函数,只有函数在调用的时候才会运行
 2 def factorial(n):
 3     '''returns n!'''
 4     return 1 if n < 2 else n * factorial(n-1)
 5 
 6 #factorial(42)是函数function的实例
 7 print(factorial(42))
 8 print(type(factorial))
 9 
10 #函数众多属性中的其中一个~
11 print(factorial.__doc__)

以上代码执行的结果为:

1405006117752879898543142606244511569936384000000000
<class 'function'>
returns n!

  展示了函数对象的“一等”本性。我们可以把 factorial 函数赋值给变量 fact,然后通过变量名调用。我们还能把它作为参数传给map 函数。map 函数返回一个可迭代对象,里面的元素是把第一个参数(一个函数)应用到第二个参数(一个可迭代对象,这里是range(11))中各个元素上得到的结果。

? 通过别的名称使用函数,再把函数作为参数传递

 1 #创建一个函数,只有函数在调用的时候才会运行
 2 def factorial(n):
 3     '''returns n!'''
 4     return 1 if n < 2 else n * factorial(n-1)
 5 
 6 fact = factorial
 7 print(fact)
 8 
 9 map_fact = map(factorial, range(11))
10 print('map fact:', map_fact)
11 print(list(map_fact))

以上代码执行的结果为:

<function factorial at 0x1007a2e18>
map fact: <map object at 0x101c452e8>
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

 

高阶函数

  接受函数为参数,或者把函数作为结果返回的函数是高阶函数(higherorderfunction)。map 函数就是一例。此外,内函数 sorted 也是:可选的 key 参数用于提供一个函数,它会应用到各个元素上进行排序。

举个? 根据单词的长度排序一个列表

>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted_fruits = sorted(fruits, key=len)
>>> print(sorted_fruits)
['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']

根据反向拼写给一个单词列表排序

>>> def reverse(word):
...     return word[::-1]
... 
>>> reverse('testing')
'gnitset'
>>> reverse(fruits)
['banana', 'raspberry', 'cherry', 'apple', 'fig', 'strawberry']
>>> fruits
['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=reverse)
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

 函数式语言通常会提供 map、filter 和 reduce 三个高阶函数(有时使用不同的名称)。在 Python 3 中,map 和 filter 还是内置函数,但是由于引入了列表推导和生成器表达式,它们变得没那么重要了。列表推导或生成器表达式具有 map 和 filter 两个函数的功能,而且更易于阅读,如示例:

>>> list(map(lambda x:x*x, range(6)))
[0, 1, 4, 9, 16, 25]
>>> [x*x for x in range(6)]
[0, 1, 4, 9, 16, 25]
>>> list(map(lambda x:x*x, filter(lambda x:x>5, range(10))))
[36, 49, 64, 81]
>>> [x*x for x in range(10) if x >5]
[36, 49, 64, 81]

  使用 reduce 和 sum 计算 0~99 之和

>>> from functools import reduce
>>> reduce(lambda x,y: x+y, range(100))
4950
>>> sum(range(100))
4950

all 和 any 也是内置的归约函数

all(iterable)

  如果 iterable 的每个元素都是真值,返回 True;all([]) 返回True

any(iterable)

  只要 iterable 中有元素是真值,就返回 True;any([]) 返回False

 

匿名函数

  lambda 关键字在 Python 表达式内创建匿名函数。然而,Python 简单的句法限制了 lambda 函数的定义体只能使用纯表达式。换句话说,lambda 函数的定义体中不能赋值,也不能使用 while和 try 等 Python 语句。

举个? 使用 lambda 表达式反转拼写,然后依此给单词列表排序

>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=lambda word: word[::-1])
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

如果使用 lambda 表达式导致一段代码难以理解,Fredrik Lundh 建议像下面这样重构

  1. 编写注释 ,说明lambda表达式的作用

  2. 研究一会儿注释,并找出一个名称来概括注释

  3. 把lambda表达式转成def语句,使用那个名称来定义函数

  4. 删除注释

可调用对象

  除了用户定义的函数,调用运算符(即 ())还可以应用到其他对象上。如果想判断对象能否调用,可以使用内置的 callable() 函数。Python 数据模型文档列出了 7 种可调用对象。

用户定义的函数

  使用 def 语句或 lambda 表达式创建

内置函数

  使用 C 语言(CPython)实现的函数,如 len 或 time.strftime

内置方法

  使用 C 语言实现的方法,如 dict.get

方法 

  在类的定义体中定义的函数

  调用类时会运行类的 __new__ 方法创建一个实例,然后运行__init__ 方法,初始化实例,最后把实例返回给调用方。因为 Python没有 new 运算符,所以调用类相当于调用函数。(通常,调用类会创建那个类的实例,不过覆盖 __new__ 方法的话,也可能出现其他行为

类的实例

  如果类定义了 __call__ 方法,那么它的实例可以作为函数调用

生成器函数

  使用 yield 关键字的函数或方法。调用生成器函数返回的是生成器对象

举个? 判断对象是否可以被调用

>>> abs, str, 123
(<built-in function abs>, <class 'str'>, 123)
>>> [callable(s) for s in [abs, str, 123]]
[True, True, False]

 

用户定义的可调用类型

  不仅 Python 函数是真正的对象,任何 Python 对象都可以表现得像函数。为此,只需实现实例方法 __call__。

 1 import random
 2 
 3 
 4 class BingoCage:
 5 
 6     def __init__(self, item):                   #接收一个可迭代的对象,转成列表,以防止意外
 7         self._item = list(item)
 8         random.shuffle(self._item)
 9 
10     def pick(self):
11         try:
12             return self._item.pop()             #每调用一次会会从删除一个值
13         except IndexError:                      #列表中没有值会报索引错误
14             raise LookupError('pick from empty BingoCage')
15 
16     def __call__(self):
17         return self.pick()                      #支持函数作为实例调用
18 
19 
20 
21 bingo = BingoCage(range(3))
22 print(bingo.pick())               #普通的调用
23 print(bingo())                    #函数作为实例调用
24 print(callable(bingo))

以上代码执行的结果为:

2
0
True

  实现 __call__ 方法的类是创建函数类对象的简便方式,此时必须在内部维护一个状态,让它在调用之间可用,例如 BingoCage 中的剩余元素。装饰器就是这样。装饰器必须是函数,而且有时要在多次调用之间“记住”某些事 [ 例如备忘(memoization),即缓存消耗大的计算结果,供后面使用 ]。

 

函数内省 

  除了 __doc__,函数对象还有很多属性。使用 dir 函数可以探知factorial 具有下述属性:

>>> def factorial():
...     pass
... 
>>> dir(factorial)
['__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__']

  下面重点说明函数专有而用户定义的一般对象没有的属性。计算两个属性集合的差集便能得到函数专有属性列表

1 class C: pass               #声明一个类
2 obj = C()                   #实例化类
3 
4 def func(): pass            #声明一个函数对象
5 
6 print(sorted(set(dir(func)) - set(dir(obj))))   #打印函数和类实例化类对象中的差集

以上代码执行得结果为:

['__annotations__', '__call__', '__closure__', '__code__', '__defaults__',

'__get__', '__globals__', '__kwdefaults__', '__name__', '__qualname__']

用户定义的函数的属性

名称类型说明
__annotations__dict参数返回值和的注释信息
__call__method-wrapper实现()运算符;即可调用对象协议
__closure__tuple函数闭包,即自有变量的绑定(通常是None)
__code__code编译成字节码的函数元数据和函数定义体
__defaults__tuple形式参数的默认值
__get__method-wrapper实现只读描述符协议
__globals__dict函数所在模块中的全局变量
__kwdefaults__dict仅限关键字形式参数的默认值
__name__str函数名称
__qualname__str函数的限定名称,如Random.choice

 

 

 

 

 

 

 

 从定位参数到仅限关键字参数 

  Python 最好的特性之一是提供了极为灵活的参数处理机制,而且 Python3 进一步提供了仅限关键字参数(keyword-only argument)。与之密切相关的是,调用函数时使用 * 和 **“展开”可迭代对象,映射到单个参数。

  tag 函数用于生成 HTML 标签;使用名为 cls 的关键字参数传入“class”属性,这是一种变通方法,因为“ class”是 Python的关键字

 1 def tag(name, *content, cls=None, **attrs):
 2     if cls is not None:
 3         attrs['class'] = cls
 4     if attrs:
 5         attr_str = ''.join(' %s="%s"' % (attr, value)
 6                            for attr, value in sorted(attrs.items()))
 7     else:
 8         attr_str = ''
 9 
10     if content:
11         return '  '.join('<%s%s>%s</%s>' %
12                          (name, attr_str, c, name) for c in content)
13     else:
14         return '<%s%s />' % (name, attr_str)
15 
16 print(tag('br'))
17 
18 print('-'*20)
19 print('位置参数:\n', tag('p', 'hello'))
20 
21 print('-'*20)
22 print('位置参数,通过函数*接收到一个元祖:\n', tag('p', 'hello', 'world'))
23 
24 print('-'*20)
25 print('位置参数和关键字参数:\n', tag('p', 'hello', id=33))
26 
27 print('-'*20)
28 print('cls的关键字参数传参数:\n', tag('p', 'hello', 'world', cls='sidebar'))
29 
30 print('-'*20)
31 print('两个关键字参数,content传递给函数中的content,name传递给name:\n', tag(content='testing', name="img"))
32 
33 print('-'*20)
34 #在 my_tag 前面加上 **,字典中的所有元素作为单个参数传入,同名键会绑定到对应的具名参数上,余下的则被 **attrs 捕获
35 my_tag = {'name': 'img', 'title': 'Sunset Boulevard', 'src': 'sunset.jpg', 'cls': 'framed'}
36 print('传递一个字典进去,通过**拆分成关键词参数:\n', my_tag)

以上代码执行的结果为: 

<br />
--------------------
位置参数:
 <p>hello</p>
--------------------
位置参数,通过函数*接收到一个元祖:
 <p>hello</p>  <p>world</p>
--------------------
位置参数和关键字参数:
 <p id="33">hello</p>
--------------------
cls的关键字参数传参数:
 <p class="sidebar">hello</p>  <p class="sidebar">world</p>
--------------------
两个关键字参数,content传递给函数中的content,name传递给name:
 <img content="testing" />
--------------------
传递一个字典进去,通过**拆分成关键词参数:
 {'name': 'img', 'title': 'Sunset Boulevard', 'src': 'sunset.jpg', 'cls': 'framed'}

注意:

  仅限关键字参数是 Python 3 新增的特性。在示例 ,cls 参数只能通过关键字参数指定,它一定不会捕获未命名的定位参数。定义函数时若想指定仅限关键字参数,要把它们放到前面有 * 的参数后面。如果不想支持数量不定的定位参数,但是想支持仅限关键字参数,在签名中放一个 *,如下所示:

>>> def f(a, *, b):
...     return a, b
... 
>>> f(1, b=2)
(1, 2)

注意,仅限关键字参数不一定要有默认值,可以像上例中 b 那样,强制必须传入实参

 

函数注解

  Python 3 提供了一种句法,用于为函数声明中的参数和返回值附加元数据。

? 有注释的clip函数

 1 def clip(text:str, max_len:'int > 0'=80) -> str:        #函数中给每个参数都设置了传递的参数的信息,包括函数最终的返回结果的类型
 2     """
 3     :param text:
 4     :param max_len:前面或者后面的第一个空格处截取文本
 5     :return:
 6     """
 7     end = None
 8     if len(text) > max_len:                            #获取传入字符串的长度并与传递的要查找空格最大长度做判断
 9         space_before = text.rfind(' ', 0, max_len)     #从开头查找到max_len的长度截止,如果能找到则返回空格在字符串中的位置,否则则返回-1
10         if space_before >= 0:
11             end = space_before                         #查找空格的索引位置等于找到最终函数的切片的最终位置
12         else:
13             space_after = text.rfind(' ', max_len)     #如果从开头没有找到空格,则从max_len的长度开始往后继续查找
14             if space_after >= 0:
15                 end = space_after
16 
17     if end is None:
18         end = len(text)
19 
20     return text[:end].rsplit()                         #返回从查找截止到空格位置的所有字符串
21 
22 
23 s = 'testing testing'
24 result = clip(s, 10)
25 print(result)

  函数声明中的各个参数可以在 : 之后增加注解表达式。如果参数有默认值,注解放在参数名和 = 号之间。如果想注解返回值,在 ) 和函数声明末尾的 : 之间添加 -> 和一个表达式。那个表达式可以是任何类型。注解中最常用的类型是类(如 str 或 int)和字符串(如 'int >0')

 注解不会做任何处理,只是存储在函数的 __annotations__ 属性(一个字典)中:

>>> from clip_annot import clip
>>> clip.__annotations__
{'text': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}

  'return' 键保存的是返回值注解,即示例中函数声明里以 -> 标记的部分。

  Python 对注解所做的唯一的事情是,把它们存储在函数的__annotations__ 属性里。仅此而已,Python 不做检查、不做强制、不做验证,什么操作都不做。换句话说,注解对 Python 解释器没有任何意义。注解只是元数据,可以供 IDE、框架和装饰器等工具使用。标准库中还没有什么会用到这些元数据,唯有inspect.signature() 函数知道怎么提取注解,如示例所示。

 1 from inspect import signature
 2 
 3 
 4 def clip(text:str, max_len:'int > 0'=80) -> str:        #函数中给每个参数都设置了传递的参数的信息,包括函数最终的返回结果的类型
 5     """
 6     :param text:
 7     :param max_len:前面或者后面的第一个空格处截取文本
 8     :return:
 9     """
10     end = None
11     if len(text) > max_len:                            #获取传入字符串的长度并与传递的要查找空格最大长度做判断
12         space_before = text.rfind(' ', 0, max_len)     #从开头查找到max_len的长度截止,如果能找到则返回空格在字符串中的位置,否则则返回-1
13         if space_before >= 0:
14             end = space_before                         #查找空格的索引位置等于找到最终函数的切片的最终位置
15         else:
16             space_after = text.rfind(' ', max_len)     #如果从开头没有找到空格,则从max_len的长度开始往后继续查找
17             if space_after >= 0:
18                 end = space_after
19 
20     if end is None:
21         end = len(text)
22 
23     return text[:end].rsplit()                         #返回从查找截止到空格位置的所有字符串
24 
25 sig = signature(clip)
26 print(sig.return_annotation)
27 
28 for param in sig.parameters.values():
29     note = repr(param.annotation).ljust(13)
30     print(note, ':', param.name, '=', param.default)

以上代码执行的结果为:

<class 'str'>
<class 'str'> : text = <class 'inspect._empty'>
'int > 0'     : max_len = 80

  signature 函数返回一个 Signature 对象,它有一个return_annotation 属性和一个 parameters 属性,后者是一个字典,把参数名映射到 Parameter 对象上。每个 Parameter 对象自己也有 annotation 属性。

 

支持函数式编程的包

operator模块

  在函数式编程中,经常需要把算术运算符当作函数使用。例如,不使用递归计算阶乘。求和可以使用 sum 函数,但是求积则没有这样的函数。我们可以使用 reduce 函数,但是需要一个函数计算序列中两个元素之积。展示如何使用 lambda 表达式解决这个问题

>>> from functools import reduce
>>> reduce(lambda x,y:x*y, range(1,11))
3628800

  operator 模块为多个算术运算符提供了对应的函数,从而避免编写lambda x, y: x*y 这种平凡的匿名函数

>>> from operator import mul
>>> from functools import reduce
>>> reduce(mul, range(1, 11))
3628800

  operator 模块中还有一类函数,能替代从序列中取出元素或读取对象属性的 lambda 表达式:因此,itemgetter 和 attrgetter 其实会自行构建函数。

? 演示使用itemgetter排序一个元祖列表

>>> from operator import itemgetter
>>> metro_data = [
...     ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
...     ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
...     ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
...     ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
...     ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
...     ]
>>> for city in sorted(metro_data, key=itemgetter(1)):
...     print(city)
... 
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833))
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))
('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
('Mexico City', 'MX', 20.142, (19.433333, -99.133333))
('New York-Newark', 'US', 20.104, (40.808611, -74.020386))

  上面代码中的itemgetter(1)等同于lambda fields:fields[1]的效果,也就是每个元素中的第一个索引进行排序

 如果把多个参数传给 itemgetter,它构建的函数会返回提取的值构成的元组:

>>> cc_name = itemgetter(1, 0)
>>> for city in metro_data:
...     print(cc_name(city))
... 
('JP', 'Tokyo')
('IN', 'Delhi NCR')
('MX', 'Mexico City')
('US', 'New York-Newark')
('BR', 'Sao Paulo')

  attrgetter 与 itemgetter 作用类似,它创建的函数根据名称提取对象的属性。如果把多个属性名传给 attrgetter,它也会返回提取的值构成的元组。此外,如果参数名中包含 .(点号),attrgetter 会深入嵌套对象,获取指定的属性。这些行为如示例 5-24 所示。这个控制台会话不短,因为我们要构建一个嵌套结构,这样才能展示attrgetter 如何处理包含点号的属性名。

?  定义一个 namedtuple,名为 metro_data,演示使用 attrgetter 处理它

 1 from collections import namedtuple
 2 from operator import attrgetter
 3 
 4 
 5 LatLong = namedtuple('LatLong', 'lat long')
 6 Metropolis = namedtuple('Metropolis', 'name cc pop coord')
 7 metro_data = [
 8     ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
 9     ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
10     ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
11     ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
12     ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
13 ]
14 
15 metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long))
16                for name, cc, pop, (lat, long) in metro_data]
17 
18 print(metro_areas[0])
19 print(metro_areas[0].coord.lat)
20 
21 name_lat = attrgetter('name', 'coord.lat')          #等同于调用Metropolis中的name和coord字段。coord字段对应LatLong,然后在去LatLong中的lat
22 
23 for city in sorted(metro_areas, key=attrgetter('coord.lat')):
24     print(name_lat(city)) #等同于Metropolis中获取每一个name的字段和coord.lat的字段

以上代码执行的结果为:

Metropolis(name='Tokyo', cc='JP', pop=36.933, coord=LatLong(lat=35.689722, long=139.691667))
35.689722
('Sao Paulo', -23.547778)
('Mexico City', 19.433333)
('Delhi NCR', 28.613889)
('Tokyo', 35.689722)
('New York-Newark', 40.808611)

methodcaller 使用示例:

>>> from operator import methodcaller
>>> s = 'The time has come....'
>>> upcase = methodcaller('upper')
>>> upcase(s)
'THE TIME HAS COME....'
>>> hiphenate = methodcaller('replace', ' ', '-')
>>> hiphenate(s)
'The-time-has-come....'

使用functools.partial冻结参数

  functools.partial 这个高阶函数用于部分应用一个函数。部分应用是指,基于一个函数创建一个新的可调用对象,把原函数的某些参数固定。使用这个函数可以把接受一个或多个参数的函数改编成需要回调的API,这样参数更少。

使用 partial 把一个两参数函数改编成需要单参数的可调用对象

from functools import partial
from operator import mul

triple = partial(mul, 3)
print(triple(7))
print(list(map(triple, range(1, 10))))

以上代码执行的结果为:

21
[3, 6, 9, 12, 15, 18, 21, 24, 27]

在来个举个简单的 ?

def sum(x, y):
    return x + y

s = partial(sum, 10)
print(s(20))

#使用lambda表达式实现
sum = lambda x,y:x+y
s1 = partial(sum, 30)
print(s1(50))

以上代码执行的结果为:

30
80

 

转载于:https://www.cnblogs.com/demon89/p/7389609.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值