python基础函数图_Python基础:函数

一、概述

函数(function)是一个可调用的(callable)对象,它获取一些(0个或多个)参数,然后执行一段代码,最后返回一个值给调用者。

在Python中,函数是第一级对象(first-class),因此它具有与其他Python对象完全相同的基本行为特征,如可以被传递、可以作为右值进行赋值、可以作为另一个函数的参数或返回值等等。

二、声明、定义和调用

与C/C++不同的是,Python中的函数不单独区分 声明 和 定义,这两者同时发生在def语句被执行时,因此统一称为 定义。

def funcname([parameters]):

与其他高级语言类似,Python中的函数必须在定义后才能 调用(即先定义,后调用);同时,在函数定义中允许存在 前向引用(即在函数A的定义中引用了函数B,但函数B在函数A之后才定义)。

# 先定义,后调用

>>> funcA()

Traceback (most recent call last):

File "", line 1, in

NameError: name 'funcA' is not defined

>>> def funcA():

... print 'ok'

...

>>> funcA()

ok

# 前向引用

>>> def funcA():

... print 'in funcA()'

... funcB()

...

>>> def funcB():

... print 'in funcB()'

...

>>> funcA()

in funcA()

in funcB()

# 前向引用(也必须遵守“先定义,后调用”的原则)

>>> def funcA():

... print 'in funcA()'

... funcB()

...

>>> funcA()

in funcA()

Traceback (most recent call last):

File "", line 1, in

File "", line 3, in funcA

NameError: global name 'funcB' is not defined

三、参数

以下讨论中,实参 是指调用函数时由调用者传入的参数,形参 是指函数定义中在内部使用的参数(类似于C/C++中的参数概念)。

1、参数传递

Python中对函数参数的传递采用 传引用 的方式,即实参和形参都是引用,它们指向同一个对象实体(换言之,即形参是实参的浅拷贝)。

例如有以下函数:

>>> def changer(a, b): # 函数定义

... a = 2 # 改变形参a

... b[0] = 'spam' # 改变形参b

...

>>> X = 1 # 实参X指向一个整数(不可变对象)

>>> L = [1, 2] # 实参L指向一个列表(可变对象)

>>> changer(X, L) # 函数调用

>>> X # 对形参a的修改,不影响实参X

1

>>> L # 对形参b的修改,影响了实参L

['spam', 2]

上述示例中,函数changer的实参和形参的传递关系如下:

综上可知:

如果参数引用的对象本身是 不可变的,如数值、字符串、元组,则在函数中对形参的修改 不会影响 实参

如果参数引用的对象本身是 可变的,如列表、字典,则在函数中对形参的修改 会影响 实参

2、实参类型

调用函数时,可以指定两种类型的参数:位置参数(positional argument)和关键字参数(keyword argument)(参考 argument)。

1)位置参数

位置参数 又称为非关键字参数(non-keyword argument),这种参数的指定方式有两种:直接以值的形式 和 以 * 开头的可迭代对象 (iterable)。

例如,在以下对complex()函数的调用中,3和5都是位置参数:

>>> complex(3, 5)

(3+5j)

>>> complex(*(3, 5))

(3+5j)

2)关键字参数

关键字参数 的指定方式也有两种:以 name=value 的形式 和 以 ** 开头的字典。

例如,在以下对complex()函数的调用中,3和5都是关键字参数:

>>> complex(real=3, imag=5)

(3+5j)

>>> complex(**{'real': 3, 'imag': 5})

(3+5j)

3)混合使用

如果在调用函数时,要混合使用位置参数和关键字参数,则位置参数必须位于关键字参数之前。

例如,在以下对complex()函数的调用中,3是位置参数,5是关键字参数:

>>> complex(3, imag=5)

(3+5j)

3、形参绑定

在函数定义中,可以指定四种类型的参数:常规参数、默认参数、变长元组参数和变长字典参数。这四种形参类型的区别与 形参绑定 强相关。

形参绑定 是指:调用函数时,Python对实参与形参进行一一匹配的过程(进而完成参数传递)。在这个绑定过程中,每种形参能够接受的实参类型是不同的,具体对应关系如下:

