返回目录

本篇索引

(1)函数基本

(2)函数参数

(3)作用域

(4)递归

(1)函数基本

● 函数是第一类对象

Python中万物皆对象,所有对象都是第一类的(first class),函数也不例外,也是第一类对象。既然是对象,那就可以当作普通的对象数据处理,比如:拥有自己的属性、可赋值给一个变量、可放入容器存储、可作为另一个函数的参数或是返回值等等。当你在使用 def 定义函数时,就相当于生成了一个函数对象。

下例中,将3个内置函数放入一个序列,并用迭代器取出后分别调用:

line = 'abc,1,3.14'
fun_list = [str, int, float]
para_list = line.split(',')
obj_list = [f(val) for f, val n zip(fun_list, para_list)] # obj_list结果为:['abc', 1, 3.14]

● 文档字符串

通常,将函数def后的第一行,作为描述函数用途的“文档字符串”,保存在函数的__doc__属性中。用内置函数 help(函数名) 也可以查看函数的描述文档。

当函数使用装饰器时,有可能会破坏与文档字符串相关的帮助功能,解决这个问题的办法是 在装饰器函数定义中,手工设置函数的文档字符串和函数名称,如下例所示:

def mywrap(func):
def callf(*args, **kwargs):
return func(*args, **kwargs)
callf.__doc__ = func.__doc__
callf.__name__ = func.__name__
return callf @mywrap
def myfunc():
"""文档字符串内容"""
...

由于这是一个常见问题,因此 functools 模块提供了函数装饰器函数 wraps,用于自动复制这些属性, 使用方法如下例所示:

from functools import wraps
def mywrap(func):
@wraps(func)
def callf(*args, **kwargs):
return func(*args, **kwargs)
return callf @mywrap
def myfunc():
"""文档字符串内容"""
...

● 可调用类型

可调用类型表示支持函数调用操作的对象,包括:用户定义的函数、内置函数、实例方法、类、提供了可调用接口的实例。可以使用内置函数 callable() 来检查一个对象是否是可调用的。

类都是是可以调用的,调用类时,会自动将入参传递给类的__init__()方法,用以创建一个新实例。

实例对象一般是不可调用的,但是如果这个实例实现了__call__()方法,那么这个实例就可直接调用。例如:若x是某个实例,执行 x(args) 就相当于调用 x.__call__(args) 方法。

● 函数的属性

函数作为一种对象,理论上可以给函数添加任意属性。函数属性主要保存在函数的__dict__属性中, __dict__属性是一个字典。函数也有一些内部默认的属性,见下列各表。

“内置函数”具有以下属性

属性 描述
__doc__ 文档字符串。
__name__ 函数名称。
__self__ 与方法相关的实例。 说明:对于像len()这样的内置函数,__self__为None(表明未绑定);而像s.append()这样的内置方法,__self__为列表对象s。

“用户定义函数”具有以下属性

属性 描述
__doc__ 文档字符串。
__name__ 函数名称。
__dict__ 包含函数属性的字典。
__code__ 编译后的代码。
__defaults__ 包含默认参数的元组。
__globals__ 函数应用时对应的全局命名空间的字典。
__clousre__ 闭包(包含与嵌套作用域相关数据的元组)

“实例方法”具有以下属性

属性 描述
__doc__ 文档字符串。
__name__ 方法名称。
__class__ 定义该方法的类。
__func__ 实现方法的函数对象
__self__ 与方法相关的实例(如果是非绑定方法,则为None)

和文档字符串一样,函数属性也有混合装饰器后丢失的问题。因为使用装饰器包装函数, 实际上是访问装饰器函数的属性而不是原始函数的属性。解决方法也是类似的, 用户在装饰器函数中需要手动把原始函数的属性赋值给装饰器函数,如下例所示:

def mywrap(func):
def callf(*args, **kwargs):
return func(*args, **kwargs)
callf.__doc__ = func.__doc__
callf.__name__ = func.__name__
callf.__dict__.update(func.__dict__)
return callf @mywrap
def myfunc():
"""文档字符串内容"""
...

● 绑定与非绑定方法

通过实例调用方法时,有绑定和非绑定两种用法。绑定方法封装了成员函数和一个对应实例,调用绑定方法时,实例会作为第一个参数self自动传递给方法。而非绑定方法仅封装了成员函数,并没有实例,用户在调用非绑定方法时,需要显式地将实例作为第一个参数传递进去。详见下面2个例子

