1
本文章记录了本人在学习Python基础之函数篇的重点知识及个人心得,欢迎打算入门Python的朋友与我一起学习交流。。

本文重点:

1、了解函数在Python中是一等对象;
2、了解Python中的可调用对象;
3、掌握正确定义函数参数的方法;
4、了解operator和functools中支持函数式编程的方法。

一、函数是一等对象

1、一等对象

定义:一等对象是满足如下条件的程序实体:

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

在Python中,所有函数都是一等对象。

2、高阶函数

定义:接受函数为参数,或者把函数作为结果返回的函数是高阶函数。
在Python中传统的高阶函数有map,filter,reduce;常用的高阶函数有内置函数sorted、min、max和functools.partial。

  • map(function, iterable, ...):
    map返回一个迭代器,迭代器是通过function处理可迭代对象中的每个元素产生的返回值的集合。
  • filter(function, iterable):
    filter相当于一个过滤器,以函数返回值为判定条件,筛选出True的元素并放入迭代器中返回。
  • functools.reduce(function, iterable[, initializer])
    reduce对可迭代对象中从左开始元素选出两个进行函数运算,将返回的运算值作为一个参数继续与第三个元素进行函数运算,直至迭代完成返回运算值。

3、归约函数:

定义:能够接受一个可迭代对象并返回单个结果的函数是归约函数。
reduce就是归约函数的一种,sum也是一种归约函数。本章额外介绍两个内置的归约函数。

all(iterable):
可迭代对象中每一个元素都是真值则返回True,否则False。
any(iterable)
可迭代对象中存在一个元素是真值则返回True,否则False。
归约函数会在第14章中讨论可迭代对象时重点讲解。

4、匿名函数:

匿名函数:使用lambda表达式创建的函数,函数本身没有名字来辨识,因而叫做匿名函数。
句法特点:lambda函数只能使用纯表达式,不能赋值,也不能使用while和try等语句。
语法:lambda [arg1 [,arg2,.....argn]]:expression。
优点:创建方便,简化代码工作量。
缺点:代码可读性降低。

5、函数内省

在计算机编程中,自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。

二、函数参数与注解

1、函数参数

在函数的定义过程可能需要传入参数,对于函数涉及到的参数分为以下四种:

  • 必填参数
    调用函数必须填写的参数。在参数中居于靠前位置。
  • 默认参数
    当必填参数设置默认值时可选填。注意默认值是不可变对象,否则有逻辑错误。
  • 可变参数
    用单星号*args表示,即传入的参数是不定的。*args把参数收到元组中接受。
    传入方式既可以是直接传入,如func(1, 2, 3);也可以用列表或元组传入,如func(*(1,2,3))。
  • 强制关键字参数
    此类参数只能捕获通过指定关键字传入的参数,无法按照位置顺序读参。定义时前面需放一个*。
  • 关键字参数
    此参数可填可不填。传入方式分两种,一是”传递参数名=传递参数值”形式的参数,这种方式传入对位置无要求;二是不写参数名,按照位置顺序传入参数值。
    当关键字参数不定时用双星号**kw表示。**kw把关键字参数收到字典中接受。关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。

参数组合:当函数涉及到使用多种参数时,定义和传入的参数需按照的顺序:
必填参数、默认参数、可变参数、强制关键字参数、关键字参数.

def func(name,country="China",*,age,**rest):
    print("name :",name," country :",country," age :",age," rest :",rest)
func("Jack","US",age=20)
func("Hoya",age=15,male="Boy",height="178",grade="excellent",country="UK")

#output:
name : Jack  country : US  age : 20  rest : {}
name : Hoya  country : UK  age : 15  rest : {'male': 'Boy', 'height': '178', 'grade': 'excellent'}
#强制关键字参数错误传参
 func("Hoya",country="UK",20)

SyntaxError: positional argument follows keyword argument
#强制关键字参数只能利用关键字传入参数

注:函数参数知识引自
作者:东皇Amrzs

2、获取参数:inspect模块

可以通过A=inspect.signature(object)提取关于函数参数的信息;
signature支持signature.parameter方法返回关于参数的有序映射。
signature支持signature.bind(args, *kwargs)方法,此方法可将多个实参绑定到签名的形参来接受。

3、函数注解:

注解(annotation)从Python3开始存在,用于为函数声明中的参数和返回值附加元数据。只有inspect.signature()可以提取注解。
本人目前把注解简单理解为一种标签。

三、可调用对象

定义:支持调用运算符()的对象叫做可调用对象。
判断方法:利用内置的callable()函数判断。

Python数据模型包含7种可调用对象:

  • 用户定义的函数
    使用def语句或lambda表达式创建。
  • 内置函数
    使用CPython实现的函数,如len。
  • 内置方法
    使用CPython实现的方法,如list.pop。
  • 方法
    在类的定义体中定义的函数。
  • 类的实例
    如果类定义了__call__方法,这个类的实例可以作为函数调用。
  • 生成器函数
    使用yield关键字的函数或方法。调用生成器函数返回的对象是生成器。

下面针对类的实例为示范进行调用操作:

class Text:
    def __init__(self,text):
        self.text=str(text)
    def number_search(self):
        import re
        num_search=re.compile(r"\d+")
        return print("number search :",num_search.findall(self.text))
    def __call__(self, *args, **kwargs):
        return self.number_search()