形参类型实参类型(位置参数)实参类型(关键字参数)

常规参数

默认参数

变长元组参数

×

变长字典参数

×

下面结合实参与形参的绑定过程,分别介绍形参的这四种类型:

1)常规参数

常规参数 是必须指定的形参。根据实参类型的不同,绑定规则如下:

如果实参是“位置参数”,则按照 参数位置 来严格匹配实参和形参,实参和形参的个数必须相等、顺序必须一致。

如果实参是“关键字参数”,则按照 参数名称 来严格匹配实参和形参,实参和形参的个数必须相等、名称必须一致(顺序不重要)。

参考以下示例:

# 函数定义

>>> def func(a, b):

... print type(a), a

... print type(b), b

...

# 函数调用(实参是“位置参数”)

>>> func() # 个数必须相等

Traceback (most recent call last):

File "", line 1, in

TypeError: func() takes exactly 2 arguments (0 given)

>>> func(1)

Traceback (most recent call last):

File "", line 1, in

TypeError: func() takes exactly 2 arguments (1 given)

>>> func(1, 2, 3)

Traceback (most recent call last):

File "", line 1, in

TypeError: func() takes exactly 2 arguments (3 given)

>>> func(1, 2) # 顺序一致时:a等于1,b等于2

1

2

>>> func(2, 1) # 顺序相反时:a等于2,b等于1

2

1

# 函数调用(实参是“关键字参数”)

>>> func(a=1, b=2) # 顺序一致时:a等于1,b等于2

1

2

>>> func(b=2, a=1) # 顺序相反时:a等于1,b等于2

1

2

>>> func(c=1, d=2) # 名称必须一致

Traceback (most recent call last):

File "", line 1, in

TypeError: func() got an unexpected keyword argument 'c'

2)默认参数

在函数定义中,默认参数 被指定了默认值,因此在调用函数时:

如果不指定对应的实参,则形参使用其默认值

如果指定了对应的实参,则形参使用实际指定的参数值

指定实参时,默认参数的绑定规则与“常规参数”相同

参考以下示例:

# 函数定义

>>> def func(a=1):

... print type(a), a

...

# 函数调用

>>> func() # 使用默认值

1

>>> func(2) # 实参是“位置参数”

2

>>> func(a=2) # 实参是“关键字参数”

2

对于默认参数,还有一点值得注意的是:默认参数只在函数定义(即执行def语句)时被求值一次,以后每次调用函数时都使用以前的值(参考 function definitions)。由此可知,当默认参数的默认值是一个可变对象的时候,如果函数内部对默认参数有修改,就会影响到下一次调用函数时的默认值(一般情况下,这可能不是你想要的行为)。简单示例如下:

# 函数定义

>>> def func(a=[]):

... print a

... a.append(0)

...

# 函数调用

>>> func()

[]

>>> func()

[0]

>>> func()

[0, 0]

为了避免上述问题,可以采用以下方式:

# 函数定义

>>> def func(a=None):

... if a is None:

... a = []

... print a

... a.append(0)

...

# 函数调用

>>> func()

[]

>>> func()

[]

3)变长元组参数

在“常规参数”和“默认参数”绑定完成后(如果有的话),如果还有额外(0个或多个)的“位置参数”,则 变长元组参数 将会把这些多余的“位置参数”以 元组 的形式搜集到一起。示例如下:

# 函数定义

>>> def func(*args):

... print type(args), args

...

# 函数调用

>>> func() # 允许没有“位置参数”

()

>>> func(1, 2) # 实参是“位置参数”

(1, 2)

>>> func(*(1, 2)) # 实参是“位置参数”

(1, 2)

>>> func(a=1, b=2) # 不接受实参是“关键字参数”的情况

Traceback (most recent call last):

File "", line 1, in

TypeError: func() got an unexpected keyword argument 'a'

4)变长字典参数

在“常规参数”和“默认参数”绑定完成后(如果有的话),如果还有额外(0个或多个)的“关键字参数”,则 变长字典参数 将会把这些多余的“关键字参数”以 字典 的形式搜集到一起。示例如下:

# 函数定义

>>> def func(**kwargs):

... print type(kwargs), kwargs

