欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

函数是一等对象的学习总结

程序员文章站 2022-04-06 15:07:31
...

函数是一等对象(first-class object)

在Python中,万物皆可为对象,函数也不例外。

在Python中,函数为一等对象(first-class object),那么什么才是一等对象呢?

编程语言理论家把“一等对象”定义为满足下述条件的程序实体

  • 在运行时创建
  • 能赋值给变量或数据结构中的元素
  • 能作为参数传给函数
  • 能作为函数的返回结果

1、把函数当作对象

Talk is cheap. Show me the code.

下面分别举例说明这四个特点

  • 在运行时创建

    test()
    
    
    def test():
        print('hello')
    

    Traceback (most recent call last):
    File “E:/pyproject/fluent_python/test1.py”, line 1, in
    test()
    NameError: name ‘test’ is not defined

    对于上述这段代码,我们可以看到程序会报错。

    这是因为Python是从上到下一行一行执行语句的。当我们在执行到第一行时,由于还没有执行到函数test定义这一部分。因此会报test没有定义这种错误。

    这一现象说明函数满足“在运行时创建”这一特点

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

    ⚠️注:对于定义的一个函数例如func,不加括号指的是函数的本身,加括号表示对函数的调用(原因后续会解释)。

    def func1():
        print('hello')
    
    
    def func2():
        print('world')
    
    # 赋值给变量
    a = func1  #建立了a对函数func1的一个引用
    a()
    a_list = [1, 2]
    # 赋值给列表中的元素
    a_list[1] = func2 
    print(a_list)
    

    hello
    [1, <function func2 at 0x0000026F5C2A60D8>]

  • 能作为参数传给函数

    def inner():
        print('hello')
    
    
    def outer(func):
        func()
    
    # 将函数inner作为参数赋值给outer
    outer(inner)
    

    hello

  • 能作为函数的返回结果

    def inner():
        print('hello')
    
    
    def outer(func):
    
        return func
    
    
    print(outer(inner))
    

    <function inner at 0x000001EA859461F8>

2、高阶函数

接受函数作为参数,或者把函数当做结果返回的函数是高阶函数(higher-order function)

例如,想要根据单词的长度排序,只需将len函数传入到sorted函数的key参数中.

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

[‘apple’, ‘cherry’, ‘banana’, ‘strawberry’]

高阶函数map,filter和reduce的现代替代品

reduce在Python3中已移除。map,filter可以使用列表推导或者生成器表达式代替,而且可读性更强。

def factorial(n):
    return 1 if n < 2 else n * factorial(n - 1)


print(list(map(factorial, range(6))))
print([factorial(n) for n in range(6)])

print(list(map(factorial, filter(lambda x: x % 2, range(6)))))
print([factorial(n) for n in range(6) if n % 2])

[1, 1, 2, 6, 24, 120]
[1, 1, 2, 6, 24, 120]
[1, 6, 120]
[1, 6, 120]

3、匿名函数

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

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

[‘banana’, ‘apple’, ‘fig’, ‘raspberry’, ‘strawberry’, ‘cherry’]

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

4、可调用对象

除了用户定义的函数,调用运算符(即()),还可以应用到其他对象上。

想判断对象能否调用,可以使用内置的callable函数。

Python 数据模型文档列出了7 种可调用对象

名称 说明
用户定义的函数 使用def或lambda表达式创建
内置函数 如len 或time.strftime
内置方法 如dict.get
方法 在类的定义体中定义的函数
调用类时会运行类的__new__方法创建一个实例,然后运行__\init__方法,初始化实例,最后把实例返回给调用方。
类的实例 如果类定义了__call__,那么他的实例可以作为函数调用
生成器函数 使用yield关键词的函数或方法。调用生成器函数返回的是生成器对象

判断对象是否可调用callable

print([callable(obj) for obj in [abs, str, 13, list]])

函数和方法的区别

  • 函数指的是用户自己定义的函数或者内置的函数

    是可以以对象作为参数传入函数的

    # 将字符串对象作为参数传入函数中
    list('hello')
    
  • 方法指的是用户定义的类或者内置的类中定义的方法

    a_dict = {'a': 1, 'b': 2}
    # a_dict为一个dict类的实例
    print(type(a_dict)) # output:<class 'dict'>
    # 方法的调用形式:实例.方法
    print(a_dict.get('a'))
    

两者最大的使用区别就是

函数要作用在某个对象身上,形式为:function(obj)

方法需要使用到点(即.)操作,形式为:instance.method()

7、仅限关键词指定

下面函数展示了一个人能力和财富之间关系

def cal_money(name, *other, working_competence=0, **other_competence):
    money = working_competence*1000000
    print(f'{name} can earn ${money} in the end of the year')

例如,公司来了个小红,但是这个小红一天天不务正业,整天跟朋友一起玩,没事睡觉看电视。

今天主管说,经过精密的设计,公司开发了一块软件,通过这个软件发放年终奖

于是她想通过这个软件计算一下自己能拿多少年终奖。

他将她认识的朋友和消耗的时间都输入进去了

cal_money('xiaohong','xiaoming','xiaogang',watch_tv=100, sleep=200)

结果出来的结果让她十分伤心

xiaohong can earn $0 in the end of the year

为了测试这个功能是不是有问题,她特意找来了工作能力很强的小凤。小凤试了一下

cal_money('xiaofeng',working_competence=5)

xiaofeng can earn $5000000 in the end of the year

小红恍然大悟,原始在指定的方向努力才会有收获

这个故事告诉我们:working_competence只能通过关键字参数指定才能对其赋值,它一定不会捕获未命名的定位参数,如你认识的人xiaoming,也不会捕获关键字名不对应的值,如干与工作提升无关的事watch_tv

  • 定义函数时若想指定仅限关键字参数,要把它们放到前面有* 的参数后面。

    如将working_competence放到*other的后面。

  • 如果不想支持数量不定的定位参数,但是想支持仅限关键字参数,在签名中放一个*

    例如

    def new(name, *, working_competence):
        money = working_competence * 1000000
        print(f'{name} can earn ${money} in the end of the year')
    

    在这个函数中,要求你只能填两个值(例如:绩效考核时,我并不想知道你认识多少人),而且我不管你第一个值输入什么,你第二个值必须体现出你的工作能力working_competence=4

    new('a',working_competence=4)
    

    a can earn $4000000 in the end of the year

参考文献

参考内容

[1] Fluent Python by Luciano Ramalho (O’Reilly).Copyright 2015 Luciano Ramalho, 978-1-491-94600-8.