Python基础与拾遗9:Python中的函数与作用域
与其他编程语言相同,在具有一定规模的模块化的Python工程中, 函数是必不可少的组成部分。Python中的函数与作用域和其他常用编程语言亦有所不同,本篇博文总结Python中的函数与作用域相关知识,下面开始干货。
函数的作用
- 最大化代码重用和最小化代码冗余。该点在所有编程语言中适用,函数的目的可以理解为集成一个功能,在整个工程中不同的程序部分中,可以直接调用相同的函数达到预期的目标。
- 流程的分解。在大型的工程或程序中,逻辑流程往往非常复杂。此时函数就起到了分解流程的作用,将整个逻辑流程中的不同功能部分拆解,封装在函数中,可以使得工程更有条理,且逻辑清晰。
函数的设计理念
- 耦合性:对于输入使用参数并且对于输出使用return语句。函数是对部分功能进行封装,函数外部的逻辑不应知晓函数内部的运行流程,只关心输入与输出。
- 耦合性:真正必要时才使用全局变量。全部变量较影响依赖关系,因此非必要不使用。
- 耦合性:非必要不改变可变类型的参数。对于参数中的可变类型,不建议在函数中修改,可以通过return返回修改后的变量。
- 耦合性:避免直接改变在另一个模块文件中的变量,这样会损坏模块文件间的耦合性。
- 聚合性:每一个函数都应该有一个单一的,统一的目标。每一个函数应该针对一个目标进行设计。
- 大小:每一个函数应该相对较小。对于冗长的函数,建议针对功能进行拆分,提升工程的模块性。
Python中的函数
Python中的函数关键字
- def关键字表示可执行代码,创建一个对象并将其赋值给某一变量名。def语句是实时执行的,因此可以出现在任何地方,甚至嵌套在其他语句中。创建函数时,def关键字会生成新的函数对象并将其赋值给这个函数名,函数名就成了这个函数的引用。
def <name>(arg0, arg1, ... argN):
# TODO
def add(a, b):
return a + b
def main():
sum = add(1, 2)
print(sum)
if __name__ == "__main__":
main()
# 输出:
# 3
- lambda关键字创建对象但将其作为结果返回。一般作用在def不能达到的地方。在本篇博文中包含lambda函数的详细解析,请见后文。
f = [lambda x: x**2, lambda x: x**3]
def main():
a = f[0](2)
b = f[-1](2)
print(a)
print(b)
if __name__ == "__main__":
main()
# 输出:
# 4
# 8
- return关键字将控制权返回调用者。函数中没有return也可以,会返回一个None对象,自动忽略。
def add(a, b):
return a + b
def my_print(a):
print(a)
def main():
sum = add(1, 2)
pt = my_print(1)
print(sum)
print(pt)
if __name__ == "__main__":
main()
# 输出:
# 1
# 3
# None
- yield关键字向调用者发回一个结果对象,但是记住离开的地方。yield关键字主要用在生成器中,通过yield语句来返回值,并挂起他们的状态以便稍后能恢复状态。详情可参见Python基础与拾遗8:Python中的迭代器与解析。
def gen(N):
for i in range(N):
yield i ** 2
def main():
for i in gen(5):
print(i)
if __name__ == '__main__':
main()
'''
输出:
0
1
4
9
16
'''
- global关键字声明一个模块级变量。为分配一个在整个模块文件中都可以用的变量名,可以用global进行变量声明。在本篇博文中包含global关键字的详细解析,请见后文。
var = 1
def f():
global var
var = 100
def main():
f()
print(var)
if __name__ == "__main__":
main()
# 输出:
# 100
var = 1
def f():
# global var
var = 100
def main():
f()
print(var)
if __name__ == "__main__":
main()
# 输出:
# 1
- Python 3.0及之后版本中的nonlocal关键字声明一个嵌套函数作用域内的变量,而不是所有def之外的全局模块作用域变量。在声明nonlocal名称时,这个变量必须已经存在于该嵌套函数的作用域中。在本篇博文中包含nonlocal关键字的详细解析,请见后文。
def f():
var = 1
def g():
nonlocal var
print(var)
var += 1
return g
def main():
g = f()
g()
g()
g()
if __name__ == "__main__":
main()
# 输出:
# 1
# 2
# 3
def f():
var = 1
def g():
# nonlocal var
print(var)
var += 1
return g
def main():
g = f()
g()
g()
g()
if __name__ == "__main__":
main()
# 输出:
# UnboundLocalError: local variable 'var' referenced before assignment
- 函数通过赋值来传递。若传递的是可变对象,就可以在函数中改变此对象,但不建议这么做。注意Python语言与C语言的传值调用,传址调用机制的区别。
def f(a):
if isinstance(a, list):
for i in range(len(a)):
if isinstance(a[i], int) or isinstance(a[i], float):
a[i] += 1
def main():
a = [1, 2, 3]
f(a)
print(a)
if __name__ == "__main__":
main()
# 输出:
# [2, 3, 4]
- 函数并不是声明,也不需要声明。
Python中的多态
- 在Python中,代码不应该关心具体的数据类型。反之,操作的意义取决于被操作的对象类型,该特性称为Python中的多态。
def times(x, y):
return x * y
def main():
a = times(2, 4)
b = times("hello ", 3)
print(a)
print(b)
if __name__ == "__main__":
main()
# 输出:
# 8
# hello hello hello
Python中函数的参数传递
- 参数传递通过自动将对象赋值给本地变量名实现。所有参数实际通过指针进行传递,作为参数被传递的对象从来不用自动拷贝。
- 在函数内部的参数名的赋值不会影响调用者。函数定义中的参数名是一个新的、本地变量名,该点主要解释了定义与调用时的参数之间没有关联。
- 改变函数的可变对象参数的值对调用有影响。重要的事情说三遍,这里是本篇博文的第三遍。事实上不推荐这样做,建议通过return语句返回修改的参数。
可以在函数内部直接改动形参的值,比如列表,字典等。 - 不可变参数==通过“值”进行传递。比如字符串,实数。可变对象通过“指针”==进行传递。比如列表,字典等。
def f(a, b):
a = 100
b[0] = "hello"
def main():
a = 1
b = [1, 2, 3]
f(a, b)
print(a)
print(b)
if __name__ == "__main__":
main()
# 输出:
# 1
# ['hello', 2, 3]
Python中函数的参数匹配
参数匹配表
语法 | 使用时期 | 解释 |
---|---|---|
func(value) | 函数调用 | 常规参数:通过关键字进行匹配 |
funv(name=value) | 函数调用 | 关键字参数:通过变量名匹配 |
func(*sequence) | 函数调用 | 以name传递所有对象,并作为独立的基于位置的参数 |
func(**dict) | 函数调用 | 以name成对地传递所有的关键字/值,并作为独立的关键字参数 |
def func(name) | 函数定义 | 常规参数:通过位置/变量名进行匹配 |
def func(name=value) | 函数定义 | 默认参数值,如果没有在调用中传递的话 |
def func(*name) | 函数定义 | 匹配并收集(在元祖中)所有包含位置的参数 |
def func(**name) | 函数定义 | 匹配并收集(在字典中)所有包含位置的参数 |
def func(*args, name) | 函数定义 | 参数必须在调用中按照关键字传递 |
def func(*, name=value) | 函数定义 | Python 3.0及之后版本的规则,同上 |
匹配参数顺序
- 通过位置分配非关键字参数。
- 通过匹配变量名分配关键字参数。
- 其他的额外非关键字参数分配到==*name元组==中。
- 其他的额外关键字参数分配到==**name字典==中。
- 用默认值分配给在函数定义中未得到分配的参数。
参数出现顺序
在函数定义中,参数的出现顺序:
任何一般参数name -> 任何默认参数name=value -> *name(Python 3.0及之后版本可以用*) -> name/name=value(Python 3.0及之后版本中的key-only参数)。-> **name。
在函数调用中,参数的出现顺序:
任何位置参数value -> 任何关键字参数name=value和*sequence形式的组合 -> **dict参数。
- 通过位置匹配变量名。
def f(a, b, c):
print(a, b, c)
def main():
f(1, 2, 3)
# f(1) # TypeError: f() missing 2 required positional arguments: 'b' and 'c'
if __name__ == "__main__":
main()
# 输出:
1, 2, 3
- 通过变量名匹配关键字参数。注意,非关键字参数必须在关键字参数前面。
def f(a, b=3, c=5):
print(a, b, c)
def main():
f(1, 2, 3)
f(10)
f(100, b=1)
f(1000, c=2)
f(10000, c=1, b=2)
f(c=1, a=100000, b=2)
f(c=1, b=2, 1000000) # SyntaxError: positional argument follows keyword argument
if __name__ == "__main__":
main()
'''
输出:
1 2 3
10 3 5
100 1 5
1000 3 2
10000 2 1
100000 2 1
'''
- 默认参数在定义时必须在关键字参数之后。
def f(a, b=3, c=5):
print(a, b, c)
def main():
f(1)
if __name__ == "__main__":
main()
# 输出:
# 1, 3, 5
def f(a=3, b, c=5):
print(a, b, c)
def main():
f(1)
if __name__ == "__main__":
main()
# 输出:
# SyntaxError: non-default argument follows default argument
- 函数定义中*表示收集任意数目的不匹配位置参数,**表示收集任何数目的关键字参数。
def f(a, *pargs, **kargs):
print(a, pargs, kargs)
def main():
f(1, 2, 3, 4, x=5, y=6)
if __name__ == "__main__":
main()
# 输出:
# 1 (2, 3, 4) {'x': 5, 'y': 6}
- 解包参数。在调用函数时,显式地输入*和**,*解包元组,**解包字典。
# 元组的解包
def f(*a): # 接收序列参数
print(a) # 直接打印序列参数
print(*a) # 解包元祖
def g(a, b, c, d):
print(a, b, c, d)
def main():
t = (1, 2, 3, 4)
f(*t)
g(*t)
if __name__ == "__main__":
main()
# 字典的解包
def f(**a): # 接收字典参数
print(a) # 直接打印字典参数
print(*a) # 注意,这里是得到字典键
# print(**a) # TypeError: 'a' is an invalid keyword argument for print()
def g(a, b, c):
print(a, b, c)
def main():
d = {
'a': 1, 'b': 2, 'c': 3}
f(**d)
g(**d)
if __name__ == "__main__":
main()
Python 3.0及之后版本中的Keyword-Only参数
- 定义时,keyword-only参数必须编写在**args任意关键字形式之前,且在*args或者*之后。在调用时,keyword-only参数必须在**args参数之前或者包含在**args中,可以在*args之前或者之后。注意,调用时形式为键值对。
- 如果在函数定义中,keyword-only参数没有指定默认值,在调用时必须传入键值对。
- 注意,在函数定义与调用中,如果出现**arg形式,只能在最后。
def f(a, b=2, *c, d=4, **e):
print('a: ', a)
print('b: ', b)
print('c: ', c)
print('d: ', d)
print('e: ', e)
def main():
f(1, 10, 3, 4, 5, 6, 7)
print('------------------------------')
f(1, d=100, *(3, 4, 5, 6, 7))
print('------------------------------')
f(1, c=(3, 4, 5, 6, 7), d=1000)
print('------------------------------')
f(1, *(3, 4, 5, 6, 7), d=10000)
print('------------------------------')
f(1, 3, (4, 5, 6, 7), **dict(d=100000, e=8, f=9))
print('------------------------------')
f(1, 3, (4, 5, 6, 7), **dict(e=8, f=9))
print('------------------------------')
f(1, 3, (4, 5, 6, 7), e=8, f=9, d=4)
if __name__ == "__main__":
main()
'''输出
a: 1
b: 10
c: (3, 4, 5, 6, 7)
d: 4
e: {}
------------------------------
a: 1
b: 3
c: (4, 5, 6, 7)
d: 100
e: {}
------------------------------
a: 1
b: 2
c: ()
d: 1000
e: {'c': (3, 4, 5, 6, 7)}
------------------------------
a: 1
b: 3
c: (4, 5, 6, 7)
d: 10000
e: {}
------------------------------
a: 1
b: 3
c: ((4, 5, 6, 7),)
d: 100000
e: {'e': 8, 'f': 9}
------------------------------
a: 1
b: 3
c: ((4, 5, 6, 7),)
d: 4
e: {'e': 8, 'f': 9}
------------------------------
a: 1
b: 3
c: ((4, 5, 6, 7),)
d: 4
e: {'e': 8, 'f': 9}
'''
Python中函数的属性与注解
- 函数也是一个对象,自身全部都在内存块中。
- 函数名可以直接进行赋值,也可以当做函数参数进行传递,当做返回值返回。
def add(*a):
return sum(*a)
def f(func, *a):
return func(a)
def main():
sum = f(add, 1, 2, 3, 4)
print(sum)
if __name__ == "__main__":
main()
# 输出:
# 10
- 函数内省与属性。内省工具允许用户探索函数的实现细节,也可以给函数添加自定义属性,通过dir查看。
def f(a, b):
return a + b
def main():
f.__handles__ = "F_Handle" # 增加自定义属性
print(dir(f))
print(dir(f.__call__))
print(dir(f.__class__))
print(dir(f.__code__))
if __name__ == "__main__":
main()
'''
输出:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__handles__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__objclass__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_posonlyargcount', 'co_stacksize', 'co_varnames', 'replace']
'''
- Python 3.0及之后版本中的函数注解。对于参数的注解,出现在紧随参数名的冒号之后;对于返回值的注解,编写于紧跟函数列表之后的一个->之后。注解不影响函数的默认值。注解只在def表达式中有效,在lambda表达式中无效。
def f(a: 'float', b: 'float') -> float:
return a + b
def main():
print(f.__annotations__)
if __name__ == "__main__":
main()
# 输出:{'a': 'float', 'b': 'float', 'return': <class 'float'>}
Python中的lambda表达式
- lambda是一个表达式,不是一个语句。lambda主体是一个单个的表达式,不是一个代码块,允许在使用的代码内嵌入一个函数的定义。注意,lambda表达式尽量简短,复杂功能函数应该使用def。
def main():
f = lambda x, y: x + y
print(f(1, 2))
if __name__ == "__main__":
main()
# 输出:3
- lambda可以嵌套。
def main():
f = (lambda x: (lambda y: x + y))
g = f(1)
print(g(2))
if __name__ == "__main__":
main()
# 输出:3
Python中的map,filter与reduce函数
- map对一个序列对象中的每一个元素应用被传入的函数。在Python 2.6及之后版本中返回一个包含所有用函数调用结果的一个列表,而在Python 3.0及之后版本中返回一个可迭代对象,可以使用list()将其转换为列表。相比for循环或列表解析,map有性能方面的优势,执行更快。
def f(x):
return x ** 2
def main():
o = map(f, [1, 2, 3, 4])
print(o)
print(list(o))
if __name__ == "__main__":
main()
# 输出:
# <map object at 0x000001383432D220>
# [1, 4, 9, 16]
- map与lambda可以协同使用。
def main():
l = list(map(lambda x: x ** 2, [1, 2, 3, 4]))
print(l)
if __name__ == "__main__":
main()
# 输出:
# [1, 4, 9, 16]
- filter工具按照条件过滤,在Python 2.6及之后版本中返回一个包含所有函数调用结果的一个列表,而在Python 3.0及之后版本中返回一个可迭代对象,可以使用list()将其转换为列表。
def main():
l = list(filter(lambda x: x > 0, range(-5, 5)))
print(l)
if __name__ == "__main__":
main()
# 输出:
# [1, 2, 3, 4]
- reduce工具对每对元素都应用函数并运行到最后结果,在Python 2.6及之后版本中返回一个包含所有函数调用结果的一个列表,而在Python 3.0及之后版本中返回一个可迭代对象,可以使用list()将其转换为列表。注意,在Python 3.0及之后版本中,reduce工具被包含在functools模块中,要使用需先导入模块。对于filter和reduce也可参见Python基础与拾遗8:Python中的迭代器与解析
from functools import reduce
def main():
sum = reduce((lambda x, y: x + y), [1, 2, 3, 4])
print(sum)
if __name__ == "__main__":
main()
Python中的作用域
作用域规定
- 内嵌的模块是全局作用域。在每一个模块文件中直接定义的变量(在函数或者类外的),属于全局作用域,可以在这个模块中被任意使用。
x = [1, 2, 3, 4]
def f():
x.append(5)
def main():
f()
print(x)
if __name__ == "__main__":
main()
# 输出:[1, 2, 3, 4, 5]
- 全局作用域的作用范围仅限于单个文件,一个文件的顶层变量名仅对于这个文件内部的代码而言是全局的。
- 每次对函数的调用创建一个新的本地作用域。每个def语句或者lambda表达式都定义了一个新的本地作用域。
x = [1, 2, 3, 4]
def f():
x = 2
return x
def main():
x = f()
print(x)
if __name__ == "__main__":
main()
# 输出:2
- 赋值的变量名,除非声明为全局变量(global)或Python3.0 及之后版本的非本地变量(nonlocal),否则均为本地变量。
- 所有变量名都可以归纳为本地,全局或内置的。
变量名解析原则:L(本地作用域) -> E(上一层结构中def或lamda的本地作用域) -> G(全局作用域) -> B(内置作用域)
global语句
- 全局变量是位于模块文件内部顶层的变量名。
- 全局变如果在函数内部被赋值,必须经过声明。
- 全局变量名在函数内部不经过声明也可以被引用。
- global的赋值变量可以在赋值前直接不存在,会直接在模块中创建该变量。
- global使得作用域查找从模块的作用域开始,继续查找至内置作用域。对全局变量的赋值总是在模块的作用域中修改或创建变量。
x = 1
y = 2
def f():
global x, z
x = 2
z = 3
return x, y
def g():
global z
return z
def main():
x, y = f()
z = g()
print(x)
print(y)
print(z)
if __name__ == "__main__":
main()
'''
输出:
2
2
3
'''
- 没有在函数中赋值的变量会在整个模块中查找。
Python 3.0及之后版本中的nonlocal语句
- nonlocal应用于一个嵌套函数作用域中的一个名称,而不是所有def之外的全局模块作用域,哪怕全局作用域中有这个名称。
- nonlocal的名称必须要在一个嵌套的def作用域中出现过。
- nonlocal限制作用域查找只是嵌套的def,作用域查找不会继续到全局或内置作用域。下面的例子对比非本地变量与本地变量的区别。
def f(x):
a = x
def g():
nonlocal a
a += 1
return a
return g
def main():
a = f(1)()
print(a)
if __name__ == "__main__":
main()
# 输出:2
def f(x):
a = x
def g():
a = 100
return a
return g
def main():
a = f(1)()
print(a)
if __name__ == "__main__":
main()
# 输出:100
以上,欢迎各位读者朋友提出意见或建议。
写在最后
经过Python基础与拾遗部分的9讲,相信各位读者朋友对于Python语言已经有了初步的体会,能够进行初级的Python编程。这也是笔者对技术总结与复盘的过程,很高兴与各位读者朋友一起成长,享受进步的喜悦。
呈上Python基础与拾遗前8讲链接:
Python基础与拾遗1:Python中的数字
Python基础与拾遗2:Python中的字符串与字符串格式化
Python基础与拾遗3:Python中的列表
Python基础与拾遗4:Python中的字典
Python基础与拾遗5:Python中的元组
Python基础与拾遗6:Python中的文件
Python基础与拾遗7:Python中的数据类型总结
Python基础与拾遗8:Python中的迭代器与解析
欢迎阅读笔者后续博客,各位读者朋友的支持与鼓励是我最大的动力!
written by jiong
无人相
无我相
无众生相
无寿者相