• Python学习笔记(四) 函数


    这篇文章介绍有关 Python 函数中一些常被大家忽略的知识点,帮助大家更全面的掌握 Python 中函数的使用技巧

    1、函数文档

    给函数添加注释,可以在 def 语句后面添加字符串,这样的注释被称为 文档字符串,它将作为函数的一部分储存起来

    >>> def square(x):
    		'Calculates the square of the number x.'
    		return x*x
    

    可以通过内置属性 __doc__ 访问文档字符串

    >>> square.__doc__
    # 'Calculates the square of the number x.'
    

    也可以通过内置方法 help() 访问文档字符串

    >>> help(square)
    # Help on function square in module __main__:
    # square(x)
    #     Calculates the square of the number x.
    

    2、参数魔法

    在 C++ 中,函数参数的传递方式有两种,一是按值传递,二是按引用传递,那么在 Python 中参数传递方式是什么呢?

    我们先来看一个例子

    >>> name = 'Andy'
    >>> def change(string):
    		string = 'Function'
    >>> change(name)
    >>> name
    # 'Andy'
    

    函数形参变量的修改并不能影响实参变量的取值,这有点像按值传递,所以 Python 中参数的传递方式是按值传递吗?

    别急,我们再来看另一个例子

    >>> names = ['Andy']
    >>> def change(strings):
    		strings[0] = 'Function'
    >>> change(names)
    >>> names
    # ['Function']
    

    在这个例子中,函数形参变量的修改改变了实参变量的取值,这又有点像按引用传递,为什么会这样呢?

    为了理解清楚这里面的关系,我们需要深入了解其内在的原理

    首先,我们需要知道,在 Python 中变量赋值实际上并不是将值储存在变量中

    反倒有点像将变量名贴在值上,以下图片展示了调用语句 x = 'Hello' 的一种直观理解

    该过程在内存中的理论模型如下(变量可以视为指向值的名称):

    变量可以分为可变类型和不可变类型,当修改可变对象时,将直接修改物体内的值

    但是在“修改”不可变对象时,由于物体内的值不可改变,所以 Python 采取了一种特殊的方法处理这种情况

    即将标签贴在另一个储存了新内容的物体上,储存了旧内容的物体在没有标签指向时被系统自动回收

    以下图片展示了这一过程的直观理解

    该过程在内存中的理论模型如下:

    好,下面让回到最初的问题:为什么将字符串传入函数之后不会改变,而将列表传入函数之后会改变呢?

    现在我们总算明白了:

    • 当传入可变对象作为函数参数时,函数内的修改会改变原来的值

    • 当传入不可变对象作为函数参数时,函数内的修改不会改变原来的值

    假如我们想传入可变对象(例如列表)作为函数参数同时又不想修改原列表的值,该怎么办呢?

    这时我们可以传入列表的副本即可,例如 change(names[:])

    又假如我们想传入不可变对象(例如字符串)作为函数参数同时又想修改原字符串的值,该怎么办呢?

    不好意思,没有办法!这时我们只能从函数中返回所需要的值,并赋值给原变量,例如 name = change(name)

    3、关键字参数

    我们一般使用的参数称为 位置参数,因为它们的位置至关重要,在调用函数时我们甚至可以忽略它们的名称

    >>> def test(greeting,name):
    		print(greeting,name)
    >>> test('Hello','World') # 参数名称无关重要,只需要关注它们的位置即可
    # Hello World
    

    但是,一旦参数很多,难以记住每一个参数的位置时,可以在调用函数的时候指定参数的名称,这称为 关键字参数

    >>> def test(greeting,name):
    		print(greeting,name)
    >>> test(name='World',greeting='Hello')
    # Hello World
    

    关键字参数更重要的作用是给参数 指定默认值

    >>> def test(greeting='Hello',name='World'):
    		print(greeting,name)
    >>> test()
    # Hello World
    >>> test(name='Helen')
    # Hello Helen
    

    注意:指定默认参数时,一定要从后面开始指定,像 def test(greeting='Hello',name) 这样的语法是错误的

    4、收集参数

    设想我们遇到这样一种情况,当函数参数的数量不确定时,我们应该怎么处理呢?

    这时,我们可以使用 收集参数,收集参数在 定义函数 时使用星号 * 实现

    调用函数 时,将输入的参数作为一个元组储存起来

    >>> def test(a,b,*c):
    		print(a,b,c)
    >>> test(1,2,3,4,5,6)
    # 1 2 (3, 4, 5, 6)
    

    假如我们把收集参数放在前面会发生什么呢?比如说这样

    >>> def test(a,*b,c):
    		print(a,b,c)
    >>> test(1,2,3,4,5,6) # TypeError: test() missing 1 required keyword-only argument: 'c'
    

    这时会产生一个语法错误,因为星号意味着会收集余下的所有参数,导致形参 c 无法接收实参而产生错误

    我们可以使用关键字参数解决这个问题

    >>> def test(a,*b,c):
    		print(a,b,c)
    >>> test(1,2,3,4,5,c=6)
    1 (2, 3, 4, 5) 6
    

    使用星号还存在一个问题,即它无法收集关键字参数

    >>> def test(*paras):
    		print(paras)
    >>> test(1,2,3) # 收集位置参数,正常
    # (1, 2, 3)
    >>> test(x=1,y=2,z=3) # 收集关键字参数,错误
    # TypeError: test() got an unexpected keyword argument 'x'
    

    这时我们可以使用双星号 ** 解决这个问题,此时得到的将会是一个字典,而非元组

    另外要注意双星号不能收集位置参数

    >>> def test(**paras):
    		print(paras)
    >>> test(x=1,y=2,z=3) # 收集关键字参数,正常
    # {'x': 1, 'y': 2, 'z': 3}
    >>> test(1,2,3) # 收集位置参数,错误
    # TypeError: test() takes 0 positional arguments but 3 were given
    

    所以,我们一般可以使用以下格式同时收集位置参数和关键字参数:function(*args, **kwds)

    5、分配参数

    分配参数 与收集参数执行相反的操作,在 调用函数 时使用星号 * 或者双星号 ** 实现

    >>> def test(x,y):
    		print(x,y)
    >>> paras1 = (1,2)
    >>> test(*paras1) # 将元组变成独立的位置参数
    # 1 2 
    >>> paras2 = {'x':1,'y':2}
    >>> test(**paras2) # 将字典变成独立的关键字参数
    # 1 2
    

    5、作用域

    变量究竟是什么呢?其实可以将变量视为指向值的名称,这或许有点类似于字典(在字典中键指向值)

    实际上我们的确是在使用一种看不见的字典,一个名为 vars() 的内置函数可以返回这个看不见的字典

    我们称这个看不见的字典为 作用域

    >>> x = 1
    >>> vars()['x']
    # 1
    

    一般而言,不应该修改 vars 返回的字典,因为这会产生意想不到的后果

    下面考虑这样一个问题:为什么函数内局部变量的修改不会影响函数外同名的全局变量呢?

    >>> x = 1
    >>> def change():
        	x = 10
    >>> change()
    >>> x
    # 1
    

    这是因为在调用函数时,Python 会创建一个新的作用域,赋值语句在这个局部作用域中执行,而不影响全局作用域中的变量

    那么,现在假如我们想在函数中访问全局变应该怎么办呢?其实,这通常不会有任何问题

    >>> x = 1
    >>> def add(y):
        	print(x + y)
    >>> add(2)
    # 3
    

    但是,当全局变量和局部变量同名呢?

    >>> x = 1
    >>> def add(x):
        	print(x + x)
    >>> add(2)
    # 4
    

    此时,局部变量会覆盖全局变量

    我们可以使用 globals() 函数解决这个问题,globals() 函数类似于 vars() 函数,返回一个包含全局变量的字典

    >>> x = 1
    >>> def add(x):
        	print(x + globals()['x'])
    >>> add(2)
    # 3
    

    最后,假如我们想在函数中需改全局变量应该怎么办呢?

    我们应该使用关键字 global 明确告诉 Python,该变量就是全局变量

    >>> x = 1
    >>> def change():
        	global x
        	x = 10
    >>> change()
    >>> x
    # 10
    

    6、lambda 函数

    最后一个要介绍的知识点就是 lambda 函数

    lambda 函数即匿名函数,省去了函数命名的烦恼,对于一些功能简单的函数尤为合适

    其基本语法如下:lambda parameters : expression

    • parameters:用逗号分隔的变量列表,可选,代表传入函数的参数

    • expression:简单语句,不能包含 return,代表函数的输出结果

    示例 1:实现简单的加法

    >>> # 例如下面的语句表示:输入x、y,输出 x + y
    >>> add = lambda x,y: x+y
    >>> # 我们可以像使用正常函数一样使用 lambda 函数
    >>> add(1,2)
    # 3
    >>> # 但是,上面的用法并不是 lambda 函数最常用的用法,因为 lambda 函数的出现是为了创建匿名函数
    

    示例 2:使用在 sorted 函数中,指定排序规则

    >>> # 按绝对值排序
    >>> li = [3,-2,4,-1,5]
    >>> sorted(li, key = lambda x: abs(x))
    # [-1, -2, 3, 4, 5]
    

    示例 3:使用在 filter 函数中,指定过滤规则

    filter 函数的使用格式如下:filter(function,sequence)

    filter 将 function 作用于 sequence 中的每一个元素,并返回作用后结果为 true 的元素

    >>> # 过滤奇数
    >>> list(filter(lambda x: x % 2 == 0, range(10)))
    # [0, 2, 4, 6, 8]
    

    示例 4:使用在 map 函数中,指定处理规则

    map 函数的使用格式如下:map(function,sequence)

    map 将 function 作用于 sequence 中的每一个元素,并返回作用后的元素

    >>> # 求平方
    >>> list(map(lambda x: x**2, range(10)))
    # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    

    示例 5:使用在 reduce 函数中,指定处理规则

    reduce 函数的使用格式如下:reduce(function,sequence)

    reduce 将 function 迭代作用于 sequence 中的元素

    例如:reduce(function, [x1 , x2 , x3]) = function(function(x1 , x2) , x3) )

    >>> # 求累加和
    >>> from functools import reduce 
    >>> reduce(lambda x,y: x + y, range(10))
    # 45
    

    【 阅读更多 Python 系列文章,请看 Python学习笔记

    版权声明:本博客属于个人维护博客,未经博主允许不得转载其中文章。
  • 相关阅读:
    生成随机端口函数
    于获得MFC窗口其它类指针的方法
    VC6.0中使用ADO操作Access数据库 (转)
    【原创】C++利用IXMLDOM解析XML文件。
    转帖:用MFC对话框做无闪烁图片重绘一一 程序设计: icemen
    C代码优化方案(转)
    【转】C++ Socket UDP "Hello World!"
    线程中使用UpdateData出错解决方法(转)
    C语言调试打印log函数。
    Windows Sockets 网络编程(三) —— WINDOWS SOCKETS 1.1 程序设计(转)
  • 原文地址:https://www.cnblogs.com/wsmrzx/p/10286209.html
Copyright © 2020-2023  润新知