[Python系列] Python函数及函数式编程(三)

第二节 参数

定义函数的时候,我们把参数的名字和位置定下来,对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了.调用者不需要关心函数的实现.

但是Python中函数的参数很灵活,具体体现在传递参数有多种形式上.因此本节我们重点介绍几种不同形式的参数和调用方式.

参数的分类

Python中,参数分为形参和实参两种

形参即为形式上的参数,是函数定义时规定的,函数调用时用以接收实际的值

实参是实际的参数,是我们调用参数时传入的实际的值

def rectangle_area(width,height): #形参
    area = width * height
    return area

r_area = rectangle_area(100,200.0) #实参
print("100*200的长方形面积:{0:.2f}".format(r_area))

Python中,形参和实参有着不同的形式,我们可以将其进行分类,引用Python手册上的定义,分类如下:

argument – 实参

  • 关键字参数: 在函数调用中前面带有标识符(例如 name=)或者作为包含在前面带有 ** 的字典里的值传入.

    以下的35都被称为关键字参数

    complex(real=3, imag=5)
    complex(**{'real': 3, 'imag': 5})
    
  • 位置参数: 不属于关键字参数的参数。位置参数可出现于参数列表的开头以及/或者作为前面带有 * 的元素被传入.举例来说,35 在以下调用中均属于位置参数:

    complex(3, 5)
    complex(*(3, 5))
    

parameter – 形参

  • positional-or-keyword:位置或关键字,指定一个可以作为位置参数传入也可以作为 关键字参数 传入的实参。这是默认的形参类型,例如下面的 foobar:

    def func(foo, bar=None): ...
    
  • positional-only:仅限位置,指定一个只能按位置传入的参数。Python 中没有定义仅限位置形参的语法。但是一些内置函数有仅限位置形参(比如 abs())。

  • keyword-only:仅限关键字,指定一个只能通过关键字传入的参数。仅限关键字形参可通过在函数定义的形参列表中包含单个可变位置形参或者在多个可变位置形参之前放一个 * 来定义,例如下面的 kw_only1kw_only2:

    def func(arg, *, kw_only1, kw_only2): ...
    
  • var-positional:可变位置,指定可以提供由一个任意数量的位置参数构成的序列(附加在其他形参已接受的位置参数之后)。这种形参可通过在形参名称前加缀 * 来定义,例如下面的 args:

    def func(*args, **kwargs): ...
    
  • var-keyword:可变关键字,指定可以提供任意数量的关键字参数(附加在其他形参已接受的关键字参数之后)。这种形参可通过在形参名称前加缀 ** 来定义,例如上面的 kwargs

Python规定了形参能够接受什么样的参数,实参能够传递什么样的参数.上面的描述有些晦涩难懂,下面我们将从形参的角度对Python参数传递的方式进行详细的解释

形参

接收位置参数

位置参数(positional arguments),顾名思义,就是和位置息息相关的参数,在Python中,位置参数的使用频率很高.

我们举一个例子,假定我们在开发一个论坛,需要有一个能够输出用户姓名和年龄的函数

def user_info(name,age):
    print("{0:s},年龄{1:d}".format(name,age))
    return None
user_info("Marry",19)

调用时就可以按照形参的位置传入实参,此时实参传递的可以是位置参数

Marry,年龄19

但是如果我们如下调用

user_info(19,"Marry")
user_info("Marry")

系统都会报错.

当函数接收位置参数时,形参和实参需要一一对应


参数默认值

假如我们的user_info函数不仅仅想要输出用户的姓名和年龄,还想要输出用户的所在地,只需要稍加更改即可

def user_info(name,age,area):
    print("{0:s},年龄{1:d},所在地是{2:d}".format(name,age,area))
    return None

不过有些用户不太愿意透露自己的所在地,不想填写area

user_info调用时传入的实参个数可能没有area,这样必然会导致报错,那么,如何解决这个问题呢?

这时候就可以用到参数默认值了,我们可以把默认参数看做实参的"缺省"值.默认参数的写法如下

def user_info(name,age,area="Beijing"):
    print("{0:s},年龄{1:d},所在地是{2:s}".format(name,age,area))

这样,我们在调用user_info时,即便不传入area实参,函数执行时,形参area也会有值,这个值就是我们事先准备好的"缺省"值

user_info("Lily",18)
# Lily,年龄18,所在地是Beijing

