‘一等对象’定义:

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

在python中,整数,字符串,字典都是一等对象。但是在python中函数也符合以上特征,所有函数都是一等对象。

示例,实现阶乘


def factorial(num):
    """阶乘实现 :一个正整数的阶乘是所有小于及等于该数的正整数的积,并且有0的阶乘为1。"""
    return 1 if num <= 1 else num * factorial(num - 1)


print(factorial.__doc__)  #__doc__是函数对象的一个属性,调用help(factorial)会用到__doc__
print(factorial(4))
打印
阶乘实现 :一个正整数的阶乘是所有小于及等于该数的正整数的积,并且有0的阶乘为1。
24


高阶函数

高阶函数的定义:接受函数为参数,或者把函数作为结果返回的函数。

比如内置的sorted,可选的key参数接受一个函数,它会应用到各个元素上进行排序。

示例,sorted函数应用


# 根据长度排序
fruits = ['apple', 'banana', 'mango', 'carrot']
print(sorted(fruits, key=len))


# 根据反向拼写单词排序
def reverse(word):
    return word[::-1]


print(sorted(fruits, key=reverse))
打印:
['apple', 'mango', 'banana', 'carrot']
['banana', 'apple', 'mango', 'carrot']


再比如常见的map、filter、reduce都需要传递函数作为规则。不过有列表推导或者生成器作为替代品。

示例,使用列表推导代替map和filter


def factorial(num):
    """阶乘实现 :一个正整数的阶乘是所有小于及等于该数的正整数的积,并且有0的阶乘为1。"""
    return 1 if num <= 1 else num * factorial(num - 1)


# 批量计算阶乘
print(list(map(factorial, range(6))))  # map
print([factorial(x) for x in range(6)])  # 列表推导

# 批量计算 奇数的阶乘
print(list(map(factorial, filter(lambda n: n % 2, range(6)))))  # map和filter,还要借助lambda
print([factorial(x) for x in range(6) if x % 2])  # 列表推导
打印
[1, 1, 2, 6, 24, 120]
[1, 1, 2, 6, 24, 120]
[1, 6, 120]
[1, 6, 120]


以上可以看到,列表推导完全可以代替map和filter,而且还更简洁,还避免了使用lambda表达式

reduce在python2中是内置函数,到python3中移入了functoos模块中。

示例,使用reduce和sum计算0~99的和

from functools import reduce
from operator import add

print(reduce(add, range(100)))  # 使用现成add加法函数,配置reduce累加

print(sum(range(100)))  # sum求和
打印:
4950
4950

reduce和sum都是把某个操作连续应用到序列的元素上,累加之前的结果,最后归约成一个值。称之为归约函数

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

all(iterable)

any(iterable)

匿名函数

lambda关键字在python表达式内创建匿名函数。

然后纯表达式限制了lambda表达式中不能使用while、try、等python语句,除了作为参数传递给高阶函数之外,Python很少使用匿名函数。

可调用对象

除了函数,调用运算法(也就是括号())可以用在其他对象上。

判断一个对象是否可调用,可以使用内置函数callable(obj)来判断。

在Python中有七种可调用对象:

  • 用户自定义个的函数,包括def语句、lambda表达式。
  • 内置函数,如len
  • 内置方法,如dict.get()
  • 方法,在类中定义的函数
  • 类,当类使用调用运算符时,会运行类的__new__方法创建一个实例,然后运行__init__方法初始化实例。
  • 类的实例,如果类定义了__call__方法,那么他的实例是可以调用的。
  • 生成器函数,使用yeild关键字的函数或者方法,他的实例可以调用。

示例,实现一个随机球的笼子


import random


class BingoCage:
    def __init__(self, list_):
        self.balls = list_
        random.shuffle(self.balls)  # 打乱成随机排序

    def pike(self):
        """取出一个球"""
        try:
            return self.balls.pop()
        except IndexError:
            raise LookupError('ball is empty!')  # 如果balls空了,抛出异常

    def __call__(self, *args, **kwargs):
        return self.pike()


balls = BingoCage([1, 3, 6, 7, 8])
print(callable(balls))
print(balls())
print(balls())
print(balls())
打印
True
3
8
1


函数内省 function introspection

