【转载】Python 一等函数

原文网址:https://mp.weixin.qq.com/s/OUPcqBJiP0_wyn_Bm7IvAQ

 

Python 一等函数

在 Python 中,不仅整数、字符串、字典是一等对象,连函数也被当做一等公民。这说明了什么问题,先来看看一等对象的定义:

  • 在运行时创建

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

  • 能作为参数传给函数

  • 能作为函数的返回结果

那么,也就意味着 Python 函数是对象,是 function 类的实例。这篇文章从函数的属性、参数等几个方面拆解并分析 Python 函数。

Python 的可调用对象

首先看看 Python 中可直接调用的对象。我们知道,调用函数的方法是用调用运算符 () ,那么判断一个对象是否可调用,就是判断 () 能否应用到该对象上,可以使用内置的 callable() 函数。

Python 数据模型文档列出了 7 种可调用对象:用户定义的函数、内置函数、内置方法、方法、类、类的实例、生成器函数。

上述 7 种对象中,函数天生就是可以调用的,方法是类中定义的函数,而创建类的实例就是直接调用了类(其实是运行类的 __new__ 方法,然后运行 __init__ 方法初始化实例)。比较难理解的是类的实例,类的实例是通常意义上的对象,对象可调用的前提是类中定义了 __call__ 方法。看个  吧:

  1. class Father:

  2.    def __init__(self, age):

  3.        self.sex = "male"

  4.        self.age = age

  5.        self.money = 0

  6.  

  7.    def own_money(self, money):

  8.        self.money = money

  9.  

  10.  

  11. if __name__ == "__main__":

  12.    f = Father(30)

  13.    f.own_money(100)

  14.    print("Father's money: {}"

  15.          .format(f()))

运行结果:

  1. Traceback (most recent call last):

  2.  File "/Users/zww/my_demo/test.py", line 15, in <module>

  3.    .format(f()))

  4. TypeError: 'Father' object is not callable

提示 Father object 不可被调用。在 Father 类中定义下面这个方法:

  1.    def __call__(self, *args, **kwargs):

  2.        return self.money

运行结果:

  1. Father's money: 100

所以,只要定义了方法 __call__ ,任何 Python 对象都可以表现得像函数。

函数的属性

我们可以通过 dir 函数来探知 Python 函数具有哪些属性:

  1. In [1]: def fun():

  2.   ...:     pass

  3.   ...:

  4.  

  5. In [2]: dir(fun)

  6. Out[2]:

  7. ['__call__',

  8. '__class__',

  9. '__closure__',

  10. '__code__',

  11. '__defaults__',

  12. '__delattr__',

  13. '__dict__',

  14. '__doc__',

  15. '__format__',

  16. '__get__',

  17. '__getattribute__',

  18. '__globals__',

  19. '__hash__',

  20. '__init__',

  21. '__module__',

  22. '__name__',

  23. '__new__',

  24. '__reduce__',

  25. '__reduce_ex__',

  26. '__repr__',

  27. '__setattr__',

  28. '__sizeof__',

  29. '__str__',

  30. '__subclasshook__',

  31. 'func_closure',

  32. 'func_code',

  33. 'func_defaults',

  34. 'func_dict',

  35. 'func_doc',

  36. 'func_globals',

  37. 'func_name']

其中,大部分属性是 Python 对象共有的,但这里我们主要讨论几种类的实例没有,但函数有的属性:

名称类型说明
__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 进一步提供了仅限关键字参数。

仅限关键字参数

在 Python 中调用函数时使用 * 和 ** “展开” 可迭代对象,映射到单个参数。 * 表示将调用时的多个参数放入元组中,而 ** 表示将关键字参数放入一个字典中。

用  说话:

  1. def father_own_money(name, *args, **kwargs):

  2.    father_name = name

  3.    default_money = args

  4.    owned_money = kwargs["money"]

  5.    return father_name, default_money, owned_money

  6.  

  7.  

  8. if __name__ == "__main__":

  9.    name = "MaYun"

  10.    result = father_own_money(name, 1, 2, money=100)

  11.    father_name = result[0]

  12.    default_money = result[1]

  13.    owned_money = result[2]

  14.    print("Father {} already has money {}.\n"

  15.          "Then he owned money {}.".format

  16.          (father_name, default_money, owned_money))