user_info("Lucy",19,"ShangHai")
# Lucy,年龄19,所在地是Shanghai

这个"缺省"值,就是我们的参数默认值,如果有实参传入,参数默认值便会被覆盖.

通常情况下,默认参数可以简化函数的调用,但是在使用时我们需要注意以下要点:

要点
  1. 有默认值的参数应该放置在最后

    看下面的例子

    def user_info(area="Beijing",name,age):
        print("{0:s},年龄{1:d},所在地是{2:s}".format(name,age,area))
    

    如果用户不想填写area,我们的函数如何执行?

    user_info("Smith",22)
    

    上述调用必然出现错误.我们的第一个实参"Smith",其实赋值给了形参area

  2. 参数默认值应该指向不变对象

    如果参数默认值是可变对象(列表,字典以及大多数类的实例)时,该参数值的改变会影响后续的函数执行.

    这句话听起来可能比较抽象,我们再次举一个简单的例子

    def f(a,L=[]):
        L.append(a)
        return L
    

    函数体非常简单,我们希望将传入的位置参数添加到一个空列表中去,每次的返回值都是仅有一个元素a的列表,但是执行之后的结果却和想象的不同

    print(f(1))
    print(f(2))
    print(f(3))
    

    这将输出

    [1]
    [1, 2]
    [1, 2, 3]
    

    这是什么原因呢?

    Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

    要修改上面的例子,我们可以将可变对象[]写做不可变对象None

    def f(a, L=None):
        if L is None:
            L = []
        L.append(a)
        return L
    

    如果可以设计一个不变对象,那就尽量设计成不变对象,这样会减少很多不必要的麻烦,这也是我们编写程序的技巧之一.


接收关键字参数

在本节前几个例子中,我们一般按照顺序传递一些值,也就是实参给函数调用,但是可读性不是很好

user_info("Lily",18)

代码阅读者只看上述函数调用,不太清楚传入的实参到底是什么.为了提高函数调用的可读性,在函数调用时可以使用关键字参数作为实参

采用关键字参数,在函数定义时不需要做额外的工作

def user_info(name,age,area="Beijing"):
    print("{0:s},年龄{1:d},所在地是{2:s}".format(name,age,area))

在调用时采用key=value形式传入参数即可

user_info(name="Lucy",age=19)

甚至也不需要考虑参数传入的顺序问题

user_info(age=19,name="Lucy")

上述两个函数调用的结果是一样的

Lucy,年龄19,所在地是Beijing

从上述代码可见,采用关键字参数,调用者能够清晰的看出传递参数的含义,关键字参数对于有多个参数的函数调用非常有用.

在下文,我们还有一些关于关键字参数的知识,不过在这之前,我们需要关注一些要点

要点
  1. 有参数默认值的形参,也可以接受关键字实参传递

    def user_info(name,age,area="Beijing"):
        print("{0:s},年龄{1:d},所在地是{2:s}".format(name,age,area))
    

    这个例子中,我们同样可以传递关键字参数,覆盖area的参数默认值

    user_info(name="Lucy",age=19,area="Hebei")
    
  2. 函数可以同时接收位置参数和关键字参数,一旦有实参是关键字参数,那么其后面的所有的实参都必须采用关键字参数

    我们依旧使用上例

    def user_info(name,age,area="Beijing"):
        print("{0:s},年龄{1:d},所在地是{2:s}".format(name,age,area))
    

    此时,我们尝试几个不同的调用方式

    user_info("Lucy",age=19)
    # 正常输出
    
    user_info(name="Lucy",19)
    # 报错 SyntaxError: positional argument follows keyword argument
    
    user_info("Lucy",age=19,area="Hebei")
    # 正常输出
    
    user_info("Lucy",age=19,"Hebei")
    # 报错 SyntaxError: positional argument follows keyword argument
    

    由此可见,我们在使用关键字参数时,一定要注意参数的传递规则

一般情况下,形参既可以接收位置参数,也可以接受关键字参数,这种形参即为positional-or-keyword 位置或关键字参数

但是形参还可以接收更为复杂的参数传递.


接收可变参数

用户的信息不仅仅只包含"姓名",“年龄”,和"所在地",如果用户想要更多的输入自己的个人信息,作为商家,肯定是十分欢迎的

user_info("Lucy",19,"Hebei", addr='Baoding')

对我们现在的函数来说,上述调用肯定会报错,我们该如何解决这个问题呢?