先解释一下什么叫内省(xing):

内省,也叫作类型内省,是在运行时进行的一种对象检测机制。我们可以通过内省来获取一个对象的所有信息,比如这个对象有哪些属性,这个对象的类型

比如dir()可以获取函数的所有属性,type()可以获取到函数的类型。

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__']

函数的__dict__存储了赋予它的用户属性。一般来说给函数赋予属性不是很常见,但是Djongo框架就这么做了

示例,给函数赋予属性


def upper_name(obj): return obj.upper() upper_name.description = 'Upper Name'


列出函数中特有的属性,在常规对象中没有的:

>>> class C:pass

...

>>> obj = C()

>>> def func():pass

...

>>> list(set(dir(func))-set(dir(obj)))

['__get__', '__globals__', '__qualname__', '__kwdefaults__', '__call__', '__code__', '__name__', '__defaults__', '__annotations__', '__closure__']

Python3的仅限关键字参数

“仅限关键字参数”是Python3中的新特征。

定义函数如果想指定仅限关键字参数,就把他们放到参数*的后面,就像这样def func(a, * , b, c) 此时的b和c参数,就必须要用b=xxx 和c=xxx形式传入了:func(1,b=2,c=3)

以下示例和仅限关键字参数没啥关系,只是说明cls参数只能通过关键字参数指定。


def tag(name, *content, cls=None, **attrs):
    """生成HTML标签
    :param name: 标签名
    :param content:内容
    :param cls:class
    :param attrs:属性
    :return:
    """
    if cls is not None:
        attrs['class'] = cls
    if attrs:
        attrs_str = ''.join(' %s="%s"' % (attr, value) for attr, value in sorted(attrs.items()))
    else:
        attrs_str = ''
    if content:
        return '\n'.join('<%s%s>%s</%s>' % (name, attrs_str, cont, name) for cont in content)
    else:
        return '<%s%s />' % (name, attrs_str)


print(tag('br'))
print(tag('p', 'hello'))
print(tag('p', 'hello', 'world', cls='sidebar'))
打印
<br />
<p>hello</p>
<p class="sidebar">hello</p>
<p class="sidebar">world</p>


获取函数参数的信息

可以通过函数以下的属性,获取到参数的信息:

__defaults__它的值是一个元祖,里面保存着定位参数和关键字参数的默认值。

__kwdefaults__保存了仅限关键字参数的默认值。

__code__是一个对象引用。它自身有很多属性,就包括参数的名称。

__code__.co_varnames 参数名称,不过里面还有函数中的局部变量。

__code__.co_argcount 参数个数,配合参数名称可以用co_varnames[:co_argcount]这种形式切片出函数的参数。

__code__.co_name 函数的名字

示例,获取函数的参数信息,(函数:在指定长度附近截断字符串)


def clip(text, max_len=80):
    """在max_len前面或者后面的第一个空格处截断文本"""
    end = None
    if len(text) > max_len:
        space_before = text.rfind(' ', 0, max_len)  # str.rfind(str, beg=0 end=len(string))如果没有匹配项则返回 -1
        if space_before > 0:
            end = space_before
        else:
            space_before = text.rfind(' ', max_len)
        if space_before >= 0:
            end = space_before
    if end is None:  # 没找到空格
        end = len(text)
    return text[:end].rstrip()


######################提取函数参数信息####################################
print(clip.__defaults__)
print(clip.__code__)
print(clip.__code__.co_name)

print(clip.__code__.co_varnames)
print(clip.__code__.co_argcount)
print(clip.__code__.co_varnames[clip.__code__.co_argcount])
打印
(80,)
<code object clip at 0x016F2230, file "C:/Users/lijiachang/PycharmProjects/collect_demo/test.py", line 38>
clip
('text', 'max_len', 'end', 'space_before')
2
end


更好的方式--使用inspect模块提取函数参数

先看示例:


######################提取函数参数信息####################################
from inspect import signature
sig = signature(clip)
print(str(sig))  # 参数信息

for name, param in sig.parameters.items():
    print(param.kind, ':', name, '=', param.default)
打印
(text, max_len=80)
POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'>
POSITIONAL_OR_KEYWORD : max_len = 80


可以看到使用str(signature(函数对象)) 可以直接返回了函数的参数信息。而且便于阅读。

