【Python】【函数式编程】

#【练习】

请定义一个函数quadratic(a, b, c),接收3个参数,返回一元二次方程:

ax2 + bx + c = 0

的两个解。

提示:计算平方根可以调用math.sqrt()函数:

>>> import math
>>> math.sqrt(2)
1.4142135623730951
# -*- coding: utf-8 -*-

import math

def quadratic(a, b, c):

# 测试:
print('quadratic(2, 3, 1) =', quadratic(2, 3, 1))
print('quadratic(1, 3, -4) =', quadratic(1, 3, -4))

if quadratic(2, 3, 1) != (-0.5, -1.0):
    print('测试失败')
elif quadratic(1, 3, -4) != (1.0, -4.0):
    print('测试失败')
else:
    print('测试成功')
import math
# 解答
def quadratic(a,b,c):
for i in (a,b,c):
if not isinstance(i,(int,float)):
raise TypeError('bad operand type')
if a == 0:
x = -c/b
return x
delta = b*b - 4*a*c
if delta < 0:
print ('此方程无解')
elif delta > 0:
x1 = (-b+math.sqrt(delta))/(2*a)
x2 = (-b-math.sqrt(delta))/(2*a)
return x1,x2
elif delta == 0:
x1=x2= -b/(2*a)
return x1,x2
print (quadratic(1,4,1))
print (quadratic(0,2,-1))
print (quadratic('a',4,1))
# 结果

(-0.2679491924311228, -3.732050807568877)
0.5
Traceback (most recent call last):
File "/Users/suren/PycharmProjects/liaoxuefeng_test1/test_one.py", line 22, in <module>
print (quadratic('a',4,1))
File "/Users/suren/PycharmProjects/liaoxuefeng_test1/test_one.py", line 6, in quadratic
raise TypeError('bad operand type')
TypeError: bad operand type


#【练习】2

利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法:

【解答】

def trim(string):
if not isinstance(string,(str,)):
raise TypeError('arg error')
#左判断
while ' ' == string[0]:
string = string[1:]
#右判断
while ' ' == string[-1]:
string = string[:-1]
return string

 









# 【【函数式编程】】
"""
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。

我们首先要搞明白计算机(Computer)和计算(Compute)的概念。

在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。

而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。

对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
"""
#【高阶函数】

#变量可以指向函数
print (abs(-10))
print(abs) #<built-in function abs>


def add(x,y,f):
return f(x) + f(y)
x = -5
y = 6
f = abs
print(add(x,y,f)) #11

# 【高阶函数】【map】
def f(x):
return x * x
r = map(f,[1,2,3,4,5])
print(list(r)) #[1, 4, 9, 16, 25]
print(r) #<map object at 0x1022454a8>
#一行代码就是
print(list(map(f,[1,2,3,4])))#[1, 4, 9, 16, 25]

#【高阶函数】【reduce】
from functools import reduce
def add(x,y):
return x + y
r = reduce(add,[1,2,3,4])
print(r) #10
#当然,求和运算也可以用python内建函数sum,没必要动用reduce.
#但是,如果要把序列[1,3,5,7,9]变成整数13579,reduce就可以派上用场
from functools import reduce
def fn(x,y):
return 10 * x + y
print(reduce(fn,[1,3,5,7,9])) #13579

# str 转 int
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,"0":0}

def str2int(s):
def fn(x,y):
return 10 * x + y
def char2num(ss):
return DIGITS[ss]
return reduce(fn,map(char2num,s))
print(str2int("13579")) #13579

# 还可以用lambda函数进一步简化
def str2int(s):
return reduce(lambda x,y:10 * x + y,map(lambda a: DIGITS[a],s))
print(str2int("2468")) #2468

# 练习1:利用map函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。
def nomalize(name):
return name.capitalize()
L1 = ["adam","LISA","barT"]
L2 = list(map(nomalize,L1))
print(L2) #['Adam', 'Lisa', 'Bart']

