函数基础

一个函数就是将一些语句集合在一起的部件,它们能够不止一次地在程序中运行。函数的主要作用:

最大化的代码重用和最小化代码冗余

流程的分解

一般地,函数讲的流程是:告诉你怎样去做某事,而不是让你使用它去做某事。

Python 中的 def 语句实际上是一个可执行的语句:当它运行时,它会创建一个新的函数对象并将其赋值给一个变量名。因为它是一个语句,一个 def 可以出现任一语句可以出现的地方——甚至是嵌套在其他的语句中。例如,下面的使用也是合法的:

test = ...

if test:

def func():

...

else:

def func():

...

...

func()

def 在运行时才进行评估,而在 def 之中的代码在函数调用后才会评估。

Python 中的多态

下面以一个 times() 函数为例来说明:

def times(x, y):

return x * y

times(2, 5)

结果:

10

times(3.14, 4)

结果:

12.56

times('Ni', 4)

结果:

'NiNiNiNi'

从上面我们可以看出:times 函数中表达式 x*y 的意义完全取决于 x 与 y 的对象类型。这种依赖类型的行为称为多态。任何支持函数所预期的接口 (函数所执行的一组方法和表达式运算符) 都能够被函数所支持。

作用域

当你在一个程序中使用变量名时,Python 创建、改变或查找变量名都是在所谓的命名空间 (作用域,一个保存变量名的地方) 中进行的。也就是说,在代码中变量名被赋值的位置决定了这个变量名能被访问到的范围。包括作用域的定义在内,所有变量名都是在 Python 赋值时产生的。

内嵌的模块是全局作用域

全局作用域的作用范围仅限于单个文件

每次对函数的调用都创建了一个新的本地作用域

赋值的变量名除非声明为 global 或者 nonlocal,否则均为本地变量

所有其他的变量名都可以归纳为本地、全局或者内置的

对函数的每次调用都会创建一个新的本地作用域

对对象的就地更改(in-place changes)不将变量归类为局部变量;只有实际的名称分配。例如, 如果将名称 L 分配给模块顶层的列表, 函数中的语句 L = X 会将 L 作为本地分类, 但 L.append(X) 不会。在后一种情况下, 我们正在更改 L 引用的 list 对象, 而不是 L 本身在全局范围中像往常一样被发现, 并且 Python 在不需要全局 (或非本地) 声明的情况下愉快地修改它。与往常一样, 它有助于保持名称和对象之间的区分清楚: 更改对象不是对名称的赋值。

Name Resolution: The LEGB Rule

变量名解析:LEGB 原则

Name assignments create or change local names by default.

Name references search at most four scopes: local, then enclosing functions (if any), then global, then built-in.

Names declared in global and nonlocal statements map assigned names to enclosing module and function scopes, respectively.

工厂函数

闭合 (closure) 或者工厂函数:一个能够记住嵌套作用域的变量值的函数,尽管那个作用域或许已经不存在了。

例如,工厂函数有时用于需要及时生成事件处理、实时对不同情况进行反馈的程序中:

def maker(N):

def action(X):

return X ** N

return action

f = maker(2) # pass 2 to N

f

.action(X)>

f(3)

9

f(4)

16

函数的不定长参数

当函数的参数不确定时,可以使用*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')

0. apple

1. banana

2. cabbage

def table_things(**kwargs):

for name, value in kwargs.items():

print(name, "=", value)

table_things(apple='fruit', cabbage='vegetable')

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)

args= (1, 2, 3, 4)

kwargs= {}

args= ()

kwargs= {'a': 1, 'b': 2, 'c': 3}

args= (1, 2, 3, 4)

kwargs= {'a': 1, 'b': 2, 'c': 3}

args= ('a', 1, None)

