函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务——
这种分解可以称之为面向过程的程序设计——函数就是面向过程的程序设计的基本单元。
函数式编程Functional Programming——虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算
我们首先要搞明白计算机(Computer)和计算(Compute)的概念。
在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。(对应到编程语言——越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言)
而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。(对应到编程语言——越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。)
函数式编程functional programming就是一种抽象程度很高的编程范式
纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用
允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
(一)高阶函数(High-order function)
编写高阶函数,就是让函数的参数 能够 接收 别的函数。
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式
1.1 变量可以指向函数
f = abs
f
结论:函数本身也可以赋值给变量,即:变量可以指向函数。
f(-10)
10
说明变量f现在已经指向了abs函数本身。直接调用abs()函数和调用变量f()完全相同。
1.2 函数名也是变量
1.3 传入函数
1.4 map( )和reduce( )是python的2个内建函数
map()函数接收两个参数,一个是函数,一个是Iterable。
map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator(迭代器)返回。
def f(x):
... return x*x
...
r=map(f,[1,2,3,4,5,6,7,8,9])
type(r) #但是教程中提到结果r是一个Iterator迭代器
list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
reduce( )的用法
reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算
将列表[1,3,5,7,9]变成数字13579,使用reduce函数就非常有用:
from functools import reduce
def fn(x,y):
return x*10+y
reduce(fn,[1,3,5,7,9])
13579
#计算效果示意reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
#整合成函数——str2int()
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
# 这里的digits十分巧妙!!!!!!!!这个特殊的dict可以建立字符'0-9'与数字之间的映射关系
def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))
#输出结果:
str2int('13579')
13579
str2int('147587')
147587
#利用lambda进一步整理
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x,y:x*10+y, map(char2num,s))
#输出结果
str2int('12345')
12345
str2int('12045')
12045
———————————————————————————————————————————————————
#课后练习1(checked)
#利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字
#输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']:
#困扰我的地方的知识点是关于+的拼接作用
#以及大写upper()、小写函数lower()
('NaYnCMe')[0].upper()+('NaYnCMe')[1:].lower()
#输出为'Nayncme'
#map不用从某个包导入
def gfnm(name):
def normalize(nm):
nm=nm[0].upper()+nm[1:].lower()
return nm
return list(map(normalize,name))
#map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回
#输出结果
gfnm(['adam', 'LISA', 'barT'])
['Adam', 'Lisa', 'Bart']
gfnm(['adAm', 'LIsA', 'bART'])
['Adam', 'Lisa', 'Bart']
————————————————————————————————————————————
#练习2(checked)
#Python提供的sum()函数可以接受一个list并求和
def qiuhe(L):
s=0
for i in L:#但是对于list中嵌套了list的列表来说就会报错
s=s+i
return s
#注意return的位置,这里的return位置是错了一次之后才对的。
#运行结果
qiuhe((1,2,3,4))
10
qiuhe((1,10,20))
31
qiuhe((1,1000,20))
1021
————————————————————————————————————————————
#课后练习3(checked)
#请编写一个prod()函数,可以接受一个list并利用reduce()求积:
from functools import reduce
def prod(L):
def ji(x,y):
return x*y
return reduce(ji, L)
#运行结果
from JJJtest2 import prod
prod([1,2,3])
6
prod([1])
1
prod([1,2,3,4])
24
prod([1,2,5,10])
100
——————————————————————————————
#课后练习4(checked)
#利用map和reduce编写一个str2float函数,把字符串'123.456'转换成浮点数123.456:
#难点:没有想到运用str.index('.')来定位小数点的索引
from functools import reduce
def str2float(s):
def fn(x,y):
return x*10+y
n=s.index('.')
————————————————————————————
#!!!难点:没有想到运用str.index('.')来定位小数点的索引!!!!!!!!!!
s='123.456'
n=s.index('.')
n
3
n=s.index('6') #同样可用于定位 6这个字符所在的索引
n
6
n=s.index('2')
n
1
————————————————————————————————
s1=list(map(int,[x for x in s[:n]]))
s2=list(map(int,[x for x in s[n+1:]]))
return reduce(fn,s1)+reduce(fn,s2)/(10**(len(s)-n-1))
#这里对小数点的位置进行了更精细的处理,比‘123.456’转成123.456更加灵活化了小数点的位置
#输出结果
str2float('123.45567445')
123.45567445
str2float('123868694933.45567445')
123868694933.45567
str2float('18694933.45567445')
18694933.45567445
str2float('123.456')
123.456
str2float('1233333.456')
1233333.456
str2float('56778.456')
56778.456
(二)返回函数
filter()用于过滤序列,filter()也接收一个函数和一个序列——把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
2.1 filter()用于过滤序列,从一个序列中筛出符合条件的元素
#filter()也接收一个函数和一个序列
#————把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素
#由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素
def is_odd(n):
return n%2==1
is_odd(3)
True
list(filter(is_odd,[1,2,4,5,6,9,10,15]))
[1, 5, 9, 15]
#把一个序列中的空字符串删掉
def not_empty(s):
return s and s.strip()
#strip()函数用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
#注意:该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。
list(filter(not_empty,[‘A’,’’,‘B’,None,‘C’,’ ']))
[‘A’, ‘B’, ‘C’]
#注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list
———————————————————
#利用python生成素数
#构造一个从3开始的奇数序列
#这是一个generator并且是一个无限序列
def _odd_iter():
n=1
while True:
n=n+2
yield n
————————————————————
#定义一个筛选函数
#divisible 可被整除的、可分的
def _not_divisible(n):
return lambda x: x%n>0 #return为True时说明该x不能被n整除
————————————————————
#最后,定义一个生成器,不断返回下一个素数:
def primes():
yield 2
it=_odd_iter( )# 从3开始的奇数序列(3、5、7、9、…)
while True:
n=next(it)
yield next
it=filter(_not_divisible(n),it) # 构造新序列
#这个生成器先返回第一个素数2,然后,利用filter()不断产生筛选后的新的序列。
#由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件:
#打印1000以内的素数:
for n in primes():
if n<1000:
print(n)
else:
break
#注意到Iterator是惰性计算的序列,所以我们可以用Python表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。
——————————————————————————————————————
#课后练习:
#回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:
s
‘123456’
s[::-1] # 倒序播放元素
‘654321’
s[::2]
‘135’
s[::-2]
‘642’
——————————————————————————————————————
#输入的s为数,所以要先用str()函数将int转换为str
#再利用slice功能中的str[::-1]将元素倒叙
def huishu(s):
return int(str(s))==int(str(s)[::-1])
#输出结果
huishu(123321)
True
huishu(909909909)
True
2.2 sorted——排序算法
无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小
数字,我们可以直接比较
如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来
#sorted(list)
sorted([36,5,-12,9,-21])
[-21, -12, 5, 9, 36]
sorted([36,5,-12,9,-21],reverse=True)# 从大到小
[36, 9, 5, -12, -21]
#sorted()函数也是一个高阶函数
#它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:
sorted([36,5,-12,9,-21],key=abs)
[5, 9, -12, -21, 36]
sorted([36,5,-12,9,-21],key=abs,reverse=True)
[36, -21, -12, 9, 5]
#字符串str排序的例子
sorted(['bob','about','Zoo','Credit'])
['Credit', 'Zoo', 'about', 'bob']
#默认情况下对str排序是按照A-Za-z即ASCII大小比较的
#如果想要忽略大小写——则可以都变成小写(大写)再比较
sorted([‘bob’,‘about’,‘Zoo’,‘Credit’],key=str.lower)
[‘about’, ‘bob’, ‘Credit’, ‘Zoo’]
sorted([‘bob’,‘about’,‘Zoo’,‘Credit’],key=str.lower,reverse=True)
[‘Zoo’, ‘Credit’, ‘bob’, ‘about’]
#课后作业:假设我们用一组tuple表示学生名字和成绩:
#L = [(‘Bob’, 75), (‘Adam’, 92), (‘Bart’, 66), (‘Lisa’, 88)]
#请用sorted()对上述列表分别按名字排序:
def by_name(t):
return t[0]
#再按成绩从高到低对上述列表分别排序:
def by_score(t):
return t[1]
L=[(‘Bob’, 75), (‘Adam’, 92), (‘Bart’, 66), (‘Lisa’, 88)]
#key指定的函数将作用于list的每一个元素上
#对于L的每一个元素(‘Bob’, 75),key指定的函数比如by_name(t): return t[0]将作用在上面
#即取到了’Bob’
L3=sorted(L,key=by_name)
L3
[(‘Adam’, 92), (‘Bart’, 66), (‘Bob’, 75), (‘Lisa’, 88)]
L4=sorted(L,key=by_score,reverse=True)
L4
[(‘Adam’, 92), (‘Lisa’, 88), (‘Bob’, 75), (‘Bart’, 66)]
——————————————————————————————————————
2.3. 返回函数****加粗样式
#ordinary求和函数
def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax
#返回求和的函数
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum #返回的是sum()函数
#在这个例子中,我们在函数lazy_sum中又定义了函数sum
#并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量
#当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中
#这种称为“闭包(Closure)”的程序结构拥有极大的威力。
#当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
from JJJtest1 import lazy_sum
f=lazy_sum(1,3,5,7,9)
f
#输出为
#调用函数f时,才真正计算求和的结果:
f()
25
#请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数
#即使传入相同的参数:f1()和f2()的调用结果互不影响。
f1=lazy_sum(1,3,5,7,9)
f2=lazy_sum(1,3,5,7,9)
f1==f2
False
#闭包
#注意到返回的函数在其定义内部引用了局部变量args
#所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用
#所以,闭包用起来简单,实现起来可不容易。
#另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:
def count():
fs=[]
for i in range(1,4):
def f():
return i*i
fs.append(f)
return fs
f1,f2,f3=count()
——————————————————————————————————————
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431835236741e42daf5af6514f1a8917b8aaadff31bf000
#小结
#一个函数可以返回一个计算结果,也可以返回一个函数。
返回一个函数时,牢记该函数并未执行,
返回函数中不要引用任何可能会变化的变量。
——————————————————————————————————————
#课后练习:
def createCounter():
def counter():
return 1
return counter
(三)匿名函数
#Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。
list(map(lambda x: x*x, [1,2,3,4,5,6,7,8,9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
#匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
#用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突
#此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
f=lambda x:x*x
f
f(5)
25
#这里是将匿名函数作为返回值返回
def build(x,y):
return lambda:xx+yy
#请用匿名函数改造下面的代码:
def is_odd(n):
return n % 2 == 1
L = list(filter(is_odd, range(1, 20)))
#改造
L=list(filter(lambda x: x%2==1, range(1,20)))
L
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
(四)装饰器
(没认真看,太困了)
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318435599930270c0381a3b44db991cd6d858064ac0000
(五)偏函数(partial function)
在介绍函数参数的时候,我们讲到,
int('12345')
12345
#但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:
int('12345',base=8)
5349
int('12345',base=16)
74565
int('12345',16)
74565
通过设定参数的默认值,可以降低函数调用的难度
#使用默认参数
def int2(x,base=2):
return int(x, base)
int2('1000000')
64
int2('1010101')
85
#注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2
#但也可以在函数调用时传入其他值:
int2('1000000', base=10)
1000000
而偏函数也可以做到这一点——可以降低函数调用的难度
import functools
int2=functools.partial(int, base=2)
int2('1000000')
64
int2('1010101')
85
#所以functools.partial的作用是把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。