【函数】01、函数基础

时间:2021-05-11 18:37:24

一、函数

1、函数

  函数是python中组织代码的最小单元

   函数是实现模块化编程的基本组件

   Python使用def语句定义函数

  每个Python函数都有一个返回值,默认为None,也可以使用“return value”明确定定义返回值

  def语句会创建一个函数对象,并同时创建一个指向函数的对象引用

  函数也是对象,可以存储在组合数据类型中,也可以作为参数传递给其它函数

  callable()可用于测试对象是否可调用


  函数通过def定义,接着函数名,函数名后面用一对小括号列出参数列表,使用一个冒号开始函数体。

  函数体是正常的Python语句,可以组合任意结构

  return语句表示函数的返回值

  函数有输入(参数)和输出(返回值),函数其实是一个代码单元,把输入转化为输出。

  定义函数的时候并不会执行函数体,当调用函数时才会执行函数体

  函数通过函数名来调用,函数名后面一对小括号里面传入实参

In [2]: def f1():                ...:     print("hello")
   ...:     

In [3]: f1
Out[3]: <function __main__.f1>

In [4]: f1()
hello

In [5]: a = f1

In [6]: b = f1()
hello

In [7]: a
Out[7]: <function __main__.f1>

In [8]: b

In [9]: type(a)
Out[9]: function

In [10]: type(b)
Out[10]: NoneType

In [15]: callable(a)   # 实现了__call__()方法就可调用
Out[15]: True


2、对于Python的函数,我们需要记住的是

 1)函数的默认返回值是None。

 2)python是一个自上而下逐行解释并执行的语言。因此,函数的定义必须在函数被调用之前。同名的函数,后定义的会覆盖前面定义的。

 3)程序执行的时候,遇到函数定义只会先将函数整体读进内存,并不立刻执行。等到函数被调用的时候才执行函数体。

 4)python函数的参数传递的是值传递还是引用传递。

    函数参数传递本质上和变量整体复制一样,只是两个变量分别为形参a和实参b。那么,a=b后,a变了,b值是否跟着变呢?这取决于对象内容可变不可变


值传递:

     在调用函数时,将实际参数复制一份传递给函数,函数对参数进行修改将不会影响到实际参数

     适用于不可变对象(如int, str,tuples等)作为参数传递时,例如元组

引用传递

     指调用函数时,将实际参数的地址传递给函数,函数对参数进行修改,将影响实际参数

     适用于可变对象(如list,dict,类的实例等)作为参数传递时,例如列表


浅复制:(也叫影子复制)

      只复制父对象,不会复制对象的内部的子对象
深复制

      复制对象及其子对象

赋值是引用传递

In [74]: l1 = [1, 2, 3]In [75]: l2 = l1In [76]: l2Out[76]: [1, 2, 3]In [77]: l3 = l1.copy()In [78]: l3Out[78]: [1, 2, 3]In [79]: id(l3)             # l3和l1应用的是不同内存对象Out[79]: 140149284135624In [80]: id(l1)Out[80]: 140149295996616In [81]: id(l2)              # l2和l1引用的是同一个内存对象Out[81]: 140149295996616


二、函数的参数

对于函数,最重要的知识点莫过于参数了。

参数分为形式参数(形参)和实际参数(实参)。

 def f1(a, b, c):    pass     f1(1, 2, 3)

  其中,a,b,c就是形参,1,2,3就是实参,也就是实际要传递的参数。


In [27]: def add(x, y):    ...:     print(x + y)    ...:     return x  + y    ...: In [28]: add(3, 5)8Out[28]: 8In [29]: add(3)---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-29-c9889e45a711> in <module>()----> 1 add(3)TypeError: add() missing 1 required positional argument: 'y'In [30]: add(3, 5, 8)---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-30-0b52ebc12bc6> in <module>()----> 1 add(3, 5, 8)TypeError: add() takes 2 positional arguments but 3 were givenIn [42]: add(3, "5")---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-42-d73b87d3f8c1> in <module>()----> 1 add(3, "5")<ipython-input-31-4950d8722875> in add(x, y)      1 def add(x, y):----> 2     ret = x + y      3     print('{} + {} = {}'.format(x, y, ret))      4     return retTypeError: unsupported operand type(s) for +: 'int' and 'str'

