一、函数

1、函数

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

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

   Python使用def语句定义函数

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

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

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

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


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

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

  return语句表示函数的返回值

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

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

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

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

In [3]: f1
Out[3]: 

In [4]: f1()
hello

In [5]: a = f1

In [6]: b = f1()
hello

In [7]: a
Out[7]: 

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 = l1

In [76]: l2
Out[76]: [1, 2, 3]

In [77]: l3 = l1.copy()

In [78]: l3
Out[78]: [1, 2, 3]

In [79]: id(l3)             # l3和l1应用的是不同内存对象
Out[79]: 140149284135624

In [80]: id(l1)
Out[80]: 140149295996616

In [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)
8
Out[28]: 8

In [29]: add(3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in ()
----> 1 add(3)

TypeError: add() missing 1 required positional argument: 'y'

In [30]: add(3, 5, 8)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in ()
----> 1 add(3, 5, 8)

TypeError: add() takes 2 positional arguments but 3 were given


In [42]: add(3, "5")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in ()
----> 1 add(3, "5")

 in add(x, y)
      1 def add(x, y):
----> 2     ret = x + y
      3     print('{} + {} = {}'.format(x, y, ret))
      4     return ret

TypeError: 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 = 8
Out[32]: 8


2、关键字参数

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

In [33]: add(x=3, y=5)
3 + 5 = 8
Out[33]: 8

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


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

In [37]: add(3, y=5)
3 + 5 = 8
Out[37]: 8

In [38]: add(x=3, 5)
  File "", line 1
    add(x=3, 5)
            ^
SyntaxError: positional argument follows keyword argument


In [39]: add(3, x=5)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in ()
----> 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]: 5

In [48]: inc(3)
Out[48]: 4

In [49]: inc(3, x=4)
Out[49]: 7

In [56]: inc(base=4)
Out[56]: 5

In [57]: inc(base=4, x=5)
Out[57]: 9


In [50]: def inc(x=1, base):
    ...:     return base + x
    ...: 
  File "", 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]: 0

In [65]: sum(1)
Out[65]: 1

In [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)

host => localhost
port => 3300


In [79]: connect("localhost", port=3300)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in ()
----> 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 "", 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)
2
3
(4, 5, 6)
{'a': 1, 'b': 2}


In [95]: fn(2, 3)
2
3
()
{}


In [97]: fn(2, y=3)
2
3
()
{}


In [99]: fn(2, 3, 4)
2
3
(4,)
{}

In [100]: fn(2, 3, a=23)
2
3
()
{'a': 23}


In [107]: def fn(*args, x):    # 位置可变参数是否可以在普通参数之前
     ...:     print(args)
     ...:     print(x)
     ...:     

In [108]: fn(1, 2, 3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in ()
----> 1 fn(1, 2, 3)

TypeError: fn() missing 1 required keyword-only argument: 'x'

In [109]: fn(1, 2, x=3)
(1, 2)
3

In [110]: fn(1, x=3)
(1,)
3

In [111]: fn(x=3)
()
3


In [112]: def fn(**kwargs, x):     # 关键字可变参数是否可以在普通参数之前
     ...:     print(kwargs)
     ...:     print(x)
     ...:     
  File "", 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 "", line 1
    fn(x=3, 4)
           ^
SyntaxError: positional argument follows keyword argument


In [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 "", line 1
    def fn(**kwargs, x=5):
                     ^
SyntaxError: invalid syntax


In [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 = 3

In [145]: t = [1, 2]

In [146]: add(*t)
1 + 2 = 3

In [143]: t = [1, 2, 3]

In [144]: add(*t)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in ()
----> 1 add(*t)

TypeError: add() takes 2 positional arguments but 3 were given


In [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 = 3

In [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]: 3

In [163]: sum(*[1, 2])
Out[163]: 3

In [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)
 in ()
----> 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)
 in ()
----> 1 fn(1)

TypeError: fn() takes 0 positional arguments but 1 was given

In [181]: fn(x=1)
1

In [182]: fn(1, x=2)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in ()
----> 1 fn(1, x=2)

TypeError: fn() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given

In [183]:


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

In [188]: def fn(x, *, y):
     ...:     print(x)
     ...:     print(y)
     ...:     

In [189]: fn(1, y=2)
1
2

In [193]: def fn(x=1, *, y=2):
     ...:     print(x)
     ...:     print(y)
     ...:     
     ...:     

In [194]: fn()
1
2


In [199]: def fn(x=1, *, y):   # 
     ...:     print(x)
     ...:     print(y)
     ...:     
     ...:    

In [200]: fn(y=3)
1
3

In [201]: fn(3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in ()
----> 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)
1
2

In [207]: def fn(x, y, *):  # *号不能写在最后
     ...:     print(x)
     ...:     print(y)
     ...:     
  File "", line 1
    def fn(x, y, *):
                ^
SyntaxError: named arguments must follow bare *