运行结果:

  1. Father MaYun already has money (1, 2).

  2. Then he owned money 100.

上面  中的函数还可以这样调用:

  1. name = "MaYun"

  2. arrs = {"money": 100}

  3. result = father_own_money(name, 1, 2, **arrs)

当想指定仅限关键字参数时,可以将指定的关键字参数放在 * 后面,比如:

  1. def father_own_money(name, *, money):

  2.    father_name = name

  3.    owned_money = money

  4.    return father_name, owned_money

  5.  

  6.  

  7. if __name__ == "__main__":

  8.    name = "MaYun"

  9.    result = father_own_money(name, money=100)

  10.    father_name = result[0]

  11.    owned_money = result[1]

  12.    print("Father {} owned money {}.".

  13.          format(father_name, owned_money))

运行结果:

  1. Father MaYun owned money 100.

函数参数的信息

接下来我们看看函数参数的相关信息,来更好的理解函数。

结合函数的属性一节,我们知道函数的 __defaults__ 属性存放着函数形参的默认值而 __kwdefaults__ 属性则存放的是仅限关键字参数的默认值; __name__ 属性存放函数的名称; __code__ 属性存放的是编译成字节码的函数元数据和函数定义体,(好吧看不懂),没关系,只需要知道 __code__ 是一个 code 对象引用,和自身的两个属性: co_varnames 和 co_argcount 就足够装 B 了。

结合  做简单说明:

  1. def father_own_money(name="MaYun", *, money=100):

  2.    father_name = name

  3.    owned_money = money

  4.    return father_name, owned_money

  5.  

  6.  

  7. if __name__ == "__main__":

  8.    print("Function's name: \n\t{}".

  9.          format(father_own_money.__name__))

  10.    print("Function keywords defaults: \n\t{}".

  11.          format(father_own_money.__kwdefaults__))

  12.    print("Function formal params defaults: \n\t{}".

  13.          format(father_own_money.__defaults__))

  14.    print("Function __code__ property: \n\t{}".

  15.          format(father_own_money.__code__))

  16.    print("Function all params name: \n\t{}".

  17.          format(father_own_money.__code__.co_varnames))

  18.    print("Function formal params num: \n\t{}".

  19.          format(father_own_money.__code__.co_argcount))

先看运行结果:

  1. Function's name:

  2.    father_own_money

  3. Function keywords defaults:

  4.    {'money': 100}

  5. Function formal params defaults:

  6.    ('MaYun',)

  7. Function __code__ property:

  8.    <code object father_own_money at 0x101f618a0, file "xx.py", line 14>

  9. Function all params name:

  10.    ('name', 'money', 'father_name', 'owned_money')

  11. Function formal params num:

  12.    1

结合运行结果可以看出 __code__.co_varnames 记录的是该函数的所有参数,包括传参和函数内定义的局部变量;而 __code__.co_argcount 记录的是形式参数的个数,可以看出不包含关键字参数。更多关于函数参数的分析可以引用 Python 的 inspect 模块来提取函数的前面,感兴趣可以研究研究,这里不做详解。

函数注解

Python3 提供了一种句法,用于为函数声明中的参数和返回值附加元数据,就是函数注解,目的是更方便的进行文档编写、参数检查等。这是个可选项,入参的注解在参数后加个 : 即可,而返回值的注解需要在 ) 和 : 直接添加 -> 和一个表达式。上面 father_own_money添加注解如下:

  1. def father_own_money(name: str ="MaYun", *, money: "default = 100"=100) -> ():

  2.    father_name = name

  3.    owned_money = money

  4.    return father_name, owned_money

需要说明的是,函数注解仅仅在函数的 __annotations__ 属性中做了存储,除此之外 Python 不做检查、不做验证、不做强制,没有任何处理。换句话说,注解只是元数据,可以供 IDE 、框架和装饰器等工具使用。

高阶函数和匿名函数

了解了 Python 函数的以上特性之后,我们就可以利用一等函数的特性实现函数式风格编程了。

高阶函数

函数式风格编程的特点之一就是高阶函数,那么什么是高阶函数呢?既然 Python 函数是一等公民,那么 TA 既可以作为函数参数传入,也可以作为结果返回。而接受函数为参数,或者把函数作为结果返回的函数,我们称之为高阶函数。Python 中最为人熟知的高阶函数有: map() 、 reduce() 、 sorted()、 filter() 。

  • map(func, *iterables) --> map object