函数调用时,传入的实参必须和函数定义时的行参想匹配,如果不匹配会抛出TypeError.

Python中的形式参数有以下几种:


1、位置参数

  通常在传递参数的时候我们按照参数的位置,逐一传递,这叫“位置参数”。

In [31]: def add(x, y):    ...:     ret = x + y    ...:     print('{} + {} = {}'.format(x, y, ret))    ...:     return ret    ...: In [32]: add(3, 5)  3 + 5 = 8Out[32]: 8


2、关键字参数

  而有时候我们会用“形参名”=“值”的方式传递参数,这叫“关键字参数指定参数”。

In [33]: add(x=3, y=5)3 + 5 = 8Out[33]: 8In [34]: add(y=3, x=5)  # 关键字参数和顺序无关5 + 3 = 8Out[34]: 8


位置参数和关键字参数混用时关键字参数必须在位置参数后面

In [37]: add(3, y=5)3 + 5 = 8Out[37]: 8In [38]: add(x=3, 5)  File "<ipython-input-38-165b39de39ac>", line 1    add(x=3, 5)            ^SyntaxError: positional argument follows keyword argumentIn [39]: add(3, x=5)---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-39-b0f1746a3413> in <module>()----> 1 add(3, x=5)TypeError: add() got multiple values for argument 'x'


3、默认参数

  默认参数是为某些参数设定一个默认值,既可以减少参数传入量,也可以享受使用默认值的便利。

默认参数必须位于参数列表的最后部分

默认参数是在函数定义时,指的是形参!

In [46]: def inc(base, x=1):    ...:     return base + x    ...: In [47]: inc(3, 2)Out[47]: 5In [48]: inc(3)Out[48]: 4In [49]: inc(3, x=4)Out[49]: 7In [56]: inc(base=4)Out[56]: 5In [57]: inc(base=4, x=5)Out[57]: 9In [50]: def inc(x=1, base):    ...:     return base + x    ...:   File "<ipython-input-50-ac010ba50fd9>", line 1    def inc(x=1, base):           ^SyntaxError: non-default argument follows default argument


4、可变(动态)参数

 Python的动态参数有两种,分别是*args和**kwargs,这里面的关键是一个和两个星号,而不是args和kwargs,实际上你可以使用*any或**whatever的方式,但就如self一样,潜规则我们使用*args和**kwargs。

可变参数是在函数定义时,指的是形参!


*args位置可变参数;函数定义时参数名前加一个星号

           一个星号表示接受任意个动态参数。调用时,会将实际参数打包成一个元组传入函数

           此时只能通过位置参数传参

In [63]: def sum(*args):    ...:     ret = 0    ...:     for i in args:    ...:         ret += i     ...:     return ret    ...: In [64]: sum()Out[64]: 0In [65]: sum(1)Out[65]: 1In [66]: sum(1, 2, 3, 8)Out[66]: 14


**kwargs关键字可变参数;函数定义时参数名前加两个星号

                  两个星表示接受键值对的动态参数,数量任意。调用的时候会将实际参数打包成字典

                  此时只能通过关键字参数传参

例如:

In [73]: def connect(**kwargs):    ...:     print(type(kwargs))    ...:     for k, v in kwargs.items():    ...:         print('{} => {}'.format(k, v))    ...:         In [74]: connect(host="localhost", port=3300)<class 'dict'>host => localhostport => 3300In [79]: connect("localhost", port=3300)---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-79-6aeadd0e08a5> in <module>()----> 1 connect("localhost", port=3300)TypeError: connect() takes 0 positional arguments but 1 was given