#练习2:python提供的sum()函数可接收一个list并求和。请编写一个prod()函数,可接收一个list并通过reduce求积
def prod(L):
return reduce(lambda x,y:x * y,L)
print('1*3*5*4=',prod([1,3,5,4]))
if prod([1,3,5,4]) == 60:
print("测试成功!")
else:
print("测试失败!")
'''
1*3*5*4= 60
测试成功!
'''
#练习3:利用map reduce编写str2float,把字符串"123.456"转成浮点数123.456
#方法1 我自己写的
from functools import reduce
def str2float1(s):
digits = {"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"0":0}
def fn(x,y):
return 10 * x + y
def char2num(s):
return digits[s]
return reduce(fn,map(char2num,s.split(".")[0])) + reduce(fn,map(char2num,s.split(".")[1]))/10**len(s.split(".")[1])
print('str2float1(\'123,456\')=',str2float1('123.456'))
if str2float1('123.456') == 123.456:
print('测试成功!')
else:
print('测试失败!')
'''
str2float1('123,456')= 123.456
测试成功!
'''

# 方法2:网上的,比较简洁
from functools import reduce
def str2float2(s):
def fn(x,y):
return 10*x + y
n = s.index(".")
s1 = list(map(int,s[:n]))
s2 = list(map(int,s[n+1:]))
return reduce(fn,s1) + reduce(fn,s2)/10**len(s2)
print('str2float(\'789.123\')=',str2float2('789.123'))
if str2float2('789.1234') == 789.1234:
print('测试成功!')
else:
print('测试失败!')
'''
str2float('789.123')= 789.123
测试成功!
'''

# 【高阶函数】【filter】
# and & or
print(0 or 1) #1
print(1 or 0) #1
print(False or True) #True
print(True or 0) #True
print(False or 0) #0
print('***********')
print(0 and 1) #0
print(1 and 3) #3
print(1 and 0) #0
print(True and False) #False
print(False and 7) #False
print(1 and 7) #7
print(None and None.strip())#None

# 把一个序列中的空字符串删掉
def not_empty(s):
return s and s.strip()
print(list(filter(not_empty,['A','','B',None,'C',' ']))) #['A','B','C']

# 用filter求素数
'''
【分析】
计算素数的一个方法是埃氏筛法,它的算法理解起来非常简单:
首先,列出从2开始的所有自然数,构造一个序列:
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
取新序列的第一个数5,然后用5把序列的5的倍数筛掉:
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
不断筛下去,就可以得到所有的素数。
'''
# 分三步。1 先构造一个从3开始的奇数数列 2 定义一个筛选函数 3 定义一个生成器,不断返回下一个素数
def _odd_iter():
n = 1
while True:
n = n + 2
yield n

def _not_divisible(n):
return lambda x:x %n > 0

def primes():
yield 2
it = _odd_iter() # 初始数列
while True:
n = next(it)
yield n
it = filter(_not_divisible(n),it)

# 由于primes()也是一个无限序列,所以调用时需设置一个退出循环的条件:
for n in primes():
if n < 10:
print(n)
else:
break
'''
2
3
5
7
'''

# 练习 回数指从左往右和从右往左都是一样的数。利用filter()筛选出 回数
output = list(filter(lambda n: str(n)[0:] == str(n)[::-1],range(1,100)))
print(output)
if output == [1,2,3,4,5,6,7,8,9,11,22,33,44,55,66,77,88,99]:
print('测试成功!')
else:
print('测试失败!')


# 【高阶函数】【sorted】
print(sorted([36,5,-12,9,-21])) #[-21, -12, 5, 9, 36]
print(sorted([36,5,-12,9,-21],key=abs)) #[5, 9, -12, -21, 36]
print(sorted(['bob','about','Zoo','Credit'])) #['Credit', 'Zoo', 'about', 'bob'] 【备注】字符串排序,默认按照ASCII大小,由于大写字母在小写字母之前
#如果想忽略大小写
print(sorted(['bob','about','Zoo','Credit'],key=str.lower)) #['about', 'bob', 'Credit', 'Zoo']
#用大写函数同样适用
print(sorted(['bob','about','Zoo','Credit'],key=str.upper)) #['about', 'bob', 'Credit', 'Zoo']
#反向排序
print(sorted(['bob','about','Zoo','Credit'],key=str.upper,reverse=True))#['Zoo', 'Credit', 'bob', 'about']

# 练习:用一组tuple表示学生名字和成绩
L = [('Bob',75),('Adam',92),('Bart',66),('Lisa',88)]
#请用sorted对上述列表分别按照名字和成绩排序
def by_name(t):
return t[0].lower()
def by_score(t):
return t[1]
print('按照名字排序结果: ',sorted(L,key=by_name)) #按照名字排序结果: [('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
print('按照成绩排序结果: ',sorted(L,key=by_score)) #按照成绩排序结果: [('Bart', 66), ('Bob', 75), ('Lisa', 88), ('Adam', 92)]

# 【【函数式编程】】【返回函数】
#函数作为返回值
#实现一个可变参数的求和。通常情况下,求和函数这样定义
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
f = lazy_sum(1,3,5,7,9)
print(f) #<function lazy_sum.<locals>.sum at 0x101a5a048>
print(f()) #25
# 【注意】每次调用lazy_sum()时,都会返回一个新的函数,即使传入相同的参数
f1 = lazy_sum(1,3,5)
f2 = lazy_sum(1,3,5)
print(f1 == f2) #False f1() f2()调用结果互不影响

# 闭包
def count():
fs = []
for i in range(1,4):
def f():
return i * i
fs.append(f)
return fs
f1,f2,f3 = count()
print(f1())
print(f2())
print(f3())
'''
9
9
9
全部都是9,原因在于,返回的函数引用了变量i,但它并非立即执行。等到三个函数都返回时,它们引用的变量已经编程了3,因此最终结果是9。
'''
#【注意】返回闭包牢记,返回函数不要引用任何循环变量,或者后续会发生变化的变量
#如果一定要引用循环变量,可再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何修改,已绑定到函数参数的值不变。
def count2():
def f(j):
def g():
return j * j
return g
fs = []
for i in range(1,4):
fs.append(f(i))
return fs
f1,f2,f3 = count2()
print(f1())
print(f2())
print(f3())

'''
1
4
9
'''
# 方法内的变量,在方法帧未走完之前,一直有效 【备注】我也不知道这么形容对不对。。。只是我自己对此理解的一种方式。就像
import time
def now():
timeL = []
time1 = time.time()
time2 = time1 + 1000
time3 = time2 + 1000
timeL.append(time1)
timeL.append(time2)
timeL.append(time3)
def g(x):
return timeL[x]
return g
bb = now()
for i in range(0,3):
print(bb(i))
'''
1531637726.270685
1531638726.270685
1531639726.270685
'''

#练习:利用闭包返回一个函数,每次调用它都会返回递增整数
#方法1
def createCounter():
a = [0]
def counter():
a[0] = a[0] + 1
return a[0]
return counter
createrA = createCounter()
print(createrA(),createrA(),createrA())
#方法2
a = 0
def createCounter2():
def counter():
global a
a = a + 1
return a
return counter
createrB = createCounter2()
print(createrB(),createrB(),createrB())


#【装饰器】
def now():
print('2018-07-15')
f = now
print(now.__name__) #now
print(f.__name__) #now

# 两层嵌套
'''
现在,假设要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能
的方式,称之为"装饰器"(Decorator).
本质上,decorator就是一个返回函数的高阶函数。
'''
def log(func):
def wrapper(*args,**kwargs):
print('call %s():'%func.__name__)
return func(*args,**kwargs)
return wrapper
@log
def now():
print('2018-07-15')
now()
print(now.__name__)
'''
call now():
2018-07-15
wrapper
'''

'''
把@log放到now()函数的定义处,相当于执行now = log(now),由于log()是一个decorator,返回一个函数,原来的now()函数仍然存在,只是
现在同名的now变量指向了新的函数,于是调用now()将执行新的函数
'''
#decorator本身需要传入参数。
def log(text):
def decorator(func):
def wrapper(*args,**kwargs):
print('%s %s():' % (text,func.__name__))
func(*args,**kwargs)
return wrapper
return decorator
@log('execute')
def now():
print('2018-07-15')
now()
#【解析】相当与定义now = log('execute')(now)

#经过decorator装饰的函数,名字都会编程 wrapper ,为避免以来函数签名的的代码执行错误,不需要编写wrapper.__name__ = func.__name__,python内置的functools.wraps就是做这个的。所以,一个完整的decorator如下。
import functools

def log(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print('call %s():' % func.__name__)
return func(*args,**kwargs)
return wrapper

#或者带参数的decorator
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print('%s %s():' % (text,func.__name__))
return func(*args,**kwargs)
return wrapper
return decorator()

#练习 设计一个decorator,可作用于任何函数上,并打印该函数的执行时间。
import time,functools
def printcalltime(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
end = time.time()
print('%s execute time = %s ms' % (func.__name__,(end - start)*1000) )
return res
return wrapper

@printcalltime
def print1():
time.sleep(1)
print (1)

print1()
print(print1.__name__)
'''
1
print1 execute time = 1002.7890205383301 ms
print1
'''

# 练习
'''
写出一个@log的decorator,使它既支持:

@log
def f():
pass

又支持:

@log('execute')
def f():
pass
'''
def log2(text):
if callable(text) == False:
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('execute %s for %s ' % (text,func.__name__))
return func(*args,**kwargs)
return wrapper
return decorator
else:
@functools.wraps(text)
def wrapper(*args,**kwargs):
return text(*args,**kwargs)
return wrapper
@log2
def now1():
print ("hello now1")

@log2('too late')
def now2():
print ("hello now2")

now1()
now2()
'''
hello now1
execute too late for now2
hello now2
'''

# 【偏函数】
#int函数,默认是10进制。
print(int('12345')) #12345
#int函数还提供base参数,默认是10,如果传入,则按照参数的进制进行转换
print(int('12345',base=8)) #5349
print(int('12345',8)) #5349
print(int('11',base=2)) #3
#假设要大量转换二进制字符串,每次都传入int(x,base=2)太麻烦,于是想到定义一个int2()函数,默认把base=2传进去
def int2(x,base=2):
return int(x,base)
#functools.partial就是创建偏函数的,不需要自己定义int2()
import functools
int2 = functools.partial(int,base=2)
print(int2('100')) #4
print(int2('101')) #5
'''
创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数,当传入:

int2 = functools.partial(int, base=2)
实际上固定了int()函数的关键字参数base,也就是:

int2('10010')
相当于:

kw = { 'base': 2 }
int('10010', **kw)
当传入:

max2 = functools.partial(max, 10)
实际上会把10作为*args的一部分自动加到左边,也就是:

max2(5, 6, 7)
相当于:

args = (10, 5, 6, 7)
max(*args)
结果为10。
'''























posted @ 2018-07-15 17:12  素人渔芙2017  阅读(257)  评论(0编辑  收藏  举报