美文网首页Python七号
Python 基础系列--函数

Python 基础系列--函数

作者: somenzz | 来源:发表于2018-10-07 10:22 被阅读10次

    在中学数学中我们知道 y=f(x) 代表着函数,x 是自变量,y 是函数 f(x) 的值,给定 x 可以计算出对应的 y。在程序设计中,函数的功能是一样的,给定输入,返回对应的输结果,变量 x 不在限制为数字,可以为任意的数据类型,比如字符串,列表,字典,对象,或者自定义的对象等,同样地返回值也可以任意的数据类型。函数的作用是对加工细节的一种封装,对外提供统一的接口,使用者无需关心函数对内的细节,是最基本的一种代码抽象方式。

    函数不仅减少代码行数,而且能节省内存,提高程序运行速度:当一个函数调用完毕时,退出程序堆栈,内存空间被回收,当新的函数被调用时,局部变量又可以重新使用相同的地址。当一块数据被反复读写,其数据会留在 CPU 的一级缓存中,访问速度非常快,从而加快程序执行速度。

    下面来说一说 Python 中的函数。

    定义一个函数

    Python 定义函数的规则:

    • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
    • 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
    • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
    • 函数内容以冒号起始,并且缩进。
    • return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。

    使用 def 关键字,一般格式如下:

    def 函数名(参数列表):
        函数体
    

    以简单的数据计算函数为例,定义函数 fun(a,b,h) 来计算上底为 a,下底为b,高为 h 的梯形的面积:

    >>> def fun(a,b,h):      #def 定义函数fun,参数为a,b,h
    ...     s=(a+b)*h/2     #使用梯形的面积计算公式,注意此行前有4个空格
    ...     return s         #返回面积,注意此行前有4个空格
    ...
    >>> fun(3,4,5)         #计算上底为3,下底为4,高为5的梯形的面积
    17.5
    

    函数的目的是封装,提高应用的模块性,和代码的重复利用率。将常用的处理过程写成函数,在需要的时候调用它,可以屏蔽实现细节,减少代码量,增加程序可读性。假如下许多个梯形的面积需要计算,实例如下:

    >>> for a,b,h in [(3,4,5),(7,5,9),(12,45,20),(12,14,8),(12,5,8)]:  #计算5个梯形面积
    ...     print("上底{},下底{},高{}的梯形,面积为{}".format(a,b,h,fun(a,b,h))) #字符串格式化函数format
    ...
    上底3,下底4,高5的梯形,面积为17.5
    上底7,下底5,高9的梯形,面积为54.0
    上底12,下底45,高20的梯形,面积为570.0
    上底12,下底14,高8的梯形,面积为104.0
    上底12,下底5,高8的梯形,面积为68.0
    

    普通函数

    上例中的调用方法fun(3,4,5)并不直观,为了增加可读性,我们稍做调整,并增加函数的文档说明,如下:

    >>> def trapezoidal_area(upperLength,bottom,height):
    ...     """函数说明:输入:长、宽、高
    ... 返回该梯形的面积"""
    ...     return (upperLength+bottom)*height/2
    ...
    >>> trapezoidal_area(3,4,5)   # 按定义的顺序对应 upperLength=3,bottom=4,height=5
    17.5
    >>> trapezoidal_area(upperLength=3,bottom=4,height=5)  #显式的指定参数的值,位置可以变化
    17.5
    >>> trapezoidal_area(bottom=4,height=5,upperLength=3) #显式的指定参数的值,位置可以变化
    17.5
    >>>
    

    可以使用 help 函数查看该函数的文档说明:

    >>> help(trapezoidal_area)
    Help on function trapezoidal_area in module __main__:
    
    trapezoidal_area(upperLength, bottom, height)
        函数说明:输入:长、宽、高
        返回该梯形的面积
    

    参数带默认值的函数

    在调用此函数传递参数的时候使用参数关键字,这样参数的位置可以任意放置而不影响运算结果,增加程序可读性。假如待计算的梯形默认高度都为 5,可以定义带默认值参数的函数

    >>> def trapezoidal_area(upperLength,bottom,height=5):#定义默认值参数
    ...     return (upperLength+bottom)*height/2
    ...
    >>> trapezoidal_area(upperLength=3,bottom=4)
    17.5
    >>> trapezoidal_area(3,4)
    17.5
    >>> trapezoidal_area(3,4,5)
    17.5
    >>> trapezoidal_area(3,4,10)
    35.0
    

    注意:带有默认值的参数必须位于不含默认值参数的后面

    参数个数不固定的函数

    你可能需要一个函数能处理比当初声明时更多的参数,此时你可以定义不定长参数,语法如下:

    def 函数名([固定参数列表,] *不固定参数名 ):
       "函数_文档字符串"
       函数体
       return [expression]
    

    加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。
    举个例子:

    #!/usr/bin/python3
      
    # 可写函数说明
    def printinfo( arg1, *vartuple ):
       "打印任何传入的参数"
       print ("输出: ")
       print (arg1)
       for var in vartuple:
          print (var)
     
    # 调用printinfo 函数
    printinfo( 10 ) #不向函数传递未命名的变量
    printinfo( 70, 60, 50 ) #向函数传递未命名的变量
    

    输出结果为:

    输出: 
    10
    输出: 
    70
    60
    50
    

    还有一种就是参数带两个星号 **的参数会以字典的形式传入:

    #!/usr/bin/python3
      
    # 可写函数说明
    def printinfo( arg1, **vardict ):
       "打印任何传入的参数"
       print ("输出: ")
       print (arg1)
       print (vardict)
     
    # 调用printinfo 函数
    printinfo(1, a=2,b=3)
    

    输出结果为:

    输出: 
    1
    {'a': 2, 'b': 3}
    

    声明函数时,参数中星号 * 可以单独出现,例如:

    def f(a,b,*,c):
        return a+b+c
    

    如果单独出现星号 * 后的参数必须用关键字传入。

    >>> def f(a,b,*,c):
    ...     return a+b+c
    ... 
    >>> f(1,2,3)   # 报错
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: f() takes 2 positional arguments but 3 were given
    >>> f(1,2,c=3) # 正常
    6
    >>>
    

    是值传递还是引用传递?

    关于函数是否会改变传入变量的值分两种情况:
    (1)对不可变数据类型的参数,函数无法改变其值,如字符串,数字,元组等。
    (2)对可变数据类型的参数,函数可以改变其值,如列表,字典,集合等。
    这里什么是可变数据类型,什么是不可变数据类型,请参考上一篇文章 Python 的可变/不可变数据类型。

    请尝试说出下面程序的输出结果:

    # !/usr/local/bin/python3
    # -*- coding: utf-8 -*-
    # Time: 2018/10/6 7:36:38
    # Description:
    # File Name: lx_fun_params.py
    
    def change_nothing(var):
        var = "new value"
    
    def try_change(var):
        if type(var) is list:
            var.append("new value")
        elif type(var) is str:
            var = var + " new value"
        else:
            pass
    
    def try_change1(var):
        var = var+"a"
    
    str1 = "old value"
    list1 = ["old value"]
    
    change_nothing(str1)
    change_nothing(list1)
    print("after call change_nothing:")
    print(str1)
    print(list1)
    
    #恢复原值
    str1 = "old value" 
    list1 = ["old value"] 
    
    try_change(str1) 
    try_change(list1)
    
    print("after call try_change:")
    print(str1)
    print(list1)
    

    按照 C/C++ 的思维会产生函数参数是值传递,还是引用传递。有些同学可会潜移默化的认为列表是属于引用传递, change_nothing 调用之后 str1 未被改变,list1 变成字符串 “new value", try_change 调用之后 str1 未被改变,list1 会新加入元素 “new value"。
    真正的结果是:


    image.png

    Python 函数参数的传递既不是所谓的传值也不是传引用。如果你理解发什么是可变数据类型 ,什么是不可变数据类型,这就很好理解。请牢记,在 Python 世界里,万物皆对象,变量是对象的引用,代表着对象在内存中的地址。Python 中函数参数传递的是变量的值,即就是变量所指向的对象的地址。
    对上例中的字符串 str1 ,如下图所示:在调用 change_nothing 传入参数时前,str1 与 var 均指向 "old value" 的地址,调用 change_nothing 后,var 指向了新的对象 "new value",因此 str1 未发生任何变化,对字符串 str1 调用 try_change 的本质与 change_nothing 是一样的,同样都是赋值操作,因此 str1 均不发生变化。


    image.png

    list1 也是同样的道理,因此在调用 change_nothing 之后,list1 的值仍然是 ["old value"]

    但是在调用 try_change 函数时,发生了变化。如下图所示


    image.png

    开始传参时 list1 和 var 均指向 ["old value"],由于列表是可变数据类型,增加、删除、修改元素时不产生新的对象,对象在内存中的地址不发生变化,var 仍指向原来的 list1 的地址,因此在调用 try_change 函数后,list1 被改变。

    涉及到的其他小知识:

    (1)isinstance 和 type 的用法:
    python 判断一个变量属于什么对象可以使用 isinstance 和 type,二者的区别在于判断有继承关系的类时
    isinstance 认为子类是父类,type 则认为子类不是父类,如下所示:

    class A:
        pass
    
    class B(A): # B 是 A 的子类
        pass
    
    isinstance(A(), A)  # returns True
    type(A()) == A      # returns True
    isinstance(B(), A)    # returns True
    type(B()) == A        # returns False
    

    (2)匿名函数:
    python 使用 lambda 来创建匿名函数。
    所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。
    语法
    lambda 函数的语法只包含一个语句,如下:

    lambda [arg1 [,arg2,.....argn]]:expression
    

    例子:

    #!/usr/bin/python3
     
    # 可写函数说明
    sum = lambda arg1, arg2: arg1 + arg2
     
    # 调用sum函数
    print ("相加后的值为 : ", sum( 10, 20 ))
    print ("相加后的值为 : ", sum( 20, 20 ))
    

    以上实例输出结果:

    相加后的值为 :  30
    相加后的值为 :  40
    

    (完)

    文章首发公众号,如果觉得这篇文章对您有帮助,请关注公众号 somenzz 获取最新消息或推荐给需要的朋友。


    somenzz 的公众号

    相关文章

      网友评论

        本文标题:Python 基础系列--函数

        本文链接:https://www.haomeiwen.com/subject/ilrcaftx.html