Python学习笔记009—函数

时间:2021-07-28 16:47:10

1. 空函数

如果想定义一个什么事也不做的空函数,可以用pass语句:

def nop():
    pass

pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

2. 容易混肴的函数使用方式

函数的赋值其实就是将函数名指向函数体。

2.1 函数名其实也是一个变量

例如,

def func():
    return "Python"

print (func())
print (func)
func = 100
print (func)
print (func())

输出结果

Python
<function func at 0x7f4aa8a66f28>
100
Traceback (most recent call last):
  File "test.py", line 27, in <module>
    print (func())
TypeError: 'int' object is not callable

第一次输出是调用函数的输出结果

第二次输出是函数所在的物理存储位置

第三次输出是将函数名(变量)重新绑定一个值后的输出结果

第四次输出是因为“整型不能函数调用”,因为将func绑定100后,func已经成为整型变量,func()是进行的函数调用,所以报错!

2.2 函数的绑定规则

情形1

def func(a):
    return a

b = func(6)
print(b)

输出结果

6

情形2

def func(a):
    return a

b = func
print(b(6))

输出结果

6

情形3(该情况类似情形1)

def func(a=[100,200]):
    return a

x = func()

x.append(1)
print("x:", x)

输出结果

X: [100, 200, 1]

情形4(该情况类似情形2)

def func(a=[100,200]):
    return a

x = func

x().append(1)
print("x:", x())

输出结果

x: [100, 200, 1]

情形5

def func(a=[100,200]):
    return a

x = func

x().append(1)
print("x:", x)

输出结果

x: <function func at 0x7f818e8d5f28>

该输出结果为函数所在的位置

情形6

def func(a=[100,200]):
    return a

x = func

x.append(1)
print("x:", x)

输出结果

Traceback (most recent call last):
  File "test.py", line 24, in <module>
    x.append(1)
AttributeError: 'function' object has no attribute 'append'

错误的原因是:函数“function”对象没有“append”属性

2.3 函数的绑定方式

def func(a=[100,200]):
    return a

x = func()
y = func()

print(func)
print(id(x))
print(id(y))

x.append(1)
print("y:", y)

y.append(2)
print("x:", x)

x.append(3)
print("x:", x)
print("y:", y)

输出结果

<function func at 0x7feed4d4ef28>
140663724970760
140663724970760
y: [100, 200, 1]
x: [100, 200, 1, 2]
x: [100, 200, 1, 2, 3]
y: [100, 200, 1, 2, 3]

我们发现,x和y绑定的函数为一个地址,也即当x绑定值改变时,y值也随之也改变。

但是函数 func 与函数 x 、 y 的地址是不一样的;将140663724970760转成16进制时,得到

>>> hex(140098127414024)
'0x7f6b231e4308'

所以地址是有区别的!

2.4 序列传参(元组传参)

代码片段1

def func(a,b):
    print(a+b)

func(1,2)
func(*(1,2))

输出结果

3
3

形参是与实参不一致时

def func(a,b):
    print(a+b)

func(*(1))

运行结果

Traceback (most recent call last):
  File "test01.py", line 4, in <module>
    func(*(1))
TypeError: func() argument after * must be an iterable, not int

本段代码产生错误的原因是元组形式错误导致的。在*后面必须是可迭代的,而不是整数

执行错误后,没有向下执行。

代码片段2

def func(a,b):
    print(a+b)

func(*(1,))

运行结果

Traceback (most recent call last):
  File "test01.py", line 4, in <module>
    func(*(1,))
TypeError: func() missing 1 required positional argument: 'b'

该段代码运行错误是因为实参少于形参个数

代码片段3

def func(a,b):
    print(a+b)

func(*(1,2,3))

运算结果

Traceback (most recent call last):
  File "test01.py", line 4, in <module>
    func(*(1,2,3))
TypeError: func() takes 2 positional arguments but 3 were given

该段代码运行错误的原因是因为实参多于形参个数

总结:当仅有元组传参时,实参个数必须与实参个数一致

代码片段4

def info(*var):
    print(var)
    for a in var:
        print(a)

info(*(1,2,3))

print("------------")

info(1,2,3)

输出结果

(1, 2, 3)
1
2
3
------------
(1, 2, 3)
1
2
3

2.4 序列传参(列表传参)

def func(a,b):
    print(a+b)

func(*[1,2])

输出结果

3

其实列表传参与元组传参在功能上没有明显区别

2.5 关键字传参

关键字传参只要关键字匹配就可以,顺序可以改变。

def func(a,b):
    print(a+b)
func(b=1,a=2)
func(a=1,b=2)

输出结果

33

当改变一下

def func(a,b):
    print(a+b)

func(a=1,2)

输出结果

  File "test01.py", line 4
    func(a=1,2)
            ^
SyntaxError: positional argument follows keyword argument