万能参数:

 当*args和**kwargs组合起来使用,理论上能接受任何形式和数量的参数,在很多代码中我们都能见到这种定义方式。需要注意的是,*args必须出现在**kwargs之前。

In [83]: def fn(*args, **kwargs):    ...:     print(args)    ...:     print(kwargs)    ...:     In [84]: fn(2, 3, 5, x=11, y="xxj")(2, 3, 5){'x': 11, 'y': 'xxj'}In [85]: def fn(**kwargs, *args):    ...:     print(args)    ...:     print(kwargs)  File "<ipython-input-85-ae6e58836952>", line 1    def fn(**kwargs, *args):                     ^SyntaxError: invalid syntax


可变参数和普通参数混合使用:

In [91]: def fn(x, y, *args, **kwargs):    ...:     print(x)    ...:     print(y)    ...:     print(args)    ...:     print(kwargs)    ...:     In [92]: fn(2, 3, 4, 5, 6, a=1,b=2)23(4, 5, 6){'a': 1, 'b': 2}In [95]: fn(2, 3)23(){}In [97]: fn(2, y=3)23(){}In [99]: fn(2, 3, 4)23(4,){}In [100]: fn(2, 3, a=23)23(){'a': 23}In [107]: def fn(*args, x):    # 位置可变参数是否可以在普通参数之前     ...:     print(args)     ...:     print(x)     ...:     In [108]: fn(1, 2, 3)---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-108-9ab6b0a400c3> in <module>()----> 1 fn(1, 2, 3)TypeError: fn() missing 1 required keyword-only argument: 'x'In [109]: fn(1, 2, x=3)(1, 2)3In [110]: fn(1, x=3)(1,)3In [111]: fn(x=3)()3In [112]: def fn(**kwargs, x):     # 关键字可变参数是否可以在普通参数之前     ...:     print(kwargs)     ...:     print(x)     ...:       File "<ipython-input-112-3226e57525e0>", line 1    def fn(**kwargs, x):                     ^SyntaxError: invalid syntax

位置可变参数也可以在普通参数之前,但是在位置可变参数之后的普通参数变成了keyword-only参数(只能以关键字参数登入)

关键字可变参数不可以在普通参数之前(为什么?)



可变参数和默认参数混合使用:

In [113]: def fn(x=5, *args):     ...:     print(x)     ...:     print(args)     ...:     In [114]: fn(1)1()In [115]: fn(1,2)1(2,)In [116]: fn(1,2,3,4)1(2, 3, 4)In [117]: fn(x=3, 4)  File "<ipython-input-117-2d95cecddace>", line 1    fn(x=3, 4)           ^SyntaxError: positional argument follows keyword argumentIn [121]: def fn(*args, x=5):  # 位置参数在默认参数之前没限制     ...:     print(x)     ...:     print(args)     ...:     In [122]: fn()5()In [123]: fn(1, 2)5(1, 2)In [124]: fn(1)5(1,)In [125]: fn(1, x=3)3(1,)In [127]: def fn(**kwargs, x=5):     ...:     print(x)     ...:     print(kwargs)     ...:      File "<ipython-input-127-069a751edec8>", line 1    def fn(**kwargs, x=5):                     ^SyntaxError: invalid syntaxIn [128]: def fn(x=5, **kwargs):     ...:     print(x)     ...:     print(kwargs)     ...:          ...:    In [129]: fn()5{}In [130]: fn(1)1{}In [131]: fn(1, a=2)1{'a': 2}In [132]: fn(x=1, a=2)1{'a': 2}

可变位置参数在默认参数之后,默认参数不能使用关键字传参

可变关键字参数不能在默认参数之前

当默认参数和可变参数一起出现时,默认参数相当于普通参数



小结:

函数的参数规则这么多,头都大了;这里我们建议的函数参数使用用法:
1)默认参数靠后

 2)可变参数靠后

 3)默认参数和可变参数不同时出现

       不遵守不一定错,遵守代码可读性高


当我们需要同时使用默认参数和可变参数时怎么办?

我们通常这样处理:

In [139]: def connect(host='127.0.0.1', port=3306, user='root', password='', **kwargs):     ...:     pass     ...:      In [140]: def connect(**kwargs):     ...:     host = kwargs.pop('host', '127.0.0.1')     ...:


三、参数解构

1、参数解构

调用函数时,传入实参时加一个星号,可以把iterable解构成位置参数

调用函数时,传入实参时加两个个星号,可以把dict解构成关键字参数

In [141]: def add(x, y):     ...:     ret = x + y     ...:     print('{} + {} = {}'.format(x, y, ret))     ...:     In [142]: add(1, 2)1 + 2 = 3In [145]: t = [1, 2]In [146]: add(*t)1 + 2 = 3In [143]: t = [1, 2, 3]In [144]: add(*t)---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-144-d90b03560d6e> in <module>()----> 1 add(*t)TypeError: add() takes 2 positional arguments but 3 were givenIn [147]: t = [(1, 2), (3, 4)]In [148]: add(*t)             In [148]: add(*t)(1, 2) + (3, 4) = (1, 2, 3, 4)# 字典解构成关键字参数In [153]: d = {'x':1, 'y':2}In [154]: add(**d)1 + 2 = 3In [155]: add(*d)x + y = xy


2、参数解构和可变参数混用

In [161]: def sum(*args):     ...:     ret = 0     ...:     for i in args:     ...:         ret += i     ...:     return ret     ...: In [162]: sum(*(1, 2))Out[162]: 3In [163]: sum(*[1, 2])Out[163]: 3In [165]: sum(*range(5))Out[165]: 10



3、参数解构的限制

  关键字参数解构,key必须是str

In [167]: def fn(**kwargs):     ...:     print(kwargs)     ...:     In [168]: fn(**{'a':1}){'a': 1}In [170]: fn(**{12:1})---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-170-99986e5a722a> in <module>()----> 1 fn(**{12:1})In [211]: def fn(*args, **kwargs):     ...:     print(args)     ...:     print(kwargs)     ...:     In [212]: fn(*[1, 2, 3])(1, 2, 3){}In [214]: fn(*[1, 2, 3], **{'a':1, 'b':2})(1, 2, 3){'a': 1, 'b': 2}


4、keyword-only参数

星号之后的参数只能通过关键字参数传入

可变位置参数之后的参数也是keyword-only参数

只能通过关键字参数传入的参数就交keyword-only参数

keyword-only参数可以有默认值

In [179]: def fn(*, x):     ...:     print(x)     ...:     In [180]: fn(1)---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-180-cb5d79cf2c77> in <module>()----> 1 fn(1)TypeError: fn() takes 0 positional arguments but 1 was givenIn [181]: fn(x=1)1In [182]: fn(1, x=2)---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-182-8eb9b719c886> in <module>()----> 1 fn(1, x=2)TypeError: fn() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were givenIn [183]:


keyword-only参数与其它参数混用:

In [188]: def fn(x, *, y):     ...:     print(x)     ...:     print(y)     ...:     In [189]: fn(1, y=2)12In [193]: def fn(x=1, *, y=2):     ...:     print(x)     ...:     print(y)     ...:          ...:     In [194]: fn()12In [199]: def fn(x=1, *, y):   #      ...:     print(x)     ...:     print(y)     ...:          ...:    In [200]: fn(y=3)13In [201]: fn(3)---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)<ipython-input-201-f005f2a6106f> in <module>()----> 1 fn(3)TypeError: fn() missing 1 required keyword-only argument: 'y'In [202]: In [205]: def fn(*, x, y):   # *号后可以有多个keyword-only参数      ...:     print(x)     ...:     print(y)     ...:     In [206]: fn(x=1, y=2)12In [207]: def fn(x, y, *):  # *号不能写在最后     ...:     print(x)     ...:     print(y)     ...:       File "<ipython-input-207-6c060b52ac76>", line 1    def fn(x, y, *):                ^SyntaxError: named arguments must follow bare *