kwargs= {'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)

a = aardvark & b = baboon & c = cat

# 还有一个很漂亮的用法,就是创建字典:

def kw_dict(**kwargs):

return kwargs

print(kw_dict(a=1, b=2, c=3))

{'a': 1, 'b': 2, 'c': 3}

函数设计原则

耦合性

对于输入使用参数并且对于输出使用 return 语句:一般地,你需要力求让函数独立于它外部的东西。参数和 return 语句通常就是隔离对代码中少数醒目位置的外部依赖关系的最好方法。

只有在真正必要的情况下使用全局变量:全局变量可能会引发依赖关系和计时问题,导致程序调试和修改的困难。

不要改变可变类型的参数,除非调用者需要这样做:这种耦合性会导致一个函数过于特殊和不友好。

聚合性

每一个函数都应该有一个单一的、统一的目标:在设计完美的情况下,每一个函数都应该做一件事:这件事可以用一个简单说明句来总结。(这样做可以提高代码的开发效率)

大小

每一个函数都应该相对较小:保持简单,提高代码的可读性和扩展性。

耦合

避免直接改变在另一个模块文件中的变量:提高代码的可读性和扩展性。

简言之,我们应该竭力使函数和其他编程组件中的外部依赖性最小化。函数的自我包含性越好,它就越容易被理解、复用和修改。

递归(程序调用自身的编程技巧)

递归通常把一个大型复杂的问题,转化为一个与原问题相似的,且规模较小的问题来求解。

递归策略只需少量的程序就可以描述出解题的所需的多次重复计算,大大减少了程序的代码量。

递归函数一般包括:

当函数直接结束是时有基本的返回值。

递归调用函数自身:包括一个或多个对自身函数的调用。

求 \(n!\)

def Factorial(a):

result = a

for i in range(1, a):

result *= i

return result

# 调用函数

print(Factorial(10))

3628800

# 递归函数

def Recursion(a):

if a == 1:

return 1

else:

return a*Recursion(a-1)

Recursion(10)

3628800

求序列的和

def seq_sum(L):

if not L:

return 0

else:

return L[0] + seq_sum(L[1:])

seq_sum([1, 2, 3, 4, 5])

15

为了更好的理解上面的代码,我们可以借助 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])

******************************

[1, 2, 3, 4, 5]

******************************

[2, 3, 4, 5]

******************************

[3, 4, 5]

******************************

[4, 5]

******************************

[5]

******************************

[]

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

[]

------------------------------

0

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

[5]

------------------------------

5

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

[4, 5]

------------------------------

9

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

[3, 4, 5]

------------------------------

12

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

[2, 3, 4, 5]

------------------------------

14

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

[1, 2, 3, 4, 5]

------------------------------

15

15

从打印的结果可以看出: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 + seq_sum(rest)

函数式编程(Functional programming)

参考资料:

什么是函数式编程?

函数式编程使用一系列的函数解决问题。函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态。任何情况下,使用相同的参数调用函数始终能产生同样的结果。

在一个函数式的程序中,输入的数据“流过”一系列的函数,每一个函数根据它的输入产生输出。函数式风格避免编写有“边界效应”(side effects)的函数:修改内部状态,或者是其他无法反应在输出上的变化。完全没有边界效应的函数被称为“纯函数式的”(purely functional)。避免边界效应意味着不使用在程序运行时可变的数据结构,输出只依赖于输入。

可以认为函数式编程刚好站在了面向对象编程的对立面。对象通常包含内部状态(字段),和许多能修改这些状态的函数,程序则由不断修改状态构成;函数式编程则极力避免状态改动,并通过在函数间传递数据流进行工作。但这并不是说无法同时使用函数式编程和面向对象编程,事实上,复杂的系统一般会采用面向对象技术建模,但混合使用函数式风格还能让你额外享受函数式风格的优点。

为什么使用函数式编程?

函数式的风格通常被认为有如下优点:

逻辑可证:

这是一个学术上的优点:没有边界效应使得更容易从逻辑上证明程序是正确的(而不是通过测试)。

模块化:

函数式编程推崇简单原则,一个函数只做一件事情,将大的功能拆分成尽可能小的模块。小的函数更易于阅读和检查错误。

组件化:

小的函数更容易加以组合形成新的功能。

易于调试:

细化的、定义清晰的函数使得调试更加简单。当程序不正常运行时,每一个函数都是检查数据是否正确的接口,能更快速地排除没有问题的代码,定位到出现问题的地方。

易于测试:

不依赖于系统状态的函数无须在测试前构造测试桩,使得编写单元测试更加容易。

更高的生产率:

函数式编程产生的代码比其他技术更少(往往是其他技术的一半左右),并且更容易阅读和维护。

如何辨认函数式风格?

支持函数式编程的语言通常具有如下特征,大量使用这些特征的代码即可被认为是函数式的:

函数是一等公民:

函数能作为参数传递,或者是作为返回值返回。这个特性使得模板方法模式非常易于编写,这也促使了这个模式被更频繁地使用。

以一个简单的集合排序为例,假设lst是一个数集,并拥有一个排序方法sort需要将如何确定顺序作为参数。