语法错误:位置参数在关键字参数后

有位置参数的,位置参数必须写在关键参数前面;换句话说,一旦出现关键字参数,其后不能出现位置参数

def func(a,b):
    print(a+b)

func(1,a=2)

输出结果

Traceback (most recent call last):
  File "test01.py", line 4, in <module>
    func(1,a=2)
TypeError: func() got multiple values for argument 'a'

错误类型:函数的参数a得到了多个值。

很明显,当形参和实参位置赋值完毕后,再按照关键字参数再赋值,最后发现形参a有多个值,而没有报形参b值缺少。可以说明实参向形参传值时不存在形同形参覆盖问题。

2.6 字典传参

代码片段1

def print_str(a, b):
    print(b)
    print(a)

d = {"b":2,"a":1}
print_str(**d)

输出结果

2
1

代码片段2

def print_str(a, b):
    print(b)
    print(a)

print_str("b":1,"a":2)

输出结果

  File "test01.py", line 5
    print_str("b":1,"a":2)
                 ^
SyntaxError: invalid syntax

语法错误,这样写不是字典所要求的输入形式

代码片段3

def print_str(a, b):
    print(b)
    print(a)

print_str({"b":1,"a":2})

输出结果

Traceback (most recent call last):
  File "test01.py", line 5, in <module>
    print_str({"b":1,"a":2})
TypeError: print_str() missing 1 required positional argument: 'b'

因为缺少参数 b  而报错,该段代码中将字典 {"b":1,"a":2} 作为一个参数传给了 a  ;将其进行修改

代码片段4

def print_str(a, b):
    print(a)
    print(b)

print_str({"b":1,"a":2},1)

输出结果

{'b': 1, 'a': 2}
1

不难看出,该段代码中将字典当作一个参数传给形参 a  ,将实参 1  传给形参 b  ,其实,这就是位置传参

其实字典传参的输入形式如代码5所示

代码片段5

def print_str(a, b):
    print(a)
    print(b)

print_str(**{"b":1,"a":2})

输出结果

2
1

代码片段6

def print_str(**dict):
    print(dict)

print_str(**{"b":1,"a":2})

输出结果

{'a': 2, 'b': 1}

代码片段7

def print_str(**dict):
    print(dict)

print_str({"b":1,"a":2})

输出结果

Traceback (most recent call last):
  File "test01.py", line 4, in <module>
    print_str({"b":1,"a":2})
TypeError: print_str() takes 0 positional arguments but 1 was given

函数有0个位置参数,但是实参却有一个别给定

如果按照这个错误提示,当没有任何实参时,应该也不会报错

代码片段8

def print_str(**dict):
    print(dict)
    ")

print_str( )

输出结果

{}
123

代码片段9

def print_str(**dict):
    print(dict)
    ")

print_str("b":1,"a":2)

输出结果

  File "test01.py", line 5
    print_str("b":1,"a":2)
                 ^
SyntaxError: invalid syntax

说明代码述写的时候已经出错

代码片段10

def print_str(**dict):
    print(dict)

print_str(b=1,a=2)

输出结果

{'a': 2, 'b': 1}

代码片段11

def func(**kwargs):

    print(kwargs)

    print("参数个数:", len(kwargs))
    for k,v in kwargs.items():
        print(k, "->", v)

func(x = 10, y = 20, z = 30)

d = {"x":10, "y":20, "z":30}
func(**d)

func(**{"a":30, "b":40, "c":50, "d":60})

func(**{"a":"你好", "b":40, "c":50, "d":60})

输出结果

{'y': 20, 'z': 30, 'x': 10}
参数个数: 3
y -> 20
z -> 30
x -> 10
{'y': 20, 'z': 30, 'x': 10}
参数个数: 3
y -> 20
z -> 30
x -> 10
{'a': 30, 'c': 50, 'b': 40, 'd': 60}
参数个数: 4
a -> 30
c -> 50
b -> 40
d -> 60
{'a': '你好', 'c': 50, 'b': 40, 'd': 60}
参数个数: 4
a -> 你好
c -> 50
b -> 40
d -> 60

3 混合传参

混合传参形式是将位置传参、关键字传参、序列传参(元组传参、列表传参)、字典传参结合使用。

3.1 位置传参+序列传参

序列传参中的元组传参与列表传参没明显区别,本处仅以元组传参为例。

代码片段1

def print_info(arg1, *var):
    print(" 参数1:", arg1)
    print(" 其他参数:")
    print(var)
    for a in var:
        print(a)

print_info(100,)
print("------------")

print_info(100)
print("------------")

输出结果

 参数1: 100
 其他参数:
()
------------
 参数1: 100
 其他参数:
()
------------

当实参满足第一个位置形参后,即使后面的元组参数是空也不报错

代码片段2