...

# 函数调用

>>> func() # 允许没有“关键字参数”

{}

>>> func(a=1, b=2) # 实参是“关键字参数”

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

>>> func(**{'a': 1, 'b': 2}) # 实参是“关键字参数”

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

>>> func(1, 2) # 不接受实参是“位置参数”的情况

Traceback (most recent call last):

File "", line 1, in

TypeError: func() takes exactly 0 arguments (2 given)

5)混合使用

如果在函数定义中,要混合使用上述四种类型的形参,则这几种形参类型的排列顺序必须从左到右依次为:常规参数,默认参数,变长元组参数,变长字典参数。

以下为混合使用几种形参的典型示例:

# 函数定义

>>> def func(a, b=0, *args, **kwargs):

... print type(a), a

... print type(b), b

... print type(args), args

... print type(kwargs), kwargs

...

# 函数调用

>>> func(1)

1

0

()

{}

>>> func(1, 2)

1

2

()

{}

>>> func(1, 2, 3)

1

2

(3,)

{}

>>> func(1, 2, 3, c=4)

1

2

(3,)

{'c': 4}

四、返回值

在Python中,一个函数总会返回一个值(除非发生异常),这个值可以是任何Python对象。根据具体函数的不同,返回值有以下几种情况:

函数中的return语句实际返回的Python对象

无return

None

return

None

return a

对象a

return a, b, c

元组(a, b, c)

简单示例如下:

>>> def f1(): pass

...

>>> def f2(): return

...

>>> def f3(): return 1

...

>>> def f4(): return 1, 2, 3

...

>>> f1(), f2(), f3(), f4()

(None, None, 1, (1, 2, 3))

五、名字空间与作用域

以下讨论中,会根据下面的 示意图 来进行具体示例分析:

1、基本概念

在Python程序中,一切对象都是借助 名字 来操作的(即名字引用对象)。名字空间(namespace)是名字到对象的映射。

在一个程序文本中,通常存在多个不同的 代码块(code block),例如模块、函数体、类定义等,每个代码块都对应一个独立的名字空间。名字空间中的名字只能在一个代码范围内可见,这个代码范围称为 作用域(scope)。

对于上述概念的准确而详细的描述,请参考 Naming and binding。

2、名字空间

一个代码块对应一个名字空间,具体到示意图中的情况:

func局部名字空间:即func函数的代码块对应的名字空间,包含变量名e,函数参数名x、y、z

func_inner局部名字空间:即func_inner函数的代码块(除开func部分)对应的名字空间,包含变量名c,函数名func,函数参数名x、y

func_outer局部名字空间:即func_outer函数的代码块(除开func_inner部分)对应的名字空间,包含变量名b,函数名func_inner,函数参数x

全局名字空间:即模块文件的代码块(除开func_outer部分)对应的名字空间,包含变量名a,函数名func_outer,变量名d(在func函数中以global方式定义),以及Python为模块预置的一些名字(例如__name__、__builtins__等)

内建名字空间:包含内建模块__builtin__中的所有名字,例如print(实际由全局名字空间中的__builtins__指定:在__main__模块中,__builtins__就是内建的__builtin__模块;在导入模块中,__builtins__是字典__builtin__.__dict__的别名)

查看名字空间的一个简单方法是:使用dir函数。例如,查看示意图中各代码块对应名字空间的示例如下:

#!/usr/bin/env python

# -*- coding: utf-8 -*-

a = 1

def func_outer(x):

b = 2

def func_inner(y):

c = 3

def func(z):

global d

d = 4

e = x + y + z

print "func's namespace:"

print dir()

func(0)

print "func_inner's namespace:"

print dir()

func_inner(0)

print "func_outer's namespace:"

print dir()

if __name__ == '__main__':

func_outer(0)

print 'global namespace:'

print dir()

print 'built-in namespace:'

print dir(__builtins__)

运行结果:

$ python shownamespace.py

func's namespace:

['e', 'x', 'y', 'z']

func_inner's namespace:

['c', 'func', 'x', 'y']

func_outer's namespace:

['b', 'func_inner', 'x']

global namespace:

['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'd', 'func_outer']

built-in namespace:

['ArithmeticError', 'AssertionError', ..., 'xrange', 'zip']

3、作用域

Python中的作用域可以分为四种:局部作用域(Local scope)、外围作用域(Enclosing scope)、全局作用域(Global scope)、内建作用域(Built-in scope)。

例如,对于示意图中的函数func而言:

局部作用域 是指func函数对应的代码范围(包含在def语句或lambda表达式内部)

外围作用域 是指func_outer函数对应的代码范围(包括内嵌的函数funct_inner,以及二次内嵌的函数func)

全局作用域 是指整个模块文件对应的代码范围

内建作用域 是指任何的Python代码范围

上述几种名字空间与这四种作用域的关系是:

func局部名字空间中的名字只在局部作用域可见

func_inner局部名字空间和func_outer局部名字空间中的名字只在外围作用域可见

全局名字空间中的名字只在全局作用域可见

内建名字空间中的名字只在内建作用域可见

4、总原则

关于名字与作用域,主要记住以下两点:

名字引用 遵循 LEGB 的查找规则:首先查找局部作用域(L),接着查找外围作用域(E),然后查找全局作用域(G),最后查找内建作用域(B);否则查找失败

名字赋值 默认新建一个局部作用域中的名字;如果与LEGB路径上的其他作用域中的名字相同,则在LEGB查找时将屏蔽其他作用域中的相同名字;如果声明为global,则将新建(或是覆盖,如果已存在)一个全局作用域中的名字

六、高级

1、装饰器

装饰器(decorator)是一个函数,它对另一个函数进行包装处理,进而扩展被包装函数的功能。尽管名称相同,但Python中的装饰器并不是设计模式中的装饰器模式的Python实现(可以参考 Decorators)。

@wrapper[(arg)]

def funcname([parameters]):

装饰器可以不带参数,例如以下两种函数定义是等价的:

# 装饰器版本

@f

def func(): pass

# 普通版本

def func(): pass

func = f(func)

装饰器也可以带参数,例如以下两种函数定义是等价的:

# 装饰器版本

@f(arg)

def func(): pass

# 普通版本

def func(): pass

func = f(arg)(func)

当然,还可以多个装饰器嵌套使用,例如以下两种函数定义是等价的:

# 装饰器版本

@f1(arg)

@f2

def func(): pass

# 普通版本

def func(): pass

func = f1(arg)(f2(func))

以上都是函数定义中的装饰器语法,下面给出一个装饰器实现的简单示例:

>>> def wrapper(func):

... def wrappedFunc():

... print 'before func'

... func()

... print 'after func'

... return wrappedFunc

...

>>> @wrapper

... def func():

... print 'in func'

...

>>> func()

before func

in func

after func

2、生成器

生成器(generator)是一个带有yield语句的函数,与普通函数不同的是,它返回一个支持迭代器(iterator)协议的对象。

以下是一个生成器的简单示例:

# 平方生成器

>>> def squares(N):

... for i in range(N):

... yield i ** 2

...

# 返回一个生成器对象,该对象支持迭代器协议

>>> x = squares(2)

>>> x

>>> x.next()

0

>>> x.next()

1

>>> x.next()

Traceback (most recent call last):

File "", line 1, in

StopIteration

# for循环自动调用next(),并处理StopIteration异常

>>> for i in squares(2):

... print i

...

0

1

如果要严格区分的话,在上述示例中,squares 是生成器函数,而 x 才是生成器(对象)。可以借助 inspect模块 来体会二者的区别(参考 Python yield使用浅析):

>>> import inspect

>>> inspect.isgeneratorfunction(squares), inspect.isgeneratorfunction(x)

(True, False)

>>> inspect.isgenerator(squares), inspect.isgenerator(x)

(False, True)

关于生成器,需要注意以下几点:

yield语句会产生一个值,作为next()调用的返回值

yield语句会中断函数处理,并记住当前的执行状态(中断位置和变量值等),以便后续原状态恢复执行

如果恢复执行后,已经没有yield语句可执行,则抛出StopIteration异常(以示迭代结束)

生成器中一般没有return语句,如果执行中遇到了return语句,则直接抛出StopIteration异常

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值