绑定用法(bound method):

class Foo():
def meth(self, a):
print(a) obj = Foo() # 创建一个实例
m = obj.meth # 将meth方法绑定到obj这个实例上
m(2) # 调用这个方法时,Python会自动将obj作为self参数传递给meth()方法

非绑定用法(unbound method):

class Foo():
def meth(self, a):
print(a) obj = Foo() # 创建一个实例
um = Foo.meth # 非绑定,仅仅是将这个方法赋值给um,并不需要实例存在
um(obj, 2) # 调用这个非绑定方法时,用户需要显式地传入obj实例作为第一个参数。

● 匿名函数

使用 lambda 语句可以创建表达式形式的匿名函数,其用途是指定短小的回调函数。语法为:

lambda 参数 : 表达式

lambda匿名函数中不能出现非表达式语句、也不能出现多行语句。

下例定义一个匿名函数:

a = lambda x,y: x*y
b = a(2,5) # b的结果为:10

下例在序列的sort()方法中传入lambda匿名函数:

[('b',2),('a',1)].sort(key=lambda x:x[1])  # 结果为 [('a',1),('b',2)]

# 说明:使用匿名函数对列表元素进行了预处理,将原本的元组('a',1)预处理为:取出元组中后一个元素(即:1),所以能够进行排序。

(2)函数参数

● 位置参数、关键字参数、返回值

在调用函数时,传入参数的顺序数量必须与函数定义匹配,否则会引发TypeError异常。如果在调用时指定参数名,这样就不必遵照函数定义中的参数顺序了,这样可大大增加调用时的可读性。这种指定参数名传入的参数叫做关键字参数,一般的未指定参数名的参数叫做位置参数。关键字参数只能放在所有的位置参数后面。

函数参数都是按值传递的,但是如果传递的是对象(即非单纯数字),所谓按值传递就是函数参数仅仅是将对象的地址值复制一下传过去而已,所以在函数中其实是可以改变外面对象中的内容的。一般最好避免使用这种风格,而且在涉及线程和并发的程序中,使用这类函数的效率很低,因为通常需要使用线程锁来防止副作用的影响。

若省略return语句、或单一个return关键字,就会返回 None 对象。

● 参数的默认值

在函数定义时,可以为某些参数指定默认值,这样在调用时可以不提供这个参数了。一旦出现带默认值的参数,此参数后续的参数都必须带默认值,否则会引发SyntaxError异常。

建议不要使用可变对象作为默认值(如空列表),这样可能导致意外的bug,如下例所示:

def fun(x, seq=[]):
seq.append(x)
return seq
fun(1) # 返回[1]
fun(2) # 返回[1,2]
fun(3) # 返回[1,2,3]

上例的本意是若未传入seq参数,则新建一个列表,并将x放入新列表。但是事实上会产生bug。这种情况建议使用seq=None,再在函数中建新列表。

● 单星号 *

在函数定义时,参数前加单星号的意思为:收集其余的位置参数,并将它们放入同一个元组中。这样在函数调用时,用户就可以提供任意多个参数了。如下例所示:

def fun(x, *y):
print(x)
print(y) fun('a', 1, 2, 'c') # 结果为:a 和 (1,2,'c')
fun(3) # 结果为:3 和 ()

单星号亦可反转使用,即:在调用函数时使用*,自动将一个元组展开为若干个指定名字的关键字参数。

def myadd(x, y):
return x+y t = (1,2)
myadd(*t) # 调用时,单星号自动将元组(1,2)展开为 x, y

● 双星号 **

在函数定义时,参数前加双星号的意思为:收集其余的关键字参数,并将它们放入一个字典中。这样在调用函数时,可以传入大量可扩充的配置项作为参数。

def fun(x, **z):
print(x)
print(z) fun(x=1, y=2, z=3) # 结果为:1 和 {'y':2, 'z':3}

双星号亦可反转使用,在调用函数时使用**,自动将一个字典拆分为若干个指定名字的关键字参数。

def myadd(x, y):
return x+y d = {'x':1, 'y':2}
myadd(**d) # 调用时,双星号自动将字典d展开为:x=1, y=2

单星号和双星号可组合使用,用于同时收集位置参数和关键字参数,**参数必须出现在*参数的后面。

def fun(*args, **kwargs):
print(args)
print(kwargs) fun(1,2,3, x=4,y=5,z=6)
# 结果为:(1,2,3) 和 {'x':4, 'y':5, 'z':6 }