inspect.signature对象中有一个paramaters属性,其中的name是参数命名,default是默认值(如果默认值为空,就是inspect.empty类),kind属性是以下5个值之一,表示参数的类型:

POSITIONAL_OR_KEYWORD

可以通过定位参数和关键字参数传入的形参(大多数python函数的参数都是这种)

VAR_POSITIONAL

定位参数元祖

VAR_KEYWORD

关键字参数字典

KEYWORD_ONLY

仅限关键字参数(Python3新增)

POSITIONAL_ONLY

仅限定位参数。Python声明函数的语法不支持,有些是C语言实现的函数如divmod。

inspect.signature对象还有个bind方法,可以把任意个参数绑定到签名的形参上,所用的规则和python解释器实参到形参的形式一样。

这样有个用途就是,可以在真正调用函数之前,验证参数

简单的使用方法:

from inspect import signature
sig = signature(tag)
my_tag = {'name'= 'img', 'cls'= 'framed'}
bound_args = sig.bind(**my_tag)
#如果想打印绑定关系:
print(bound_args.arguments)

此过程如果不抛出异常,就说明实参能符合函数的形参规则,能正常传入。

函数注解

从Python3开始,新增了函数注解功能,用于为函数声明中的参数和返回值增加元数据。

def clip(text, max_len=80): 如果使用函数注解:

def clip(text:str, max_len:'int > 0'=80) -> str:

函数注解的语法就是在:之后增加注解表达式。

注解不会做任何处理,不做检查,不做强制,不做验证。只是把它们存储在__annotations__属性中,可以用于IDE、框架、装饰器等工具使用。

函数式编程的包 operator和functools

Python得益于operator和functools等模块的支持,函数式编程风格也可以信手拈来。

operator模块

这个模块提供了多个算术运算符对应的函数。避免了些lambda a,b: a*b 这种频繁的匿名函数,使用mul就可以计算乘积。

示例,使用reduce和operator.mul函数计算阶乘。


from functools import reduce
from operator import mul


def fact(n):
    if n == 0:
        return 1
    else:
        return reduce(mul, range(1, n + 1))


还有一个比较有意思的函数methodcaller,它创建的函数会在对象上调用参数指定的方法。有点类似于偏函数partial的用法。

通俗的讲,就是把【对象.方法名】使用 xxx=methodcaller('方法名') 变成【xxx(对象)】

示例,绑定额外的参数


from operator import methodcaller

s = 'hello world'
up = methodcaller('upper')  # 这样可以实现动态改变函数的引用啊:)
print(up(s))

replace_ = methodcaller('replace', ' ', '-')  # 空格替换成-
print(replace_(s))
打印
HELLO WORLD
hello-world


functools模块

functools模块中提供了一系列的高阶函数,比如常用的reduce,partial和变体partialmethod

偏函数partial

可以创建出另一个新的可调用对象,把原函数的某些参数固定,这样参数就更少

partial的第一个参数是可调用对象(函数),后面跟着任意个要固定的定位参数和关键字参数。

示例,partial的用法,常见方法


def tag(name, *content, cls=None, **attrs):
    """生成HTML标签
    :param name: 标签名
    :param content:内容
    :param cls:class
    :param attrs:属性
    :return:
    """
    if cls is not None:
        attrs['class'] = cls
    if attrs:
        attrs_str = ''.join(' %s="%s"' % (attr, value) for attr, value in sorted(attrs.items()))
    else:
        attrs_str = ''
    if content:
        return '\n'.join('<%s%s>%s</%s>' % (name, attrs_str, cont, name) for cont in content)
    else:
        return '<%s%s />' % (name, attrs_str)


######################partial应用####################################
from functools import partial

picture = partial(tag, 'img', cls='pic-frame')  # 冻结参数
print(picture(src='path.jpg'))

print(picture.func)  # 原函数的引用
print(picture.args)  # 冻结的普通参数
print(picture.keywords)  # 冻结的固定参数
打印
<img class="pic-frame" src="path.jpg" />
<function tag at 0x02B10810>
('img',)
{'cls': 'pic-frame'}


在Python3.4中新增了一个partialmethod和partial作用一样,不过是用于方法的。