导语:本文章记录了本人在学习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)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。