“Life is short, You need Python” – Bruce Eckel
Environment
函数体现的是面向过程的编程,如可以将训练模型的过程写成一个函数;类体现的是面向对象的编程,创造具有属性和方法的对象,如可以将一个模型定义为一个类实例化而来的对象。这里做一点儿学习笔记,内容包括函数相关的操作,需要关注变量的作用域问题,并且理解函数闭包。
使用函数的意义
def 语句
实现函数的定义
def 函数名(参数):
""" 函数文档 """
函数体
return 返回值
返回值 = 函数名(参数) # 调用
对于在函数中调用另个函数的情况, Python 允许被调用的函数在之后定义
def bar():
print('bar')
def foo():
bar() # 之前定义
baz() # 之后定义
def baz():
print('baz')
foo()
“"" xxx """
, 在函数的开头编写函数文档,便于代码的维护。调用 函数名.__doc__
得到函数文档的内容例 计算 sigmoid 函数自变量分别为 0 , 1 0, 1 0,1 和 − 10 -10 −10 的结果
σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1 + e^{-x}} σ(x)=1+e−x1
import math
def sigmoid(x):
"""sigmoid 函数"""
return 1 / (1 + math.e**(-x))
print(sigmoid(0)) # 0.5
print(sigmoid.__doc__) # sigmoid 函数
执行一个定义好的函数可分为三步:
参数可分为形式参数(形参)和实际参数(实参)
例 在之前的 sigmoid 函数中
import math
def sigmoid(x): # 此处的 x 为形参
"""sigmoid 函数"""
return 1 / (1 + math.e**(-x)) # 此处的 x 为形参
res = sigmoid(0) # 传入的 0 为实参
根据函数定义时的形式,参数可以分为位置参数、默认参数和可变长参数;根据函数调用时的形式,参数可以为关键字参数。
定义函数时,
*args
或 **kwargs
,则为可变长参数例
# 其中,x 和 w 为位置参数,b 为默认参数,后俩为可变长参数
def foo(x, w, b=0):
return w * x + b
# 调用,将 1 传给 x,将 2 传给 w
print(foo(1, 2)) # 2
注意 ⚠️ 勿将默认参数的值设置为可变类型,包括列表、字典和字符串,会搞出 bug!
# 在列表最后添加一个元素
def foo(ls=[]):
ls.append(6)
return ls
print(foo()) # [6]
print(foo()) # [6, 6]
print(foo()) # [6, 6, 6]
每次调用函数时,分配给形参的内存地址都是相同的,函数存在,形参就存在,这块内存不会被 Python 的垃圾回收机制清除
def foo(ls=[0, 1]):
print(id(ls))
ls.append(2)
print(id(ls))
print(foo())
# 5187260680
# 5187260680
# [0, 1, 2]
print(foo())
# 5187260680
# 5187260680
# [0, 1, 2, 2]
调用函数时,
例
def foo(x, w, b=0):
return w * x + b
# 使用关键字参数,允许传入的顺序与定义的顺序不一致
print(foo(2, b=3, w=1)) # 5
print(foo(w=1, 2, b=3)) # 报错 SyntaxError
一些 Python 内置函数被定义为仅接受位置参数,如 float(x=0, /)
,在 /
之前的参数在调用函数时仅能以位置参数的形式传入,若以关键字参数形式传入,则报错。
print(float('1.1')) # 1.1
float(x='1.1') # 报错
不过从 Python 3.8 开始才允许用户在使用 def
语句定义函数时使用 /
定义仅位置参数。
*args
,用于接收一些可能需要的额外参数定义函数时,该参数放在最后(**kwargs
之前)。调用时先传入位置参数和关键字参数,之后剩余的参数以元组的形式打包传递给 args
def foo(a, b, c, *args):
print(args) # 元组形式
print((a, b, c, args))
print((a, b, c, *args)) # 星号解包
return None
baz(0, 1, 2, 3, 4)
# (3, 4)
# (0, 1, 2, (3, 4))
# (0, 1, 2, 3, 4)
**kwargs
定义函数时,该参数放在最后。调用时先传入前面的参数,剩余的以关键字形式传入的参数将以字典的形式打包传递给 kwargs
def bar(a, b, c, **kwargs):
print(kwargs)
print(a, b, c, kwargs)
return None
bar(1, 2, 3, d=3, e=4)
# {'d': 3, 'e': 4}
# 1 2 3 {'d': 3, 'e': 4}
例 *args
和 **kwargs
一齐使用
def forw(x, *args, **kwargs):
if len(args) == 0:
y = x
elif len(args) == 1:
w = args[0]
y = w * x
elif len(args) == 2:
w, b = args
y = w * x + b
else:
print('Too many elements in *args')
return None
if len(kwargs) > 0:
if kwargs['nonlinearity'] == 'relu':
return max(0, y)
return y
print(forw(2, -2, 3)) # -1
print(forw(2, -2, 3, nonlinearity='relu')) # 0
函数体是封装在函数内部的代码块,只有在调用该函数时才会被执行。
根据变量的作用域,可讲变量分为两类
Python 函数体可以访问在其外部定义的变量(访问变量的顺序 LEGB:Local → \rightarrow → Enclosing → \rightarrow → Global → \rightarrow → Built-in),但无法修改不可变类型的变量(注意 ⚠️ 可变类型的变量可以修改,如列表、集合)
x = 6
def foo():
y = x + 60 # 访问了全局变量
print(y)
bar() # 66
def bar():
x += 60 # 尝试修改全局变量
print(x)
bar() # 报错
# UnboundLocalError: local variable 'x' referenced before assignment
dc = {1: 'one', 2: 'two'}
def baz():
dc[3] = 'three'
baz()
print(dc) # {1: 'one', 2: 'two', 3: 'three'}
在 Python 函数体中定义变量时,可以用三种方式
global
关键字进行声明nonlocal
关键字进行声明该变量仅在函数体内部定义和作用
def foo():
a = 6
foo()
print(a) # 报错 NameError
函数内部可以访问外部的全局变量,但无法对其进行修改;若需要修改,则需要使用 global
关键字进行声明
a = 6
def foo():
global a
a += 60
print(a)
foo() # 66
使用 return
语句设定函数的返回值,该语句的执行代表函数执行的结束,这句完事后退出函数。
若一个函数内无 return
语句,则执行完所有函数体代码后返回 None
;return
后跟多个变量以逗号隔开,则以元组的形式返回多个值
def foo():
"""返回为 None"""
print()
def bar():
"""这是一个返回单个值的函数"""
return 6
def baz():
"""这是一个返回元组的函数"""
return 66, 666
print(foo()) # None
print(bar()) # 6
print(baz()) # (66, 666)
匿名函数使用 lambda 变量: 函数体
定义并调用,简化了代码。
# 如判断三个样本属于第一类和第二类的概率
y = [
[0.9, 0.1],
[0.3, 0.7],
[0.4, 0.6]
]
# 按照判断属于第一类的概率进行排列
y.sort(key=lambda x: x[0], reverse=True)
print(y)
Python 中,函数为第一类对象,其可以赋值给一个变量、可以作为元素添加到组合类型中、可作为参数值传入其它函数,还可以当作其他函数的返回值。
以函数为参数或返回值为函数的函数称为高阶函数(High-order function)。
def high_order_func(func):
return func
f = high_order_func(print)
f('hello') # hello
在一个函数中定义定义另一个函数,称为函数的嵌套。
def outer():
print('outer is being called.')
def inner():
print('inner is being called.')
print('inner done.')
inner()
print('outer done.')
outer()
# outer is being called.
# inner is being called.
# inner done.
# outer done.
若需要修改嵌套中外层函数的变量,则 global
关键字声明无法实现(global
只能修改到全局),此时需要 nonlocal
关键字声明
def outer():
x = 6
def inner():
nonlocal x
x += 60
print(x)
inner()
outer() # 66
print(x) # 报错 NameError
外层函数以内层函数为返回值,内层函数中引用了外层函数的变量,称为闭包(closure)。
例 1 内层函数无返回值
def affine_weight(w):
print('Setting affine transform config...')
b = 10
def affine(x):
print('result:', w * x + b)
print('Config done.')
return affine
transform = affine_weight(2)
# Setting affine transform config...
# Config done.
transform(3)
# result: 16
例 2 内层函数有返回值
def affine_weight(w):
print('Setting affine transform config...')
b = 10
def affine(x):
return w * x + b
print('Config done.')
return affine
transform = affine_weight(2)
# Setting affine transform config...
# config done.
print(transform(3)) # 16
闭包的 __closure__
属性是一个以 cell
对象为元素的元组,cell
对象的 cell_contents
属性就是闭包中引用的变量。
transform.__closure__
# (, |
# ) |
for elem in transform.__closure__:
print(elem.cell_contents)
# 10
# 2
闭包由一个函数与其可引用变量的外部环境所组成。
以下是维基百科的解释是
A closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment. The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created. Unlike a plain function, a closure allows the function to access those captured variables through the closure’s copies of their values or references, even when the function is invoked outside their scope.
从面向对象编程的角度理解(相关内容参考Python基础(六)类和对象):如果将闭包视作一个对象,那么
其可引用的外部环境的变量就是它的属性;
函数所执行的操作就是这个对象唯一的方法。
如下例所示,定义了三个进行仿射变换(方法)的闭包,而它们的参数(属性)不同。
# 定义三种不同的线性变换
def affine_params(w, b):
def affine(x):
res = w * x + b
return res
return affine
transform_1 = affine_params(3, 4)
transform_2 = affine_params(5, 10)
transform_3 = affine_params(8, 14)
print(transform_1(2)) # 10
print(transform_2(2)) # 20
print(transform_3(2)) # 30
Python 中闭包的主要应用就是装饰器,内容参考Python基础(十)深浅拷贝与三大器 Part 4。