如果函数不能作为参数,那么lst的sort方法只能接受普通对象作为参数。这样一来我们需要首先定义一个接口,然后定义一个实现该接口的类,最后将该类的一个实例传给sort方法,由sort调用这个实例的compare方法,就像这样:

函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。

理解函数式编程的关键,就是理解范畴论。它是一门很复杂的数学,认为世界上所有的概念体系,都可以抽象成一个个的"范畴"(category)。

1 范畴的概念

"范畴就是使用箭头连接的物体。"彼此之间存在某种关系的概念、事物、对象等等,都构成"范畴"。随便什么东西,只要能找出它们之间的关系,就能定义一个"范畴"。

箭头表示范畴成员之间的关系,正式的名称叫做"态射"(morphism)。范畴论认为,同一个范畴的所有成员,就是不同状态的"变形"(transformation)。通过"态射",一个成员可以变形成另一个成员。

2 数学模型

既然"范畴"是满足某种变形关系的所有对象,就可以总结出它的数学模型。

所有成员是一个集合

变形关系是函数

也就是说,范畴论是集合论更上层的抽象,简单的理解就是"集合 + 函数"。

理论上通过函数,就可以从范畴的一个成员,算出其他所有成员。

3 范畴与容器

我们可以把"范畴"想象成是一个容器,里面包含两样东西。

值(value)

值的变形关系,也就是函数。

本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。

在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他副作用。

函数的合成与柯里化

函数式编程有两个最基本的运算:合成和柯里化。

函数的合成:

如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。

函数合成就是将那些管道(pipe)连了起来,让数据一口气从多个管道中穿过。

def f(x):

def g():

return x+1

return g()*5

f(3)

20

柯里化:

\(f(x)\) 和\(g(x)\) 合成为 \(f(g(x))\),有一个隐藏的前提,就是 \(f\) 和 \(g\) 都只能接受一个参数。如果可以接受多个参数,比如 \(f(x, y)\) 和 \(g(a, b, c)\),函数合成就非常麻烦。 这时就需要函数柯里化了。

所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

# 柯里化之前

def add(x, y):

return x + y

add(1, 2)

3

# 柯里化之后

def addX(y):

def f(x):

return x + y

return f

addX(2)(1)

3

有了柯里化以后,我们就能做到,所有函数只接受一个参数。后文的内容除非另有说明,都默认函数只有一个参数,就是所要处理的那个值。

函子

函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。

函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。

它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。

一般约定,函子的标志就是容器具有 map 方法。该方法将容器里面的每一个值,映射到另一个容器。 函子,一般还有有 filter 、reduce.

filter 用于过滤列表对象,返回真值的原像,类似于逆映射:

def f(x):

return x**2

def even(x):

return x % 2 == 0

print('map(even, range(10)):', list(map(even, range(10))))

print('filter(even, range(15)):', list(filter(even, range(15))))

print('filter(f, range(10)):', list(filter(f, range(10))))

print('map(f,range(10)):', list(map(f,range(10))))

print('map(lambda x:x**2,range(10)):', list(map(lambda x:x**2,range(10))))

map(even, range(10)): [True, False, True, False, True, False, True, False, True, False]

filter(even, range(15)): [0, 2, 4, 6, 8, 10, 12, 14]

filter(f, range(10)): [1, 2, 3, 4, 5, 6, 7, 8, 9]