a=Text("asdljlj55fsa56af6af66f598as5asf6af59nf3asf830fa3s")
a.number_search()

#输出:
number search : ['55', '56', '6', '66', '598', '5', '6', '59', '3', '830', '3']

从中可以看出,创建函数类对象的简便方式是实现__call__方法。

四、支持函数式编程的包

1. 函数式编程:

相比较命令式编程,函数式编程是通过函数来保存程序的状态的。或者更准确一点,它是通过函数创建新的参数或返回值来保存程序的状态的。

函数式编程与命令式编程对比

认识函数式编程应掌握的两个本质:

  • 高阶函数(higher-order functions)
    函数式编程是通过高阶函数(higher-order functions)的特性来使其具有更丰富多变的表达能力。如map,filter。
    高阶函数和一等函数让基于函数演变的函数式语言表达能力大增,使其能够用函数构建起更高层更抽象的模块来解决复杂的问题。
  • 没有副作用(no side effect)
    函数所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
    使得函数式编程各个独立的部分的执行顺序可以随意打乱。 而这在命令式编程风格的代码中是不可能的。
    执行顺序的自由使其得以衍生出一大堆非常有用的特性,比如无锁(lock-free)的并发操作、惰性求值(lazy evaluation),还有在编译器级别上的各种性能优化技术。 特别在并行技术上,Clojure, Haskell, F#, Scala, Erlang这些函数式语言都无一例外地支持强大的并发功能。
    当然函数式语言不可能真的就不执行I/O,但它通过一些手段来把I/O的影响限制到最小,比如通过Continuations, Monad等技术。

注:函数式编程知识引自
作者:Jan Fan

Python的目标不是变成函数式编程语言,但通过operator和functools等包也可以进行函数式编程,下面开始介绍这两个模块。

2. operator

本节介绍operator中的mul、itemgetter、attrgetter、methodcaller四种方法。

  • operator.mul(a,b)
    返回数字a和b的乘积。
import operator
from _functools import reduce
#计算阶乘
def fact1():
    list1=filter(lambda x: x%2,range(8))
    return reduce(operator.mul,list1)
print(fact1())#输出:105
  • operator.itemgetter(item or *items)
    创建一个接受集合的函数,返回指定索引对应的元素。如果指定索引至少为2个,以元组形式返回查询结果。
    After f = itemgetter(2), the call f(r) returns r[2].
    After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3]).
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))]
a=itemgetter(1,0)
for i in metro_data:
    print(a(i))#注意分清楚a和i谁是参数,被处理的可迭代对象是参数。

#输出:
('JP', 'Tokyo')
('IN', 'Delhi NCR')
('MX', 'Mexico City')
('US', 'New York-Newark')
('BR', 'Sao Paulo')
  • operator.attrgetter(attr or *attrs)
    创建一个函数,根据名称访问对象的属性,以元组的形式返回。
    After f = attrgetter('name'), the call f(b) returns b.name.
    After f = attrgetter('name', 'date'), the call f(b) returns (b.name, b.date).
    After f = attrgetter('name.first', 'name.last'), the call f(b) returns (b.name.first, b.name.last).

eg:按照城市经度顺序输出城市名和城市经度

from collections import namedtuple
from _operator import attrgetter

latlong=namedtuple("latlong","lat long")
citydata=namedtuple("citydata", "city ID pop coord")
city=[citydata(city,ID,pop,latlong(lat,long)) for city, ID,pop,(lat,long) in metro_data]
#拆包+列表推导。拆包注意city,ID,pop,latlong(lat,long)和citydata具名元组的结构对应关系,至于拆包用的变量名字是什么并不重要,保证可读性即可。
b=attrgetter("city","coord.lat")

#方法1
for i in sorted(city,key=attrgetter("coord.lat")):
    print(b(i))

#方法2
for i in sorted(city,key=lambda x: x[3][0]):
    print(b(i))

#输出
('Sao Paulo', -23.547778)
('Mexico City', 19.433333)
('Delhi NCR', 28.613889)
('Tokyo', 35.689722)
('New York-Newark', 40.808611)
  • operator.methodcaller(name[, args...])
    自行创建函数,使用对象中支持的方法。args代表函数所需传入的参数。
    After f = methodcaller('name'), the call f(b) returns b.name().
    After f = methodcaller('name', 'foo', bar=1), the call f(b) returns b.name('foo', bar=1).
from operator import methodcaller
c=methodcaller("upper")
d=methodcaller("islower")
print(c("apple"),d("apple"))
#输出
APPLE True.

3. functools.partial

语法:functools.partial(func, *args, **keywords)
functools.partial适用于函数冻结参数的情况。冻结参数是指我们欲调用的函数中的部分或全部参数已经固定,只需补齐剩下的参数调用即可。可以按照word编辑中的格式刷来理解。

from functools import partial
from unicodedata import normalize
clean=partial(normalize,"NFC")#字符串格式化,我觉得很像格式刷啊。
e="café"
f="cafe\u0301"
print(e==f) #False
print(clean(e)==clean(f))#True

使用技巧总结:operator中的itemgetter、attrgetter和functools.partial在使用上都需要先构建类似正则表达式的compile partern,即构建对应的itemgetter,attrgetter和partial,然后在partern基础上传入待处理对象。以刚才的partial举例就是clean(e),而不是e(clean)。


Hanwencheng
163 声望5 粉丝

Be yourself.