未整理--20190315——函数式编程Functional Programming-学习笔记——廖雪峰老师的教程

函数是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 函数名也是变量
未整理--20190315——函数式编程Functional Programming-学习笔记——廖雪峰老师的教程_第1张图片
1.3 传入函数
未整理--20190315——函数式编程Functional Programming-学习笔记——廖雪峰老师的教程_第2张图片
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。
未整理--20190315——函数式编程Functional Programming-学习笔记——廖雪峰老师的教程_第3张图片

  • reduce( )的用法
    reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算
    未整理--20190315——函数式编程Functional Programming-学习笔记——廖雪峰老师的教程_第4张图片
    未整理--20190315——函数式编程Functional Programming-学习笔记——廖雪峰老师的教程_第5张图片
    将列表[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)
    

未整理--20190315——函数式编程Functional Programming-学习笔记——廖雪峰老师的教程_第6张图片

#整合成函数——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的作用是把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
    

你可能感兴趣的:(python学习记录)