关于Python的函数的几点特别说明
Python 函数还支持赋值、作为其他函数的参数以及作为其他函数的返回值。
一、Python 允许直接将函数赋值给其它变量
变量可以指向函数,以Python内置的求绝对值的函数abs()为例,调用该函数用以下代码:
>>> abs(-10)
10
但是,如果只写abs呢?
>>> abs
<built-in function abs>
可见,abs(-10)是函数调用,而abs是函数本身。
要获得函数调用结果,我们可以把结果赋值给变量:
>>> x = abs(-10)
>>> x
10
但是,如果把函数本身赋值给变量呢?
>>> f = abs
>>> f
<built-in function abs>
结论:函数本身也可以赋值给变量,即:变量可以指向函数。
如果一个变量指向了一个函数,那么,可否通过该变量来调用这个函数?用代码验证一下:
>>> f = abs
>>> f(-10)
10
成功!说明变量f现在已经指向了abs函数本身。直接调用abs()函数和调用变量f()完全相同。
那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
如果把abs指向其他对象,会有什么情况发生?
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10!
当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。
注:由于abs函数实际上是定义在__builtin__模块中的,所以要让修改abs变量的指向在其它模块也生效,要用__builtin__.abs = 10。
这样做的效果是,程序中也可以用其他变量来调用该函数,更加灵活。
例1、
def my_def ():
print("正在执行 my_def 函数")
#直接调用 my_def() 函数
my_def()
#先将函数赋值给其他变量
other = my_def
#再如下间接调用 my_def() 函数
other()
这段代码运行结果是:
正在执行 my_def 函数
正在执行 my_def 函数
特别提示:函数名(例如my_def)为该函数的内存地址;函数名 括号(例如my_def ()为调用该函数。
【python中的的fun和fun()的区别:
def fun(x,y):
return x+y
a = fun #【注1】 a等效fun函数的名
print(a(10,20)) # 输出30
b = fun(10,20) #【注2】 b等效fun函数返回的值
print(b) # 输出30
请留意【注1】【注2】处的注释】
例2、
def func (a,b):
return a+b
#直接调用带参函数
print(func(10,20))
print("------------")
#间接调用带参函数的两种用法:
ref1 = func #函数名后不带括号
print(ref1(10,20)) #
ref2 = func(10,20) #函数名后带括号
print(ref2) #
这段代码运行结果是:
30
------------
30
30
二、在Python中可以将函数当做实参传递给函数。
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数——函数名也可以作为另一个函数的参数。
一个最简单的高阶函数:
def add(x, y, f):
return f(x) + f(y)
当我们调用add(-5, 6, abs)时,参数x,y和f分别接收-5,6和abs,根据函数定义,我们可以推导计算过程为:
x = -5
y = 6
f = abs
f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
return 11
用代码验证一下:
>>> add(-5, 6, abs)
11
再举几个函数名作为另一个函数的参数的例子
例1:
import time
def test():
time.sleep(1.5)
print("test is running!")
def deco(func):
start = time.time()
func() #【注2】
stop = time.time()
print(stop-start)
deco(test) #【注1】,将test函数作为另一个函数deco的形参
说明:
在【注1】处,我们把test函数当作实参传递给deco函数的形参func,即func=test。注意,这里传递的是地址,也就是此时func也指向了之前test所定义的那个函数体,可以说在deco()内部,func就是test。在【注2】处,把函数名后面加上括号,就是对函数的调用(执行它)。因此,这段代码运行结果是:
test is running!
1.5655810832977295
例2:
def add(x, y):
return x + y
def sub(x, y):
return x - y
def apply(func, x, y): #【注1】
return func(x, y) #【注2】
print(apply(add, 2, 1)) #【注3】
print(apply(sub, 2, 1))
说明:
add 和 sub 是自定义的 Python 函数,都是接受两个值并返回一个计算的值。在【注1】 处可以看到变量接收一个就像其他普通变量一样的函数。在 【注2】 处调用了传递给 apply 的函数 func——在 Python 中双括号是调用操作符,调用变量名包含的值。在 【注3】 处展示了在 Python 中把函数作为值传参并没有特别的语法——和其他变量一样。这段代码运行结果是:
30
10
三、在Python中,可以在函数中将函数作为返回值返回
将函数作为值返回的例子
例1、
def bar():
print("in the bar.")
def foo(func):
print("in the foo.")
return bar
res=foo(bar)
res()
这段代码运行结果是:
in the foo.
in the bar.
例2、
def func(x):
def func1(y):
return x + y
return func1
test = func(4)
tes1 = test(5)
print(tes1)
这段代码运行结果是:
9
高阶函数(higher-order function):接受函数为参数,或者把函数作为结果返回的函数是高阶函数。换句话说,一个函数可以作为参数传给另外一个函数,或者一个函数的返回值为另外一个函数(若返回值为该函数本身,则为递归),满足其一则为高阶函数。
四、可变(mutable) 对象参数与不可变(immutable)对象参数的区别
由于 Python 中的变量存放的是对象引用,所以对于不可变对象而言,尽管对象本身不可变,但变量的对象引用是可变的。运用这样的机制,有时候会让人产生糊涂,似乎可变对象变化了。如下面的代码:
i = 73
i += 2
不可变的对象的特征没有变,依然是不可变对象,变的只是创建了新对象,改变了变量的对象引用。参见下图:
对于可变对象,其对象的内容是可以变化的。当对象的内容发生变化时,变量的对象引用是不会变化的。如下面的例子。
m=[5,9]
m+=[6]
参见下图:
【Mutable(可变)对象
可变对象可以在其 id() 保持固定的情况下改变其取值。
Immutable(不可变)对象
具有固定值的对象。不可变对象包括数字(numbers)、字符串(strings)和元组(tuples)。这样的对象不能被改变。如果必须存储一个不同的值,则必须创建新的对象。不可变对象不允许对自身内容进行修改。如果我们对一个不可变对象进行赋值,实际上是生成一个新对象,再让变量指向这个对象。哪怕这个对象简单到只是数字 0 和 1。】
可变对象为引用传递(函数传地址),不可变对象为值传递(函数传值)。
值传递:被调函数在执行时,首先对收到的参数对象生成一个副本,在执行过程中,是对参数副本的操作,并不会对原参数产生改变。也就是在堆栈中开辟内存空间存放由主调函数传进来的实参对应的副本值。特点:函数对收到的参数的任何操作,不会对原参数(实参变量)产生影响。
def add_fun(num):
num +=1
print(num, id(num))
return
num = 3
add_fun(num)
print(num, id(num))
【顺便提示:id()的值不是固定不变的——此值系统为对象分配的内存地址,在你练习时显示的不同值是正常的。】
这段代码运行结果是:
4 1857150478736
3 1857150478704
引用传递:当传递列表或者字典时,如果改变引用的值,就改变了原始对象。(引用传递直接传的是地址,是对原始对象的直接操作。)
def list_append_fun(list_t):
list_t.append("abc")
print(list_t, id(list_t))
return
list_t = [1,2,3]
list_append_fun(list_t)
print(list_t, id(list_t))
这段代码运行结果是:
[1, 2, 3, 'abc'] 2360375512256
[1, 2, 3, 'abc'] 2360375512256
由上面程序可以看出,引用传递,函数修改的直接是实参的值。
注意,若在函数体中修改了整个列表或者字典的值,这样做,也等于创建实参的副本,并不会对实参本身产生影响:
def list_append_fun(list_t):
list_t = ['a', 'b', 'c']
print(list_t, id(list_t))
return
list_t = [1,2,3]
list_append_fun(list_t)
print(list_t, id(list_t))
这段代码运行结果是:
['a', 'b', 'c'] 2759519584192
[1, 2, 3] 2759527239040
五、python中函数必须先定义、后调用,函数调用函数例外。