一个函数就是将一些语句集合在一起的部件,它们能够不止一次地在程序中运行。函数的主要作用:
一般地,函数讲的流程是:告诉你怎样去做某事,而不是让你使用它去做的事。
Python 中的 def
语句实际上是一个可执行的语句:当它运行时,它会创建一个新的函数对象并将其赋值给一个变量名。因为它是一个语句,一个 def
可以出现任一语句可以出现的地方——甚至是嵌套在其他的语句中。例如,下面的使用也是合法的:
test = ...
if test:
def func():
...
else:
def func():
...
...
func()
def
在运行时才进行评估,而在 def
之中的代码在函数调用后才会评估。
下面以一个 times()
函数为例来说明:
def times(x, y):
return x * y
times(2, 5)
times(3.14, 4)
times(‘Ni‘, 4)
从上面我们可以看出:times
函数中表达式 x*y
的意义完全取决于 x
与 y
的对象类型。这种依赖类型的行为称为多态。任何支持函数所预期的接口 (函数所执行的一组方法和表达式运算符) 都能够被函数所支持。
当你在一个程序中使用变量名时,Python 创建、改变或查找变量名都是在所谓的命名空间 (作用域,一个保存变量名的地方) 中进行的。也就是说,在代码中变量名被赋值的位置决定了这个变量名能被访问到的范围。包括作用域的定义在内,所有变量名都是在 Python 赋值时产生的。
global
或者 nonlocal
,否则均为本地变量对对象的就地更改(in-place changes)不将变量归类为局部变量;只有实际的名称分配。例如, 如果将名称 L
分配给模块顶层的列表, 函数中的语句 L = X
会将 L
作为本地分类, 但 L.append(X)
不会。在后一种情况下, 我们正在更改 L
引用的 list
对象, 而不是 L
本身在全局范围中像往常一样被发现, 并且 Python 在不需要全局 (或非本地) 声明的情况下愉快地修改它。与往常一样, 它有助于保持名称和对象之间的区分清楚: 更改对象不是对名称的赋值。
变量名解析:LEGB 原则
闭合 (closure) 或者工厂函数:一个能够记住嵌套作用域的变量值的函数,尽管那个作用域或许已经不存在了。
例如,工厂函数有时用于需要及时生成事件处理、实时对不同情况进行反馈的程序中:
def maker(N):
def action(X):
return X ** N
return action
f = maker(2) # pass 2 to N
f
f(3)
f(4)
当函数的参数不确定时,可以使用*args
和**kwargs
.
*args
是可变的 positional arguments 列表(表示任何多个无名参数,它是一个 tuple)**kwargs
是可变的 keyword arguments 列表(表示关键字参数,它是一个 dict)*args
必须位于 **kwargs
之前,因为 positional arguments 必须位于 keyword arguments 之前。def print_everything(*args):
for count, thing in enumerate(args):
print(("%d. %s" % (count, thing)))
print_everything(‘apple‘, ‘banana‘, ‘cabbage‘)
def table_things(**kwargs):
for name, value in kwargs.items():
print(name, "=", value)
table_things(apple=‘fruit‘, cabbage=‘vegetable‘)
def foo(*args,**kwargs):
print(‘args=‘,args)
print(‘kwargs=‘,kwargs)
print(‘ ‘)
if __name__==‘__main__‘:
foo(1,2,3,4)
foo(a=1,b=2,c=3)
foo(1,2,3,4,a=1,b=2,c=3)
foo(‘a‘,1,None,a=1,b=‘2‘,c=3)
python 可以自动将参数解析后再与调用的函数匹配。
def print_three_things(a, b, c):
print("a =", a, "& b =", b, "& c =", c)
mylist = [‘aardvark‘, ‘baboon‘, ‘cat‘]
print_three_things(*mylist)
# 还有一个很漂亮的用法,就是创建字典:
def kw_dict(**kwargs):
return kwargs
print(kw_dict(a=1, b=2, c=3))
a = (1, 2)
print(id(a))
a += (3, 4)
id(a)
return
语句:一般地,你需要力求让函数独立于它外部的东西。参数和 return
语句通常就是隔离对代码中少数醒目位置的外部依赖关系的最好方法。简言之,我们应该竭力使函数和其他编程组件中的外部依赖性最小化。函数的自我包含性越好,它就越容易被理解、复用和修改。
递归通常把一个大型复杂的问题,转化为一个与原问题相似的,且规模较小的问题来求解。
递归策略只需少量的程序就可以描述出解题的所需的多次重复计算,大大减少了程序的代码量。
递归函数一般包括:
def Factorial(a):
result = a
for i in range(1, a):
result *= i
return result
# 调用函数
print(Factorial(10))
# 递归函数
def Recursion(a):
if a == 1:
return 1
else:
return a*Recursion(a-1)
Recursion(10)
def seq_sum(L):
if not L:
return 0
else:
return L[0] + seq_sum(L[1:])
seq_sum([1, 2, 3, 4, 5])
为了更好的理解上面的代码,我们可以借助 print
函数:
def seq_sum(L):
print(‘*‘*30)
print(L)
if not L:
s = 0
else:
s = L[0] + seq_sum(L[1:])
print(‘>‘*30)
print(L)
print(‘-‘*30)
print(s)
return s
seq_sum([1, 2, 3, 4, 5])
从打印的结果可以看出:Python 中的递归是以堆栈的形式来实现的。我们可以利用解包技术来进一步简化递归函数:
def seq_sum(L):
return 0 if not L else L[0] + seq_sum(L[1:]) # 仅仅适用数值型
def seq_sum(L):
return L[0] if len(L) == 1 else L[0] + seq_sum(L[1:]) # 适用更多的类型
# 更好的写法
def seq_sum(L):
first, *rest = L
return first if not rest else first + mysum(rest)
参考资料:
函数式编程使用一系列的函数解决问题。函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态。任何情况下,使用相同的参数调用函数始终能产生同样的结果。
在一个函数式的程序中,输入的数据“流过”一系列的函数,每一个函数根据它的输入产生输出。函数式风格避免编写有“边界效应”(side effects)的函数:修改内部状态,或者是其他无法反应在输出上的变化。完全没有边界效应的函数被称为“纯函数式的”(purely functional)。避免边界效应意味着不使用在程序运行时可变的数据结构,输出只依赖于输入。
可以认为函数式编程刚好站在了面向对象编程的对立面。对象通常包含内部状态(字段),和许多能修改这些状态的函数,程序则由不断修改状态构成;函数式编程则极力避免状态改动,并通过在函数间传递数据流进行工作。但这并不是说无法同时使用函数式编程和面向对象编程,事实上,复杂的系统一般会采用面向对象技术建模,但混合使用函数式风格还能让你额外享受函数式风格的优点。
函数式的风格通常被认为有如下优点:
支持函数式编程的语言通常具有如下特征,大量使用这些特征的代码即可被认为是函数式的:
函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。
理解函数式编程的关键,就是理解范畴论。它是一门很复杂的数学,认为世界上所有的概念体系,都可以抽象成一个个的"范畴"(category)。
1 范畴的概念
"范畴就是使用箭头连接的物体。"彼此之间存在某种关系的概念、事物、对象等等,都构成"范畴"。随便什么东西,只要能找出它们之间的关系,就能定义一个"范畴"。
箭头表示范畴成员之间的关系,正式的名称叫做"态射"(morphism)。范畴论认为,同一个范畴的所有成员,就是不同状态的"变形"(transformation)。通过"态射",一个成员可以变形成另一个成员。
2 数学模型
既然"范畴"是满足某种变形关系的所有对象,就可以总结出它的数学模型。
也就是说,范畴论是集合论更上层的抽象,简单的理解就是"集合 + 函数"
。
理论上通过函数,就可以从范畴的一个成员,算出其他所有成员。
3 范畴与容器
我们可以把"范畴"想象成是一个容器,里面包含两样东西。
本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。
在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他副作用。
函数式编程有两个最基本的运算:合成和柯里化。
函数的合成:
如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。
函数合成就是将那些管道(pipe)连了起来,让数据一口气从多个管道中穿过。
def f(x):
def g():
return x+1
return g()*5
f(3)
\(f(x)\)和\(g(x)\)合成为\(f(g(x))\),有一个隐藏的前提,就是\(f\)和\(g\)都只能接受一个参数。如果可以接受多个参数,比如\(f(x, y)\)和\(g(a, b, c)\),函数合成就非常麻烦。
这时就需要函数柯里化了。
所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。
函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。
函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。
它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。
一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。
map & filter & reduce
filter用于过滤列表对象,返回真值的原像,类似于逆映射
def f(x):
return x**2
def even(x):
return x%2==0
list(map(even,range(10)))
[True, False, True, False, True, False, True, False, True, False]
list(filter(even,range(15)))
[0, 2, 4, 6, 8, 10, 12, 14]
list(filter(f,range(10)))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
list(map(f,range(10)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
list(map(lambda x:x**2,range(10)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
一个函数有多个参数,我们希望能固定其中几个参数的值。
from functools import partial
def foo(a,b,c):
return a+b+c
foo2 = partial(foo, b=2)
foo2(a=1, c=3)
6
看上去这跟提供参数默认值的情况类似。但默认值只能固定
为单个值,而柯里化能通过泛化出很多个函数,每个函数用
不同的固定值,来看一下 应用场景
from functools import partial
bin2dec = partial(int, base=2)
hex2dec = partial(int, base=16)
原 int
方法的定义为:int( x[, base])
,base
参数的默认为 10
经过柯里化之后,可以用如下方式调用:
int(‘15‘) #=>15 using default base 10
print(bin2dec(‘01011‘)) #=>11
print(hex2dec(‘67‘)) #=>103
11
103
顾名思义,柯里化的逆过程。
将多个只含单个参数的函数模拟成一个多参数函数。
你可以像这样调用:foo(1)(4)或(foo(1))(4),都能得到正确的结果5
def foo(a):
def bar(b):
return a+b
return bar
foo(1)(4)
5
map
映射map()
是 Python 内置的高阶函数,它接收一个函数 f
和一个 可迭代对象,并通过把函数 f
依次作用在 可迭代对象 的每个元素上,并返回一个新的可迭代对象。
def f(x):
return x*x
list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
list(map( lambda x, y: x * y, [1, 2, 3], [4, 5, 6]))
[4, 10, 18]
def format_name(s):
s1=s[0:1].upper()+s[1:].lower();
return s1;
list(map(format_name, [‘adam‘, ‘LISA‘, ‘barT‘]))
[‘Adam‘, ‘Lisa‘, ‘Bart‘]
## `reduce` 递推
from functools import reduce
list(map( lambda x, y: x * y, [1, 2, 3], [4, 5, 6] ))
[4, 10, 18]
list(map( lambda x, y: ( x * y, x + y), [1, 2, 3], [4, 5, 6] ))
[(4, 5), (10, 7), (18, 9)]
m = 2
n = 5
reduce( lambda x, y: x * y, range( 1, n + 1 ), m )
```
240
filter
过滤器list(filter((lambda x: x>0), range(-5, 5)))
[1, 2, 3, 4]
list(zip( [1, 2, 3], [4, 5, 6] )) # 并行
[(1, 4), (2, 5), (3, 6)]
Python中的函数是对象,故函数可以被当做变量使用。
参考资料:
对已有的函数添加一些小功能,却又不希望对函数内容有太多的刚性的修改。
修饰器的本质: Python解释器在发现函数调用修饰器后,将其传入修饰器中,然后用返回的函数对象将自身完全替换。
一个函数可以被多个修饰器嵌套修饰:
@deco3
@deco2
@deco1
def df():
pass
等同于\(f=deco3(deco2(deco1(f)))\)
def deco(func): #修饰器deco
def wrappedFunc(): #内嵌函数wrappedFunc(所有对于传入函数的修饰逻辑都将在此内嵌函数中实现。)
return ‘Hello World_‘+func()
return wrappedFunc
# 在程序中若有函数需要修饰器修饰,只需在函数定义前使用“`@+修饰器名`”即可使用此修饰器。
@deco
def f(): # 调用修饰器
return ‘I am f‘
def g(): # 没有调用修饰器
return ‘I am g‘
print(f())
print(g())
Hello World_I am f
I am g
def deco(f):
def g():
return [f()[i] for i in range(5)]
return g
@deco
def h():
return [1,2,3,4,56,7,‘75s‘]
print(h())
[1, 2, 3, 4, 56]
在deco函数和wrappedFunc函数之间添加一个_
deco函数来处理deco函数传入的参数prefix,修饰器不会立即将待修饰的函数作为参数传入完成修饰,而是先做了一个预处理,返回了一个_
deco函数,而这个_
deco函数才是真正被f函数调用的修饰器。
def deco(prefix):
def _deco(func):
def wrappedFunc():
return prefix+func()
return wrappedFunc
return _deco
@deco(‘second_‘)
@deco(‘first_‘)
def f():
return ‘I am f‘
print(f()) # 等同于 f=deco(‘second_‘)(deco(‘fist_‘)(f))
second_first_I am f
原文:https://www.cnblogs.com/q735613050/p/7347181.html