def print_info(arg1, *var):
    print(" 参数1:", arg1)
    print(" 其他参数:")
    print(var)
    for a in var:
        print(a)

print_info(100, 1, 3)
print("------------")

print_info(100, *(1, 3))
print("------------")

输出结果

 参数1: 100
 其他参数:
(1, 3)
1
3
------------
 参数1: 100
 其他参数:
(1, 3)
1
3
------------

当前面的实参个数满足位置参数个数后,后面的实参均默认为元组形参中的数值。

注意:以本代码为例,第一个位置参数不限于数字、字符串,也可以是元组或字典,为了更清楚地表达,见代码片段3

代码片段3

def print_info(arg1,arg2,arg3, *var):
    print(" 参数1:", arg1)
    print(" 参数1:", arg2)
    print(" 参数1:", arg3)
    print(" 其他参数:")
    print(var)
    for a in var:
        print(a)

print_info((100,200),300,{"a":400,"b":500},600,700,800,900)

输出结果

 参数1: (100, 200)
 参数1: 300
 参数1: {'b': 500, 'a': 400}
 其他参数:
(600, 700, 800, 900)
600
700
800
900

代码片段4

def print_info(arg1, *var):
    print(" 参数1:", arg1)
    print(" 其他参数:")
    print(var)
    for a in var:
        print(a)

a = (1,2)
b = [1,2]

print_info(100,a,200,300,b)
print("---------------------")
print_info(100,*a,200,300,*b)

输出结果

 参数1: 100
 其他参数:
((1, 2), 200, 300, [1, 2])
(1, 2)
200
300
[1, 2]
---------------------
 参数1: 100
 其他参数:
(1, 2, 200, 300, 1, 2)
1
2
200
300
1
2

注意述写时候的细节

3.2 位置传参+字典传参

def func(a, **kwargs):
   print (" 参数 a 是", a)

   print(kwargs)

   for key in kwargs:
       print(key, kwargs[key])

func(10, y = 100, x = 200, z = 1000)

输出结果

 参数 a 是 10
{'x': 200, 'z': 1000, 'y': 100}
x 200
z 1000
y 100

3.3 位置参数+序列参数+字典参数

函数代码均一样,如下段代码所示;该标题下的其他代码为函数调用代码,函数代码再调用代码中省略。

代码片段1

def print_args(arg1,  *targs, **kwargs):
    print(" arg1 是", arg1)
    print(" targs:")

    for t in targs:
        print (t)

    print(" kwargs:")
    for key in kwargs:
        print(key, kwargs[key]) 

代码片段2

print_args(1)

输出结果

 arg1 是 1
 targs:
 kwargs:

代码片段3

print_args(1, b=")

输出结果

 arg1 是 1
 targs:
 kwargs:
c 3
b 2

代码片段4

print_args(1,2,3,4,b=")

输出结果

 arg1 是 1
 targs:
2
3
4
 kwargs:
b 2
c 3

代码片段5

a = 20
b = 30
t = (20, 100, 200)

print_args(10, b, *t, 4, b = 2, c = 3)
print("-------------------------")
print_args(10, b, *t, 4, **{"b":2, "c": 3})

输出结果

 arg1 是 10
 targs:
30
20
100
200
4
 kwargs:
b 2
c 3
-------------------------
 arg1 是 10
 targs:
30
20
100
200
4
 kwargs:
b 2
c 3

代码片段6

a = 20
b = 30
t = (20, 100, 200)

print_args(10, b, b = 2, c = 3, *t, )

输出结果

 arg1 是 10
 targs:
30
20
100
200
 kwargs:
c 3
b 2

代码片段7

本段代码是将函数的形参顺序进行修改,修改后直接报错

  File "test01.py", line 1
    def print_args(arg1,  **targs, *kwargs):
                                 ^
SyntaxError: invalid syntax

可以发现,形参的顺序是固定不变的。而实参的顺序是可以改变的。

4 特殊的规则

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收cityjob作为关键字参数。这种方式定义的函数如下:

def student(name, age, *, school, address):
    print('name:', name, 'age:', age, 'school',
          school, 'address', address)

student("zhang", 24, address = "北京海淀", school = "清华")

student("zhang", 24, **{"school": "清华", "address":"北京海淀"})

输出

name: zhang age: 24 school 清华 address 北京海淀name: zhang age: 24 school 清华 address 北京海淀

*后面的参数被视为命名关键字参数。

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

def person(name, age, *args, city, job):
    print(name, age, args, city, job)

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:

>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given

以上仅是联系的一部分。

也可以参考廖雪峰的本章节博客https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431752945034eb82ac80a3e64b9bb4929b16eeed1eb9000

5 函数是否可执行属性

#函数名本质是变量

>>> callable(print)
True
>>> callable(print())
False
>>> callable(a)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

>>> b = 1
>>> callable(b)
False
>>>