Wlgls 冲鸭!

第四章 一等函数


函数是对象

在Python中,函数是一等对象(first-class object)。

对于一等对象有以下特征:

  1. 在运行时创建
  2. 能赋值给变量或数据结构中的元素
     >>> def foo():
     ...     return 1
     ... 
     # 赋值给参数
     >>> test = foo
     >>> test()
     1
     >>> test
     <function foo at 0x7ff6623babf8>
     >>> 
     >>> def foo2():
     ...     return 2
     ... 
    
     # 存储在列表中
     >>> funcs = [foo, foo2]
     >>> funcs
     [<function foo at 0x7ff6623babf8>, <function foo2 at 0x7ff6623bad90>]
    
  3. 能作为参数传给函数
     # 以sorted函数举例,我们可以将len函数作为参数传进去
     >>> strs = ['a', 'aaa', 'aa']
     >>> sorted(strs, key=len)
     ['a', 'aa', 'aaa']
    
  4. 能作为函数的返回结果
     >>> def foo():
     ...  pass
     ... 
     >>> def foo1():
     ...     return foo
     ... 
     >>> foo1()
     <function foo at 0x7ff6623bae18>
    

可调用对象

除了用户定义的函数外,调用运算符(即())还可以用在其他对象上, python的数据模型文档上列出了七种可调用对象。

  1. 用户定义的函数

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

  2. 内置函数

    如len等

  3. 内置方法

    如dict.get等

  4. 方法

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

  5. 调用类时会运行类的__new__方法创建一个实例,然后运行__init__方法,初始化实例,最后把实例返回给调用方。所以调用类相当与调用函数。

  6. 类的实例

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

  7. 生成器函数

    使用yield关键字的函数或方法。

用户定义的可调用类型

对于任何的Python对象,只要实现实例方法__call__,都可以表现的像函数。

如:

import random

class BingoCage:
    
    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):
        return self.pick()

if __name__ == '__main__':

    bingo = BingoCage(range(3))
    print(bingo.pick())
    print(bingo())
    print(callable(bingo))

在上述例子中,我们可以使用bingo()来直接代替bingo.pick(),进行更便捷的操作。

参数

Python提供了一个及其灵活的参数处理机制。其中主要有几种参数,如必选参数,默认参数,可变参数,关键字参数,仅限关键字参数

必选参数

必选参数就是在传参数时,按照顺序,依次传值。

>>> def foo(a, b):
...     pass
... 
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() missing 2 required positional arguments: 'a' and 'b'
>>> foo(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() missing 1 required positional argument: 'b'
>>> foo(1, 2)

在上述的例子中,传入的参数中在foo函数中a=1, b=2

默认参数

对于默认参数,我们通常在函数中直接给参数以默认的值,调用的时候默认参数已经有值,所以可以不用传值,但是如果传值,将改变默认参数

需要注意的是,必选参数一定在默认参数前面

>>> def foo(a, b=1):
...     print("a:", a,'b:', b)
... 
>>> foo(2)
a: 2 b: 1
>>> foo(1, 2)
a: 1 b: 2
>>> foo(3, b=4)
a: 3 b: 4

可变参数 *

对于可变参数,传入的参数的个数是可变的,然后这些可变参数在函数调用是自动变成一个元组

>>> def foo(a, *b):
...     print(type(b))
...     print(b)
... 
>>> foo(1, 2, 3)
<class 'tuple'>
(2, 3)
>>> foo(1, [2, 3])
<class 'tuple'>
([2, 3],)
>>> foo(1)
<class 'tuple'>
()

关键字参数 **

关键字参数与可变参数都是数量可变的,但是对于关键字参数,我们可以传入任意个含有参数名的参数,而且它只接受含有参数名的参数。这些参数在函数内部自动组成一个dict。

>>> def foo(a, **b):
...     print('a:', a, 'b:', b)
... 
>>> foo(1, 2, 3, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes 1 positional argument but 4 were given
>>> foo(1, a=1, b=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got multiple va对lues for argument 'a'
>>> foo(1, c=1, d=2)
a: 1 b: {'c': 1, 'd': 2}

仅限关键字参数

也叫做命名关键字参数,这可以限制要传入的参数的名字,只能传入已经命名的关键字参数。

仅限关键字参数也可以设置默认值,但这不是强制的。

>>> def foo(a, *, b):
...     print('a:', a, 'b:', b)
... 
>>> foo(a=1, c=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got an unexpected keyword argument 'c'
>>> foo(a=1, b=3)
a: 1 b: 3

混合参数

基于以上参数,我们可以将各种格式的参数混合起来,来达到我们的目的。

>>> def foo(a, *content,  b=2,  **items):
...     print('a:', a, '\ncontent:', content, '\nb:', b, '\nitems:', items)
...
>>> foo(1, 2, 3, b=4, c=5)
a: 1 
content: (2, 3) 
b: 4 
items: {'c': 5}
>>> foo(1, 2, 3,  c=5)
a: 1 
content: (2, 3) 
b: 2 
items: {'c': 5}

以上述例子,里面虽然包含了仅限关键字参数, 但是我们也有一个关键字参数**items来处理其余的带有关键字的参数

函数注解

Python对注解所做的唯一的事情就是把它们存储在函数的__annotations__属性中,除此之外,Python不做检查,不做强制,不做验证,什么操作都不进行。也就是说,注解对Python解释器是毫无用处的

>>> def foo(test: str, max_len: 'int > 0'=80) -> str:
...     pass
... 

一个简单的函数注解就像上面一样,函数声明的各个参数可以在:后面增加注解表达式。如果参数有默认值,则放在参数名和=号之间。如果想注解返回值,则在末尾添加->和一个表达式,这个表达式可以是任何类型,最常见的是类(str和int),还有字符串(“int > 0”)。

我们可以使用函数自带的__annotations__属性来得到他们的注解。

>>> foo.__annotations__
{'test': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}

匿名函数

lambda关键字创建匿名函数

但是在Python中很少使用lambda表达式。因为一些非平凡的lambda表达式要么难以阅读,要么无法写出,所以lambda通常使用在列表参数中,或者传递给其他高阶参数。

>>> a = [-1, -2, 1, 2, 3, 4]
>>> sorted(a, key=lambda num: num**2)
[-1, 1, -2, 2, 3, 4]

函数内省

在上一个我们了解了函数的__annoatations__属性,函数还拥有很多参数,我们可以使用dir()来查看

>>> dir(foo)
['__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__']

其中较为重要的有__annotations__, __call__等,我暂时对他完全想不到应用场景,用到再说, 官方文档,其中关于callable types是对可调用对象的介绍。