zoukankan      html  css  js  c++  java
  • 函数的参数详解

    # 函数的参数
    
    定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就算完成了。
    对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了
    函数内政部的复杂逻辑被封装起来,调用者无需了解。
    
    python的函数定义非常简单,单灵活度却非常大。除了正常定义的必选参数外,
    还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数
    还可以简化调用者的代码。
    
    一、位置参数
    我们先写一个计算x的平方的函数
    def power(x):
        return x * x
    对于power(x)函数,参数想就是一个位置参数。当我们调用power函数时,必须传入有且仅有的一个参数x
    >>> power(5)
    25
    >>> power(15)
    225
    >>> power(125)
    15625
    
    那么,如果我们现在要计算x的立方怎么办?可以再定义一个power3函数,但是如果要计算x的四次方,五次方怎么办?
    假设x的n次方,可以把x和n都设为参数
    
    def powerful(x, n):
        result = 1
        if n == 0:
            return result
        elif n > 0:
            while n >= 1:
                result = result * x
                n-=1
            return result
        elif n < 0:
            while n >= 1:
                result = result * x
                result = 1 / result
            return result
    
    对于这个修改后的powerful(x, n)函数,可以计算任意N次方
    修改后的powerful(x, n)函数有两个参数,x和n,这两个参数都是位置参数,调用函数时,传入的两个值
    按照位置顺序一次赋给参数x和n
    
    
    
    二、默认参数
    如果我们经常计算x的平方,那么我们完全可以把第二个参数的默认值设定为2
    
    def powerful(x, n = 2):
        result = 1
        if n == 0:
            return result
        elif n > 0:
            while n >= 1:
                result = result * x
                n-=1
            return result
        elif n < 0:
            while n >= 1:
                result = result * x
                result = 1 / result
            return result
    
    >>> powerful(5)
    25
    >>> powerful(5,3)
    125
    
    这样如果我们给n赋值,n就会成为被赋的值,如果不给n赋值,默认就是n=2,默认计算平方。
    
    从以上的例子可以看出,默认参数可以简化函数的调用。
    
    设置默认参数时,有几点问题需要注意:
    
    一是必选参数在前,默认参数在后,否则python的解释器会报错。
    二是如何设置默认参数。
    
    当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。
    变化小的参数就可以作为默认参数。使用默认参数最大的好处就是能够降低使用函数的难度。
    
    举个例子,我们写一个小学一年级学生注册的函数,需要传入name和gender两个参数
    def enroll(name, gender):
        print("name:", name)
        print("gender:", gender)
    
    这样,调用enroll()函数只需要传入两个参数
    >>> enroll("Sarah", "F")
    name: Sarah
    gender: F
    
    如果要继续传入年龄,城市信息怎么办? 我们如果规定一大堆位置参数,会使得调用函数的难度大大增加
    因为位置参数需要严格按照位置传入,python默认从左到右给参数赋值,而位置参数一旦增多,
    必然会要求函数调用者必须严格按照一定的顺序来,这样会导致出错的概率大大增加。
    在本例中,由于年龄、城市信息绝大多数学生是一致的,因此可以设定为默认值
    这样,大多数学生注册时不需要提供年龄和城市,只需提供必须的两个参数
    只有与默认参数不符的学生才需要提供额外的信息
    
    def enroll(name, gender, age = 6, city = "BeiJing"):
        print("name:",name)
        print("gender:",gender)
        print("age:",age)
        print("city:",city)
    
    >>> enroll("xiaowang", "M")
    name: xiaowang
    gender: M
    age: 6
    city: BeiJing
    
    >>> enroll("xiaoshitou", "F", 8, "shanghai")
    name: xiaoshitou
    gender: F
    age: 8
    city: shanghai
    
    >>> enroll("xiaojuzi","F", 12)
    name: xiaojuzi
    gender: F
    age: 12
    city: BeiJing
    
    >>> enroll("ligoudan","M", city="LA")
    name: ligoudan
    gender: M
    age: 6
    city: LA
    
    可见,默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现
    无论是简单调用还是复杂调用,函数只需要定义一个。
    
    有多个默认参数时,调用的时候,既可以按顺序提供默认参数
    比如enroll("Bob","M",7)
    意思是, 除了name和gender这两个参数外,最后一个参数应用在age上,
    city参数由于没有提供,仍然使用默认值。
    
    也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上,相当于指名道姓的给某某赋值
    比如调用enroll("Adam", "M", city= "tianjin")
    意思是, city参数用穿进去的值,其他默认参数继续使用默认值。
    
    注意:默认参数很有用,但使用不当也会掉坑里。默认参数有个最大的坑,演示如下:
    先定义一个函数,传入一个list,添加一个END再返回。
    
    def add_end(L=[]):
        L.append("END")
        return L
    
    当你正常调用时,结果似乎不错
    >>> add_end([1,2,3])
    [1, 2, 3, 'END']
    >>> add_end(["anything you want","shuzu","data",1985])
    ['anything you want', 'shuzu', 'data', 1985, 'END']
    
    当时使用默认参数时,一开始的结果也是对的:
    >>> add_end()
    ['END']
    
    但是,当你再次调用时,结果就不对了
    >>> add_end()
    ['END', 'END']
    >>> add_end()
    ['END', 'END', 'END']
    
    很多人开始疑惑,默认参数是一个空列表[], 但是函数似乎每次都记住了上次添加了“END”后的list
    原因解释如下:
    python函数在定义的时候,默认参数L的值就被计算出来了,即[]
    因为默认参数 L 也是一个变量,他指向对象[], 每次调用该函数,如果改变了 L 的内容,
    则下次调用时, 默认参数的内容就变了,不再是函数定义时的[]了。
    
    也就是说,如果你的默认参数是一个变量,而这个变量在函数执行的过程中会发生改变,那么
    当你下一次重新调用这个函数意图使用函数定义时的默认参数时,就不可能了,因为默认参数已经变了
    
    所以说,默认参数应该是一个固定的值,是大多数情况下不需要修改的值,必须指向不变对象!
    
    要修改上面的例子,可以用None这个不变对象来实现:
    
    def add_end(L = None):
        if L == None:
            L = []
            L.append("END")
            return L
        else:
            L.append("END")
            return L
    
    这样,无论调用多少次,都不会有问题,这样传入的参数会先进行判断,如果为空,则创建一个空列表,添加一个END返回
    >>> add_end([1,2,3])
    [1, 2, 3, 'END']
    >>> add_end()
    ['END']
    >>> add_end()
    ['END']
    >>> add_end()
    ['END']
    
    为什么要设计str、None这样的不可变对象呢? 因为不可变对象一旦创建,对象内部的数据就不能修改,
    这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取不需要加锁。
    同时读取一点问题都没有。
    我们在编写程序时,如果可以设计一个不可变对象,那就尽量设计成不变对象。
    
    什么是不可变对象?
    比如你创建一个字符串"李狗蛋",李狗蛋就是李狗蛋,不能变成其他的,至于你说,可以拆分成"李狗""李蛋"
    这不是发生了对字符串的更改,而是重新生成了两个字符串"李狗""李蛋",他们与原来字符串"李狗蛋"的关系
    仅仅是在原来字符串基础上衍生的新字符串,并没有对原字符串发生更改。
    
    
    
    三、可变参数
    
    在python函数中,还可以定义可变参数。
    顾名思义,可变参数就是传入的参数个数是可变的,可以是1个,2个,任意个。也可以0个。
    我们以数学题为例子:
    给定一组数字a, b, c....,请计算a的平方 + b的平方 + c的平方
    
    要定义出这个函数,我们必须确定输入的参数。然而,很悲伤,我们竟不知道有多少参数,
    即使知道有多少个,但是总不能在函数定义时全部写出来吧!
    
    我们可以显而易见的想到一个比较好的方法:把参数用一个列表或者元组传进来,然后遍历,再做运算,听起来不错。
    
    def sum(numbers):
        sum = 0
        for n in numbers:
            sum = sum + n
        return sum
    
    但是调用的时候,需要先组装出一个list或者tuple
    >>> sum([1,2,3,4,5])
    15
    
    还有更好的方法:函数定义时,设置可变参数。同
    样可以遍历传入的*numbers
    
    def sum(*numbers):
        sum = 0
        for n in numbers:
            sum += n
        return sum
    
    >>> sum(1,3,5,7)
    16
    >>> sum()
    0
    
    定义可变参数和定义一个list或者tuple参数相比,仅仅在参数前面加了一个 * 号。
    在函数内部, 参数number接受到的是一个tuple,因此,函数的代码完全不变。
    但是调用该函数时,额可以传入任意个参数,包括0个参数。
    着重理解一下:*numbers 在函数内部是一个元组形式,具备元组的一些功能。
    
    如果已经有一个现成的list或者tuple,要调用一个可变参数怎么办?
    可以这样做:
    >>> nums= [1, 2, 3]
    >>> sum(nums[0], nums[1], nums[2])
    6
    
    这种写法是可行的,但是相当繁琐。
    真正好的方法是在list或者tuple前面加一个 * ,好像一个反运算,又把list或者tuple的元素拆解出来传进去了
    
    >>> sum(*nums)
    6
    
    *nums表示把nums这个list的所有元素作为可变参数穿进去。这种写法相当有用,而且很常见。
    
    
    
    四、关键字参数
    可变参数允许你传入0个或者任意个参数,这些可变参数在函数调用时自动组装成为一个tuple。
    但是,你传入的这些不确定个数的参数仅仅是一个值,至于这个值是干什么,什么属性,一切都无从得知
    比如传入可变参数[1,3,5,7] 这是可以的,但是你想传入一个x,并且准备让x=9
    也就是说你想传成这样的形式[1,3,5,7,x = 9]
    这样该怎么办?
    
    使用关键字参数。
    关键字参数允许你传入0个或者任意个含参数名的参数,这些参数可以说有名有姓,有键有值
    这些关键字参数在函数内部自动组装为一个dict
    
    还是举小学生注册的例子:
    def person(name, age, **kwargs):
        print("name:",name, "age:",age, "other:",kwargs)
    
    函数person出了必选参数name和age外, 还接受关键字参数kwargs
    在调用该函数时,可以只传入必选参数
    
    >>> person("stephen curry", 29)
    name: stephen curry age: 29 other: {}
    
    当然咯,作为一个信息录入的函数,我们也十分喜欢用户传的信息详细些,比如这样
    >>> person("stephen curry", 29, gender = "M", tags="the famous basketball star")
    name: stephen curry age: 29 other: {'gender': 'M', 'tags': 'the famous basketball star'}
    
    
    所以, 关键字参数有什么用呢?
    它可以扩展函数的功能。
    比如,在person函数里,我们保证能接受到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。
    试想,你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数
    就能满足注册的需求。
    
    当然,也可以先组装一个dict,然后用 ** 分解开传进去
    >>> extra = {"reward":"twice mvp of NBA", "hobby":"golf"}
    >>> person("stephen curry", 29 **extra)
    name: stephen curry age: 29 other: {'reward': 'twice mvp of NBA', 'hobby': 'golf'}
    ** extra表示把extra这个dict的所有key-value用关键字参数传入到函数的 **kwargs
    kwargs将获得一个dict
    注意kwargs获得的dict是extra的一份拷贝,对kwargs的改动不会影响函数外的extra。
    
    五、命名关键字参数
    
    对于关键字参数,函数的调用者可以传入任意不收限制的关键字参数,至于到底传入了哪些
    就需要在函数内部通过kwargs检查
    仍以person为例,我们希望检查时候有city和job参数
    
    
    def person(name, gender, **kwargs):
    
        if "city" in kwargs:
            print("有city参数传入")
        if "job" in kwargs:
            print("有job参数传入")
    
        print("name:",name, "gender:",gender, "other:",kwargs)
    
    但是调用者仍然可以传入不受限制的关键字参数
    
    >>> person("stephen curry", "M", hobby="basketball", city="Golden States")
    有city参数传入
    name: stephen curry gender: M other: {'hobby': 'basketball', 'city': 'Golden States'}
    
    如果要限制关键字参数的名字,就可以用命名关键字参数, 例如,只接收city和job作为关键字参数。
    这种方式定义的函数如下:
    def person(name, age, *, city, job):
        print(name, age, city, job)
    
    命名关键字参数必须传入参数名,这和位置参数不同。
    >>> person("stephen curry",29, city="Colden States",job="basketball")
    stephen curry 29 Colden States basketball
    
    如果没有传入参数名,调用将报错
    >>> person("stephen curry",29,"GS","basketball")
    Traceback (most recent call last):
      File "<pyshell#85>", line 1, in <module>
        person("stephen curry",29,"GS","basketball")
    TypeError: person() takes 2 positional arguments but 4 were given
    
    
    由于调用时缺少参数名city和job,python解释器把这4个参数均视为位置参数,
    但是person()函数仅接受了2个位置参数。
    
    正所谓不传不行,不指名也不行。
    >>> person("xiaowang",24)
    Traceback (most recent call last):
      File "<pyshell#92>", line 1, in <module>
        person("xiaowang",24)
    TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job'
    >>> person("xiaowang",24, "beijing","program")
    Traceback (most recent call last):
      File "<pyshell#93>", line 1, in <module>
        person("xiaowang",24, "beijing","program")
    TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job'
    
    
    命名关键字参数可以有缺省值,从而简化调用:
    def person(name, age, *, city="BeiJing", job):
        print(name, age, city, job)
    由于命名关键字参数city具有默认值,调用时,可不传入city参数。
    >>> person("Jack",24,job="Engineer")
    Jack 24 BeiJing Engineer
    
    
    使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个 * ,如果缺少 * ,
    python解释器将无法是被位置参数和命名关键字参数
    def person(name, age, city, job):
        # 缺少 * city和job被视为位置参数
        pass
    
    命名关键字参数表示,只接收命名的关键字,多余的不再接受
    
    六、参数组合
    
    在python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。
    但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
    
    比如定义一个函数,包含上述若干种参数:
    def f1(a, b, c=0, *args, **kwargs):
        print("a = ", a, "b = ", b, "c = ", c, "args = ", args, "kwargs = ", kwargs)
    
    def f2(a, b, c=0, *, d, **kwargs):
        print("a = ", a, "b = ", b, "c = ", c, "d = ", d, "kwargs = ", kwargs)
    
    在函数调用的时候,python解释器会自动按照参数位置和参数名把对应的参数传进去。
    
    >>> f1(1,2)
    a =  1 b =  2 c =  0 args =  () kwargs =  {}
    >>> f1(1,2,c=3)
    a =  1 b =  2 c =  3 args =  () kwargs =  {}
    >>> f1(1,2,3)
    a =  1 b =  2 c =  3 args =  () kwargs =  {}
    >>> f1(1,2,3,4)
    a =  1 b =  2 c =  3 args =  (4,) kwargs =  {}
    >>> f1(1,2,3,4,5)
    a =  1 b =  2 c =  3 args =  (4, 5) kwargs =  {}
    >>> f1(1,2,3,4,5,6,7)
    a =  1 b =  2 c =  3 args =  (4, 5, 6, 7) kwargs =  {}
    >>> f1(1,2,3,4,5,6,x=10)
    a =  1 b =  2 c =  3 args =  (4, 5, 6) kwargs =  {'x': 10}
    
    
    >>> f2(1,2,3, d= 108, extra=None)
    a =  1 b =  2 c =  3 d =  108 kwargs =  {'extra': None}
    
    最神奇的是通过一个tuple和dict,你也可以调用上述函数:
    
    >>> f1(*args, **kwargs)
    a =  1 b =  2 c =  3 args =  (4,) kwargs =  {'d': 99, 'x': None}
    
    >>> f2(*args,**kwargs)
    a =  1 b =  2 c =  3 d =  99 kwargs =  {'tags': 'basketball'}
    
    所以,对于任意函数都可以通过类似func(*args, **kwargs)的形式调用它,因为加入星号*作为参数传入进函数时
    就相当于把参数拆解成一个个,如果传入的参数正确,函数会自动进行一一赋值。
    
    
    总结:
    
    python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
    
    注意:默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误。
    
    注意:定义可变参数和关键字参数的语法
    *args是可变参数, args接收的是一个tuple
    **kwargs 是关键字参数,kwargs接收的是一个dict
    
    注意:调用函数时如何传入可变参数和关键字参数的语法
    可变参数既可以直接传入:func(1,2,3),又可以先组装成list或者tuple通过*args传入:func(*(1,2,3))
    关键字参数既可以直接传入:func(a = 1, b = 2),又可以先组装dict,再通过**kwargs传入:func(**{"a":1,"b":2})
    
    使用*args 和 **kwargs 是python的习惯写法,当然也可用其他的写法,不过最好用习惯写法。
    
    命名的关键字参数是为了限制调用者可以传入的参数,同时可以提供默认值。
    
    注意:定义命名关键字参数在没有可变参数的情况下不要忘了写分隔符 * ,否则定义的将是位置参数。
  • 相关阅读:
    指针与引用
    const常量
    函数初始化列表
    Ubuntu18.04.3主力开发机使用记录(一)
    ZUI(BootStrap)使用vue动态插入HTMl所创建的data-toggle事件初始化方法
    一次JDBC支持表情存储的配置过程
    Springboot Rabbitmq 使用Jackson2JsonMessageConverter 消息传递后转对象
    搭建谷歌浏览器无头模式抓取页面服务,laravel->php->python->docker !!!
    Laravel 命令行工具之多线程同步大批量数据 DB连接混乱 解决方案
    nginx 之负载均衡 :PHP session 跨多台服务器配置
  • 原文地址:https://www.cnblogs.com/themost/p/7242970.html
Copyright © 2011-2022 走看看