map() 函数的传参:函数和一系列可迭代对象;返回一个 map 对象;函数的操作是将第二个参数(可迭代对象)的每个元素代入第一个参数(函数)中,得到的结果作为函数返回值。 如下:

  1. def father_own_money(name: str = "MaYun", money=100):

  2.    father_name = name

  3.    owned_money = money

  4.    return father_name, owned_money

  5.  

  6.  

  7. if __name__ == "__main__":

  8.    name = ["mayun"] * 10

  9.    money = range(0, 10)

  10.    results = map(father_own_money, name, money)

  11.    print(list(results))

运行结果:

  1. [('mayun', 0), ('mayun', 1), ('mayun', 2), ('mayun', 3), ('mayun', 4), ('mayun', 5), ('mayun', 6), ('mayun', 7), ('mayun', 8), ('mayun', 9)]

需要说明的是, map() 传参中,可迭代对象有几个,取决于第一个函数有多少参数,用上面的  说就是 name 和 money 分别作为 father_own_money 的第一和第二个参数,可迭代数量按最少的算。由于返回值 results 是个 map 对象,需要 list 一下,才能得到所有的返回结果。

  • reduce(function, sequence) -> value

和 map() 不同的是, reduce() 的传参 function 必须有两个参数;返回值就是 function 的返回值类型;函数的操作是:用 function 先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,直到计算完所有元素,最后得到一个结果。

  1. def father_money_sum(default_money: int, owned_money: int):

  2.    return default_money + owned_money

  3.  

  4.  

  5. if __name__ == "__main__":

  6.    money = range(0, 101)

  7.    from functools import reduce

  8.  

  9.    results = reduce(father_money_sum, money)

  10.    print("Father's all money: {}".format(results))

运行结果:

  1. Father's all money: 5050

需要注意的是, reduce 在 Python3 中从全局名字空间中被移除了,需要先引入。

  • sorted(*args, **kwargs)

sorted 函数本身只接受一个列表来排序,但因为其可选参数 key 接受函数来应用到列表的各个元素上进行排序,所以摇身一变成了高阶函数(2333333)。看个  :

  1. def father_money_sum(default_money: int, owned_money: int = 90):

  2.    if default_money > 150:

  3.        return owned_money

  4.    return default_money + owned_money

  5.  

  6.  

  7. if __name__ == "__main__":

  8.    father_money = [100, 200, 201, 105, 189]

  9.    result = sorted(father_money, key=father_money_sum)

  10.    print(result)

运行结果:

  1. [200, 201, 189, 100, 105]

由运行结果可以看出, sorted 函数仅仅将 father_money 这个列表中的每个值代入到 father_money_sum 函数中,针对结果对原列表进行排序,并没有改变列表中的值。

  • filter(function or None, iterable) --> filter object

filter() 函数的操作是将 iterable 中的元素分别代入 function 中,将结果为 True 的返回。

  1. def father_money_sum(default_money: int, owned_money: int = 90):

  2.    if default_money > 150:

  3.        return None

  4.    return default_money + owned_money

  5.  

  6.  

  7. if __name__ == "__main__":

  8.    father_money = [100, 200, 201, 105, 189]

  9.  

  10.    result = filter(father_money_sum, father_money)

  11.    print(list(result))

匿名函数

为了更好的使用高阶函数,有时候创建一些小型的函数更为便利,这时候匿名函数的作用就体现出来了。

匿名函数主要是用关键字 lambda ,语法为:

  1. lambda x: x + 1

上面这个  用数学表达式可以写成: f(x) = x + 1 ,这样理解就方便多了, lambda 相当于数学函数中的 f , : 后的表达式的计算结果为匿名函数的返回值。这种简单的语法也意味着 lambda 函数只支持纯表达式,不能赋值,不能用 while try 等 Python 语句。

当然, lambda 表达式也支持多参数,比如:

  1. lambda x, y: x + y

用数学表达式写成: f(x, y) = x + y

lambda 句法只是语法糖:与 def 语句一样, lambda 表达式会创建函数对象。

posted on 2018-09-20 10:36  nicolas_Z  阅读(106)  评论(0)    收藏  举报

导航