(3)作用域

每次执行一个函数时,就会创建一个新的局部命名空间。这个命名空间(又叫作用域),就像其内部有一个“不可见”的字典,其中包含本函数参数的名称和所有在函数内部定义的局部变量。

除了每个函数有有一个局部作用域以外,还有一个全局作用域。可以通过内置函数locals()和globals()查看局部和全局作用域字典,内建函数vars(obj)可以返回某个实例的作用域字典。

x = 1
def fun():
y = 2
print(locals())
print(globals()) fun() # locals()的显示为:{'y':2}
# globals()除了显示全局变量x以外,还会显示很多全局默认的变量:
{ 'x': 1,
'fun': <function fun at 0x000001E5C52EA048>
'__name__': '__main__',
'__doc__': None,
'__package__': None,
……
}

● 在函数内访问全局变量

Python解释器解析变量时,会先搜素局部作用域,若找不到就会搜索全局作用域,若再找不到就会搜索内置命名空间,如果仍然找不到,就会引发NameError异常。

此种方法虽然可以访问全局变量,但不能对全局变量赋值,若要赋值全局变量,需要使用global关键字。

在函数中修改全局变量:

x = 1
def fun():
global x
x = 2
fun() # 运行fun()后,全局变量 x 的值变为2

函数中局部变量名和全局变量名重复时:

x = 1
def fun():
x = 2 # 此处创建一个名为 x 的局部变量
globals()['x'] = 3 # 使用globals()内置函数,可通过字典的方式直接操作全局变量 fun() # 运行fun()后,全局变量 x 的值变为3

注意:Python中不支持访问在函数中访问上级调用函数的局部作用域,如下例的代码会引发NameError异常

def fun_inner():
print(x) def fun_outer():
x = 2
fun_inner() fun_outer() # 调用时会引发NameError异常,因为在fun_inner()函数中,不能访问外层调用函数fun_outer()的局部作用域中的 x

● 嵌套作用域

Python3支持在嵌套定义的函数中,使用外层定义函数(不是外层调用函数)的局部作用域中的变量,这个称为:动态作用域(dynamic scoping),需要使用nonlocal关键字,如下例所示:

def fun_outer():
x = 4
def fun_inner():
nonlocal x # 声明绑定到外层定义的x
x = 5
print(x) fun_inner()
print(x) fun_outer() # 会先运行fun_inner()中的print语句,打印出5;然后运行外层中的print语句,可看到x确实被修改成了5

(4)递归

函数调用自身称为递归(recursion)。递归最典型的使用场景是,可以把一个大问题分解成重复的小问题来解决,而且这个小问题的解决结构非常简洁。虽然大部分的递归都可以用循环来替代解决,但有时用递归写函数要比循环可读性更高,看起来也更优雅。

用递归计算n的阶乘:

def factorial(n):
if n <= 1:
return 1
else:
return n * factorial(n-1)

用递归实现序列的二分法查找:

def search(seq, num, lower, upper):
if lower == upper:
return upper
else:
middle = (lower + upper) // 2
if num > seq[middle]:
return search(eq, num, middle+1, upper)
else:
return search(seq, num, lower, middle) seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
seq.sort()
search(seq, num=2, lower=0, upper=9) # 使用二分法查找比起遍历查找,可以很快地找到num

● 递归的深度

Python对递归函数调用的深度做了限制,函数 sys.getrecursionlimit() 返回当前最大的递归深度, 而函数 sys.setrecursionlimit() 可以修改递归深度(默认值为1000)。当超出递归深度时, 就会引发 RuntimeError 异常。Python不会进行尾递归优化。

还要小心混合使用递归和装饰器的问题,如果对递归函数使用装饰器, 所有内部的递归调用都会通过装饰后的版本进行。如果使用装饰器的目的是进行一些系统管理 (如同步或锁定),最好不要使用递归。

返回目录