map(f,range(10)): [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

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

反柯里化(Uncurrying)

顾名思义,是柯里化的逆过程。将多个只含单个参数的函数模拟成一个多参数函数。你可以像这样调用:foo(1)(4) 或 (foo(1))(4),都能得到正确的结果 5

def foo(a):

def bar(b):

return a + b

return bar

foo(1)(4)

5

python语言支持函数式编程_python 函数式编程学习笔记相关推荐

  1. python类中最大的_python类的学习笔记(一)

    python中一切皆对象,类型的本质就是类. 在python中,用变量表示特征,用函数表示技能,因而具有相同特征和技能的一类事物就是'类',对象是则是这一类事物中具体的一个. 声明函数的方法: def ...

  2. python求数列的积_python小游戏学习笔记4-2(列表【】,数列矩阵排列)

    x数值的单列写法: xlist = [1,2,3,4,5] for x in xlist: print(x) 关于len(长度)的用法:(结果为:5)(lens的作用就是把list 化为单个数字(内容 ...

  3. python turtle库输出文字_python turtle库学习笔记

    #绘制时钟#coding=utf-8 importturtle as ttfrom datetime import * #当前日期属于一周的第几天 defWeek(t): week= ["星 ...

  4. python画大对勾_python+selenium个人学习笔记8-获取信息和勾选框

    获取信息.定位一组元素和勾选框 一.获取信息 在实际操作中,我们通常通过获取title .URL和text等信息进行断言. 1.获取当前页面的title driver.title 2.获取当前页面的U ...

  5. python语言及其应用下载_Python语言及其应用 中文pdf完整版[13MB]

    <Python语言及其应用>介绍Python 语言的基础知识及其在各个领域的具体应用,基于新版本3.x.书中首先介绍了Python 语言的一些基本知识,然后介绍了在商业.科研以及艺术领域使 ...

  6. python语言支持中文字符作为量变_尔雅尔雅汉语揭秘章节考试答案

    运营是让产品持续产生产品价值和商业价值目的. [多选题]作为现代战略营销的核心,STP营销是企业制定有效营销组合策略的基础和前提,其内容包括 ( ) A. 市场进入 B. 细分市场 C. 目标营销 D ...

  7. NVIDIA可编程推理加速器TensorRT学习笔记(二)——实操

    NVIDIA可编程推理加速器TensorRT学习笔记(二)--实操 ​ TensorRT 是 NVIDIA 自家的高性能推理库,其 Getting Started 列出了各资料入口,如下: 本文基于博 ...

  8. 《python基础教程(第二版)》学习笔记 基础部分(第1章)

    <python基础教程(第二版)>学习笔记 基础部分(第1章) python常用的IDE: Windows: IDLE(gui), Eclipse+PyDev; Python(comman ...

  9. python segy格式地震数据读写包segyio学习笔记(二)

    python segy格式地震数据读写包segyio学习笔记(二) 最近大致搞明白了segyio读取叠后和叠前segy数据的方法,以及内部存储结构,以两段代码为例: 叠后数据读取.这是一个从给定时窗内 ...

  10. python word 操作 doc 文件格式转docx 格式 学习笔记

    python word 操作 doc 文件格式转docx 格式 学习笔记 from win32com import client as wc import time # TODO file_0 = & ...

最新文章

  1. android通讯录项目_Flutter高仿微信项目开源-具即时通讯IM功能
  2. numpy.random.randint详解
  3. C#和Java的代码转换工具(开源)CSharpJavaMerger Framework
  4. Django框架深入了解_05 (Django中的缓存、Django解决跨域流程(非简单请求,简单请求)、自动生成接口文档)(二)
  5. EXECUTE IMMEDIATE用法小解
  6. bigint最大有多少位_一台 Java 服务器可以跑多少个线程?
  7. teamcity mysql 配置_CentOS 7 上 TeamCity 安装
  8. sevlet中web.xml 文件
  9. 主板定制 如何定制主板这些流程要知道
  10. MIME媒体类型:简介,作用(描述并标记多媒体内容),示例
  11. C# 关于yield return的研究(转载)
  12. vi/vim: 文件浏览和缓冲区浏览
  13. 在 Mac 上的 Pages 文稿中如何添加和替换文本?
  14. Spring Boot 2.x 注册 Servlet 三大组件 Servlet、Filter、Listener
  15. 基于Django图书管理系统设计与实践
  16. 加拿大计算机科学薪酬,加拿大最好找工作及薪酬最高的十大专业介绍
  17. 实验一 数字类型及其操作
  18. C# 串口助手中英文显示问题
  19. 给大家分享几款浪漫的唯美APP
  20. 手机上如何学会使用计算机,手机怎么投屏到电脑上,我这儿有4种方法,一分钟让你学会...

热门文章

  1. php file get contents 总是超时,file_get_contents超时问题及解决方案
  2. 【慕课网】前端零基础入门---步骤一:页面结构层HTML---03-HTML表单
  3. swap file .swp already exists
  4. 请知悉/已知悉!英语怎么说?
  5. [SSL_CHX][2021-08-25]车厢重组
  6. uniapp猫眼电影小程序(附带猫眼电影api接口)
  7. 得物购买截图生成_iPhone12订单生成器app-iPhone12订单生成器网页app免费版预约 v1.0...
  8. POJ-3368 Frequent values
  9. css做出京东登录界面
  10. 计算机模糊,电脑显示不清晰_如果计算机显示器模糊或不清楚,该怎么办