定义函数时,有时候我们不确定调用的时候会传递多少个参数(不传参也可以).此时,可用打包(packing)位置参数,或者打包关键字参数,来进行参数传递,会显得非常方便.

  1. 打包位置参数

    我们以数学为例,给定一组数字a,b,c…,请计算a+b+c+…

    这需要定义函数来进行叠加运算,但是我们无法确定形参个数,因为实参,也就是我们进行加法运算的值的个数也是不确定的.

    每次调用传入参数的个数都可能不同,所以我们不妨把参数定义为"一个"

    def _sum(*args):
        print(args)
    

    args是形参的名字,*args表示,不管你来多少参数,我一并给你打包!

    _sum(1,2,3,4,5,[0],{"name":"lisi"})
    # (1, 2, 3, 4, 5, [0], {'name': 'lisi'})
    

    我们最终得到的args,是所有传递的实参组成的元组.

    这里需要注意,一个*号表示的可变参数无法接收关键字参数

    _sum(name="lisi",age=19)
    # TypeError: _sum() got an unexpected keyword argument 'name'
    

    因此,我们的例子可以写作

    def _sum(*args):
        sum = 0
        for n in args:
            sum = sum + n
        return sum
    
    print(_sum(1,2,5,10))
    

    这种情况下,形参接收的实参仍然是位置参数,但是,个数却不确定了,这种形参我们称为var-positional:可变位置参数

  2. 打包关键字参数

    通过对包裹位置传递的学习,我们应该能够猜到包裹关键字传递的作用了

    没错,我们要把所有不确定个数的关键字参数一并打包!

    我们可以传入0个或者任意个有着key=>value关系的关键字参数,他们会在函数内部会组装一个字典

    def user_info(name,age,**kw):
        print(name,"年龄",age,"other:",kw)
    

    此时我们的用户就可以随意书写自己想写的内容了

    user_info("lucy",19,gender='M',job='Engineer')
    # lucy 年龄 19 other: {'gender': 'M', 'job': 'Engineer'}
    

    此时的可变参数使用的是 **加变量名kw来打包不定个数的关键字参数

    这种情况下,形参接收的实参仍然是关键字参数,但是,个数却不确定了,这种形参我们称为 var-keyword:可变关键字参数

我们了解了两种传递可变参数的方式之后,同样需要做一些思考,我们能过接收可变关键字,也可以接收可变位置参数,那么有没有只接收位置参数或者只接收关键字参数的形参呢?

回到手册的说明,我们可以看到

  • positional-only:仅限位置,指定一个只能按位置传入的参数。Python 中没有定义仅限位置形参的语法。
  • keyword-only:仅限关键字,指定一个只能通过关键字传入的参数, 这种语法是存在的

接下来我们就继续讲述,如何限定形参只能接受关键字参数


接收参数仅限关键字

对于可变参数,函数的调用者可以传入任意不受限制的关键字参数.至于到底传入了哪些,就需要在函数内部通过kw检查,这会导致极大的不便利.

def user_info(name,**kw):
    
    # 对**kw打包的字典筛选

    print("姓名",name)

user_info("Lily",zipcode="010000",gender='M',job='Engineer')

我们需要限制关键字参数的名字,例如,只接收areazipcode作为关键字参数,可以这么定义:

def user_info(name,*,area,zipcode):
    print("姓名",name,"所在地",area,"邮编",zipcode)

user_info("lily",area="Beijing",zipcode="010000")
# 姓名 lily 所在地 Beijing 邮编 010000

这种限制规定关键字参数必须传入参数名,这和普通的关键字参数不同。如果没有传入参数名,调用将报错:

def user_info(name,*,area,zipcode):
    print("姓名",name,"所在地",area,"邮编",zipcode)

user_info("lily","Beijing",zipcode="010000")
# ypeError: user_info() takes 1 positional argument but 2 positional arguments (and 1 keyword-only argument) were given

如果函数定义中已经有了一个可变位置参数,后面跟着的关键字参数就不再需要一个特殊分隔符*了,此时这些关键字参数必须传入参数名

def user_info(name,*args,area,zipcode):
    print("姓名",name,args,"所在地",area,"邮编",zipcode)

user_info("lily","你好~",area="Beijing",zipcode="010000")
# 姓名 lily ('你好~',) 所在地 Beijing 邮编 010000

user_info("lily","你好~","Beijing",zipcode="010000")
# TypeError: user_info() missing 1 required keyword-only argument: 'area'

