Python基础与拾遗9:Python中的函数与作用域


与其他编程语言相同,在具有一定规模的模块化的Python工程中, 函数是必不可少的组成部分。Python中的函数与作用域和其他常用编程语言亦有所不同,本篇博文总结Python中的函数与作用域相关知识,下面开始干货。

函数的作用

  1. 代码重用和最代码冗余。该点在所有编程语言中适用,函数的目的可以理解为集成一个功能,在整个工程中不同的程序部分中,可以直接调用相同的函数达到预期的目标。
  2. 流程的分解。在大型的工程或程序中,逻辑流程往往非常复杂。此时函数就起到了分解流程的作用,将整个逻辑流程中的不同功能部分拆解,封装在函数中,可以使得工程更有条理,且逻辑清晰。

函数的设计理念

  1. 耦合性:对于输入使用参数并且对于输出使用return语句。函数是对部分功能进行封装,函数外部的逻辑不应知晓函数内部的运行流程,只关心输入与输出。
  2. 耦合性真正必要时才使用全局变量。全部变量较影响依赖关系,因此非必要不使用。
  3. 耦合性非必要改变可变类型的参数。对于参数中的可变类型,不建议在函数中修改,可以通过return返回修改后的变量。
  4. 耦合性避免直接改变另一个模块文件中的变量,这样会损坏模块文件间的耦合性。
  5. 聚合性:每一个函数都应该有一个单一的,统一的目标。每一个函数应该针对一个目标进行设计。
  6. 大小:每一个函数应该相对较小。对于冗长的函数,建议针对功能进行拆分,提升工程的模块性。

Python中的函数

Python中的函数关键字

  1. 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
  1. 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
  1. 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
  1. 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
'''
  1. 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
  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
  1. 函数通过赋值来传递。若传递的是可变对象,就可以在函数中改变此对象,不建议这么做注意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]
  1. 函数并不是声明,也不需要声明。

Python中的多态

  1. 在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中函数的参数传递

  1. 参数传递通过自动将对象赋值给本地变量名实现。所有参数实际通过指针进行传递,作为参数被传递的对象从来不用自动拷贝。
  2. 在函数内部的参数名的赋值不会影响调用者。函数定义中的参数名是一个新的、本地变量名,该点主要解释了定义与调用时的参数之间没有关联。
  3. 改变函数的可变对象参数的值对调用有影响。重要的事情说三遍,这里是本篇博文的第三遍。事实上不推荐这样做,建议通过return语句返回修改的参数。
    可以在函数内部直接改动形参的值,比如列表,字典等。
  4. 不可变参数==通过“值”进行传递。比如字符串,实数。可变对象通过“指针”==进行传递。比如列表,字典等。
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及之后版本的规则,同上

匹配参数顺序

  1. 通过位置分配非关键字参数。
  2. 通过匹配变量名分配关键字参数。
  3. 其他的额外非关键字参数分配到==*name元组==中。
  4. 其他的额外关键字参数分配到==**name字典==中。
  5. 默认值分配给在函数定义中未得到分配的参数

参数出现顺序

在函数定义中,参数的出现顺序:

任何一般参数name -> 任何默认参数name=value -> *name(Python 3.0及之后版本可以用*) -> name/name=value(Python 3.0及之后版本中的key-only参数)。-> **name。

在函数调用中,参数的出现顺序:

任何位置参数value -> 任何关键字参数name=value和*sequence形式的组合 -> **dict参数。

  1. 通过位置匹配变量名。
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
  1. 通过变量名匹配关键字参数。注意,非关键字参数必须在关键字参数前面
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
'''
  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
  1. 函数定义中*表示收集任意数目的不匹配位置参数,**表示收集任何数目的关键字参数
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}
  1. 解包参数。在调用函数时,显式地输入*和**,*解包元组,**解包字典。
# 元组的解包

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参数

  1. 定义时,keyword-only参数必须编写在**args任意关键字形式之,且在*args或者*之。在调用时,keyword-only参数必须在**args参数之或者包含在**args,可以在*args之前或者之。注意,调用时形式为键值对
  2. 如果在函数定义中,keyword-only参数没有指定默认值,在调用时必须传入键值对。
  3. 注意,在函数定义与调用中,如果出现**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中函数的属性与注解

  1. 函数也是一个对象,自身全部都在内存块中。
  2. 函数名可以直接进行赋值,也可以当做函数参数进行传递,当做返回值返回
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
  1. 函数内省与属性。内省工具允许用户探索函数的实现细节,也可以给函数添加自定义属性,通过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']

'''
  1. 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表达式

  1. lambda是一个表达式,不是一个语句。lambda主体是一个单个的表达式,不是一个代码块,允许在使用的代码内嵌入一个函数的定义。注意,lambda表达式尽量简短,复杂功能函数应该使用def。
def main():
    f = lambda x, y: x + y
    print(f(1, 2))

if __name__ == "__main__":
    main()
# 输出:3
  1. 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函数

  1. 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]
  1. 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]
  1. 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]
  1. 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中的作用域

作用域规定

  1. 内嵌的模块是全局作用域。在每一个模块文件中直接定义的变量(在函数或者类外的),属于全局作用域,可以在这个模块中被任意使用。
x = [1, 2, 3, 4]

def f():
    x.append(5)

def main():
    f()
    print(x)

if __name__ == "__main__":
    main()
# 输出:[1, 2, 3, 4, 5]
  1. 全局作用域的作用范围仅限于单个文件,一个文件的顶层变量名仅对于这个文件内部的代码而言是全局的。
  2. 每次对函数的调用创建一个新的本地作用域。每个def语句或者lambda表达式都定义了一个新的本地作用域。
x = [1, 2, 3, 4]

def f():
    x = 2
    return x

def main():
    x = f()
    print(x)

if __name__ == "__main__":
    main()
# 输出:2
  1. 赋值的变量名,除非声明为全局变量(global)或Python3.0 及之后版本的非本地变量(nonlocal),否则均为本地变量
  2. 所有变量名都可以归纳为本地全局内置的。

变量名解析原则:L(本地作用域) -> E(上一层结构中def或lamda的本地作用域) -> G(全局作用域) -> B(内置作用域)

global语句

  1. 全局变量是位于模块文件内部顶层的变量名。
  2. 全局变如果在函数内部被赋值,必须经过声明
  3. 全局变量名在函数内部不经过声明也可以被引用
  4. global的赋值变量可以在赋值前直接不存在,会直接在模块中创建该变量。
  5. 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
'''
  1. 没有在函数中赋值的变量会在整个模块中查找。

Python 3.0及之后版本中的nonlocal语句

  1. nonlocal应用于一个嵌套函数作用域中的一个名称,而不是所有def之外的全局模块作用域,哪怕全局作用域中有这个名称。
  2. nonlocal的名称必须要在一个嵌套的def作用域中出现过
  3. 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
无人相
无我相
无众生相
无寿者相

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值