Python语法速查: 7. 函数基础的更多相关文章

  1. Python语法速查: 4. 字符串常用操作

    返回目录 (1)字符串常用方法 Python3中,字符串全都用Unicode形式,所以省去了很多以前各种转换与声明的麻烦.字符串属于序列,所有序列可用的方法(比如切片等)都可用于字符串. 注意:字符串 ...

  2. Python语法速查:目录

    1. 数据类型与内置函数 2. 列表.元组.字典.集合操作 3. 字符串格式化 4. 字符串常用操作 5. 运算符.math模块.表达式 6. 循环与迭代 7. 函数基础 8. 类与对象 9. 函数进 ...

  3. Python语法速查: 1. 数据类型与内置函数

    返回目录 (1)常用内置数据类型 分类 类型名称 描述 数字 int 整数 float 浮点数 complex 复数 bool 布尔值 序列 str 字符串(不可变序列) list 列表 tuple ...

  4. Python语法速查: 13. 操作系统服务

    返回目录 本篇索引 (1)sys模块 (2)os模块 (3)与Windows相关模块 (4)subprocess模块 (5)signal模块 (1)sys模块 sys模块用于Python解释器及其环境 ...

  5. Python语法速查: 5. 运算符、math模块、表达式

    返回目录 (1)一些较容易搞错的运算符 一般简单的如加减乘除之类的运算符就不写了,这里主要列些一些容易搞错或忘记的运算符.运算符不仅仅只有号,有一些英文单词如 in, and 之类,也是运算符,并不是 ...

  6. Python语法速查: 12. 文件与输入输出

    返回目录 (1)文件基本操作 ● 文件常用操作 内置函数或方法 描述 open(name [,mode [,buffering]]) 内置函数.用来打开文件,返回一个文件对象(file对象).详见下述 ...

  7. Python语法速查: 3. 字符串格式化

    返回目录 (1)简易字符串格式化 字符串属于不可变序列,只能生成新的,不能改变旧的.“字符串格式化”有点像以前C语言的sprintf,可以将若干变量代入格式化的字符串,生成一个符合要求的新字符串. 转 ...

  8. Python语法速查: 14. 测试与调优

    返回目录 本篇索引 (1)测试的基本概念 (2)doctest模块 (3)unittest模块 (4)调试器和pdb模块 (5)程序探查 (6)调优与优化 (1)测试的基本概念 对程序的各个部分建立测 ...

  9. Python语法速查: 20. 线程与并发

    返回目录 本篇索引 (1)线程基本概念 (2)threading模块 (3)线程间同步原语资源 (4)queue (1)线程基本概念 当应用程序需要并发执行多个任务时,可以使用线程.多个线程(thre ...

随机推荐

  1. Coderfocers-551C

    Professor GukiZ is concerned about making his way to school, because massive piles of boxes are bloc ...

  2. csuoj-1900 锋芒毕露

    Description 小闪最近迷上了二刀流——不过他耍的其实是剑——新买了一个宝库用来专门存放自己收集的双剑.一对剑有两把,分只能左手用的和只能右手用的,各自有一个攻击力数值.虽然一对剑在小闪刚拿到 ...

  3. E1.Send Boxes to Alice(Easy Version)//中位数

    发送盒子给Alice(简单版本) 题意:准备n个盒子放巧克力,从1到n编号,初始的时候,第i个盒子有ai个巧克力. Bob是一个聪明的家伙,他不会送n个空盒子给Alice,换句话说,每个盒子里面都有巧 ...

  4. tensorflow word2vec详解

    maybe_download 下载text8.zip.可以手工下载下来.然后指定text8.zip的路径. read_data 解压text8.zip,把数据读入到data中. data是一个长数组, ...

  5. 解决老浏览器不支持ES6的方法

    转载地址:http://www.rockyxia.com/?p=669 为什么ES6会有兼容性问题? 由于广大用户使用的浏览器版本在发布的时候也许早于ES6的定稿和发布,而到了今天,我们在编程中如果使 ...

  6. Java_输入整数求阶乘

    import java.util.Scanner;public class Work4{ public static void main(String[] args){ // 创建Scanner对象 ...

  7. JS内置对象-Array之indexOf和lastIndexOf

    indexOf() var num = [1, 7, 2, 3, 4, 7, 9] var pos = num.indexOf(7) var pos1 = num.indexOf(7, 2) cons ...

  8. ES7中的async 和 await

    async 和 await 一个函数如果加上 async ,那么该函数就会返回一个 Promise async function test() { return "1" } con ...

  9. 11条MySQL规范,你知道的有几个?

    一.数据库命令规范 · 所有数据库对象名称必须使用小写字母并用下划线分割 · 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) · 数据库对象的命名 ...

  10. Django day03之学习知识点

    今日是路由层学习: 3.路由匹配 3.1 正则表达式的特点: 一旦正则表达式能够匹配到内容,会立刻结束匹配关系 直接执行对应的函数.相当于采用就近原则,一旦找到就不再继续往下走了 重点: 正则表达式开 ...