如果不命名,*args会直接将"Beijing" 作为位置参数接收.


形参的混合使用

形参的种类有很多,很多情况下需要混合使用,这就需要我们掌握定义时形参的排列顺序

可变参数的放置位置很有讲究,我们来看一个例子

def user_info(name,age,area="beijing",*args,**kw):
    print(name,"年龄",age,"tuple",args,"所在地",area,"other:",kw)

此时我们调用函数

user_info("lily",19,"Shanghai","haha",gender='M',job='Engineer')

# lily 年龄 19 tuple ('haha',) 所在地 Shanghai other: {'gender': 'M', 'job': 'Engineer'}

没有错误,*args可以看作位置参数,应当放置到所有已经定义的位置/默认参数之后

不过如果调换位置:

def user_info(*args,name,age,area="beijing",**kw):
    print(name,"年龄",age,"tuple",args,"所在地",area,"other:",kw)
    
user_info("lily",19,"Shanghai","haha",gender='M',job='Engineer')
# user_info() missing 2 required keyword-only arguments: 'name' and 'age'

*args提前到位置函数之前,函数调用无法传入nameage,因为一并被*args打包了

同时**kw可以看做关键字参数,它不能置于任何位置参数之前,也不能置于任何仅限关键字参数传递的实参之前,如下两例

放在位置函数之前:

def user_info(name,**kw,age,area="beijing",*args):
    print(name,"年龄",age,"tuple",args,"所在地",area,"other:",kw)

# 放置在位置参数之前 未运行即报错    
# SyntaxError: invalid syntax

默认情况下:

def user_info(name,age,area="beijing",*args,zipcode,**kw):
    print(name,"年龄",age,"tuple",args,"所在地",area,"邮编",zipcode,"other:",kw)
    
user_info("lily",19,"Shanghai","haha",zipcode="010000",gender='M',job='Engineer')
# lily 年龄 19 tuple ('haha',) 所在地 Shanghai 邮编 010000 other: {'gender': 'M', 'job': 'Engineer'}

如果将**kw放置到zipcode之前,会直接出现编译错误

def user_info(name,age,area="beijing",*args,**kw,zipcode):
    print(name,"年龄",age,"tuple",args,"所在地",area,"邮编",zipcode,"other:",kw)
       
# SyntaxError: invalid syntax

因此,我们得出了宝贵的结论

  1. 可变位置参数:可接受任意数量的位置参数(元组);只能作为最后一个位置参数出现,其后参数均为关键字参数
  2. 可变关键字参数:可接受任意数量的关键字参数(字典);只能作为最后一个参数出现

实参

在上面的文章中,我们主要讲解了形参的定义形式,也了解到函数调用时传入的实参只用两种形式,位置参数和关键字参数,但是还有一些问题我们需要说明

在手册中,位置参数和关键字参数也可以写作*args**kw,这是什么呢?他们和形参中的*args,**kw有什么关系?

在定义函数时,我们采用*args接收不确定数目的位置参数,打包成元组 ,采用**kw接收不确定数目的关键字参数打包成字典.

有打包(packing)当然也就有解包(unpacking),解包出现在以下两个场景中

  1. 在实参传递元组时,我们可以让每一个元素对应一个位置参数

    def print_hello(name,sex):
        print(name,sex)
        
    args = ("Lucy","女")
    print_hello(*args)
    

    *会将元组拆包成位置参数

  2. 在实参传递字典时,我们可以让每一组键值对作为一个关键字参数传递给函数

    def print_hello(name,sex):
        print(name,sex)
        
    kw = {"name":"Lily","sex":"女"}
    print_hello(**kw)
    

    **会将字典拆包成关键字参数

这也印证了实参只有位置参数和关键字参数的说明

小结

  1. 当函数接收位置参数时,形参和实参需要一一对应
  2. 有默认值的形参应该放置在最后
  3. 参数默认值应该指向不变对象
  4. 函数可以同时接收位置参数和关键字参数,一旦有实参是关键字参数,那么其后面的所有的实参都必须采用关键字参数
  5. 可变位置参数:可接受任意数量的位置参数;只能作为最后一个位置参数出现,其后参数均为关键字参数
  6. 可变关键字参数:可接受任意数量的关键字参数;只能作为最后一个参数出现
  7. 对于函数调用简单原理可以看如下链接
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值