一、动态参数

  1. def func(a,b,c,d,e,f,g):
  2. pass
  3. func(1,2,3,4,5,6,7)

如果加 30 个参数呢?

有没有万能的参数,可以代表一切参数呢?

*args 动态参数,万能参数

agrs 接收的就是实参对应的所有位置参数,并将其放在元组中

它不会接收关键字参数

  1. def func(*args):
  2. pass
  3. func(1,2,3,4,5,6,7)

打印返回值

  1. def func(*args):
  2. print(args)
  3. func(1,2,3,4,5,6,7)

执行输出:

(1, 2, 3, 4, 5, 6, 7)

结果是一个元组

二、形参对应顺序

  1. def func(*args,a,b,c,d,e='sex'):
  2. print(args,a,b,c,d,e)
  3. func(1,2,3,4,5)

执行报错,因为*args 接收了所有实参,所以缺少了 b,c,d 参数

TypeError: func() missing 4 required keyword-only arguments: ‘a’, ‘b’, ‘c’, and ‘d’

修改正确的位置

  1. def func(a,b,c,d,*args,e='sex'):
  2. print(a,b,c,d,args,e)
  3. func(1,2,3,4,5,6,7,8,e='女')

执行输出:

1 2 3 4 (5, 6, 7, 8) 女

形参对应顺序:

位置参数,*args,默认参数

*args 参数,可以不传,默认为空()

  1. def func(a,b,c,d,*args,e='sex'):
  2. print(a,b,c,d,args,e)
  3. func(1,2,3,4,e='女')

执行输出:

1 2 3 4 () 女

args 名字可以改的,但是约定成熟使用args

kwargs 也是动态参数,和args 不同的是,它只接收关键字参数*

kwargs 动态传参,他将所有的关键字参数(未定义的)放到一个字典中。**

  1. def func(a,b,c,**kwargs):
  2. print(kwargs)
  3. func(1,2,r=4,b1=5,c1=6,c=7)

执行输出:

{‘r’: 4, ‘c1’: 6, ‘b1’: 5}

执行没有报错,是因为函数接收参数后,它会从左边到右找,最后找到了 c

c=7 参数,在 a,b,c 里面已经定义好了,所以在输出的字典中,并未出现。

因为 kwargs 返回的是未定义的关键字参数。

**kwargs 正确的位置

  1. def func(a,b,c,d,*args,e='男',**kwargs):
  2. print(a,b,c,d,args,e,kwargs)
  3. func(1,2,3,4,5,6,7,v=3,m=7,h=9,e='女')

执行输出:

1 2 3 4 (5, 6, 7) 女 {‘v’: 3, ‘h’: 9, ‘m’: 7}

最终所有参数顺序:位置参数,*args,默认参数,kwargs**

如果函数含有多个未知参数,一般使用如下格式:

  1. def func1(*args,**kwargs):
  2. pass
  3. func1()

它能接收所有的参数

比如 len()源码

  1. def len(*args, **kwargs): # real signature unknown
  2. """ Return the number of items in a container. """
  3. pass

源码千万别不要修改

三、 * 魔法运用

有如下函数

  1. def func(*args):
  2. print(args)
  3. l1 = [1,2,30]
  4. l2 = [1,2,33,21,45,66]

需要将 2 个列表的所有元素赋值给 args,如何去做?

这个时候,需要用到*

  1. def func(*args):
  2. print(args)
  3. l1 = [1,2,30]
  4. l2 = [1,2,33,21,45,66]
  5. func(*l1)
  6. func(*l1,*l2)

执行输出:

(1, 2, 30)

(1, 2, 30, 1, 2, 33, 21, 45, 66)

传两个字典给**kwargs

  1. def func(**kwargs):
  2. print(kwargs)
  3. dic1 = {'name':'jack','age':22}
  4. dic2 = {'name1':'rose','age1':21}
  5. func(**dic1,**dic2)

执行输出:

{‘name’: ‘jack’, ‘age’: 22, ‘name1’: ‘rose’, ‘age1’: 21}

**kwargs 只限于字典类型

在函数的调用执行时,

*可迭代对象,代表打散(list,tuple,str,dict(键))将元素一一添加到 args。

字典,代表打散,将所有键值对放到一个 kwargs 字典里。**

  1. def func(*args,**kwargs):
  2. print(args,kwargs)
  3. dic1 = {'name':'jack','age':22}
  4. dic2 = {'name1':'rose','age1':21}
  5. func(*[1,2,3,4],*'asdk',**dic1,**dic2)

执行输出:

(1, 2, 3, 4, ‘a’, ‘s’, ‘d’, ‘k’) {‘age1’: 21, ‘name’: ‘jack’, ‘age’: 22, ‘name1’: ‘rose’}

四、命名空间和作用域

  1. a = 4
  2. b = 3
  3. c = [1,2,3,4]
  4. c1 = {'name':'alex'}
  5. def func1():
  6. name = '老男孩'
  7. print(name)
  8. func1()

当执行函数的时候,他会在内存中开辟一个临时名称空间,存放函数体内的所有变量与值的关系,

随着函数的执行完毕,临时空间自动关闭。

input(),print(),len 内置函数

函数里面的变量,在函数外面能直接引用么?

  1. def func1():
  2. m = 1
  3. print(m)
  4. print(m) # 这行报的错

执行报错:

NameError: name ‘m’ is not defined

上面为什么会报错呢?现在我们来分析一下 python 内部的原理是怎么样:

我们首先回忆一下 Python 代码运行的时候遇到函数是怎么做的,从 Python 解释器开始执行之后,就在内存中开辟里一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读如内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。

等执行到函数调用的时候,Python 解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量回储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。

我们给这个‘存放名字与值的关系’的空间起了一个名字———-命名空间。

代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间;

在函数的运行中开辟的临时的空间叫做局部命名空间。

python 之禅

  1. import this

执行输出:

  1. The Zen of Python, by Tim Peters
  2. Beautiful is better than ugly.
  3. Explicit is better than implicit.
  4. Simple is better than complex.
  5. Complex is better than complicated.
  6. Flat is better than nested.
  7. Sparse is better than dense.
  8. Readability counts.
  9. Special cases aren't special enough to break the rules.
  10. Although practicality beats purity.
  11. Errors should never pass silently.
  12. Unless explicitly silenced.
  13. In the face of ambiguity, refuse the temptation to guess.
  14. There should be one-- and preferably only one --obvious way to do it.
  15. Although that way may not be obvious at first unless you're Dutch.
  16. Now is better than never.
  17. Although never is often better than *right* now.
  18. If the implementation is hard to explain, it's a bad idea.
  19. If the implementation is easy to explain, it may be a good idea.
  20. Namespaces are one honking great idea -- let's do more of those!

在 python 之禅中提到过:命名空间是一种绝妙的理念,让我们尽情的使用发挥吧!

命名空间一共分为三种:

全局命名空间

局部命名空间

内置命名空间

*内置命名空间中存放了 python 解释器为我们提供的名字:input,print,str,list,tuple…它们都是我们熟悉的,拿过来就可以用的方法。

三种命名空间之间的加载与取值顺序:

加载顺序:内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)

取值顺序:

在局部调用:局部命名空间->全局命名空间->内置命名空间

在全局调用:全局命名空间->内置命名空间

综上所述,在找寻变量时,从小范围,一层一层到大范围去找寻。

作用域

作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域。

全局作用域:包含内置名称空间全局名称空间,在整个文件的任意位置都能被引用、全局有效

局部作用域:局部名称空间,只能在局部范围内生效

局部变量举例

  1. name = '老男孩'
  2. def func1():
  3. name = 'taibai'
  4. print(name)
  5. func1()

执行输出:

taibai

执行 func1()方法时,进入函数体内部,加载 name 变量,执行 print(name),从函数体找到了 name 变量,直接输出 taibai

  1. name = '老男孩'
  2. def func1():
  3. print(name)
  4. func1()

执行输出:

老男孩

函数执行到 print(name)时,从函数体中寻找 name 变量,发现没有。从外部中寻找,找到了,就输出,否则报错。

取值是从内到外

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597369908346-1ca6ccfa-b387-4ffb-abb0-1e41542cc353.png)
  1. a =2
  2. print(a)
  3. def func1():
  4. age = 11
  5. print(age)
  6. func1()

执行输出:

2

11

函数什么时候执行? 函数调用的时候执行。

代码从上至下依次执行, 调用函数:函数里面从上至下依次执行。

  1. print(111)
  2. def func1():
  3. print(333)
  4. def func2():
  5. print(444)
  6. def func3():
  7. print(555)
  8. func2()
  9. func1()
  10. print(222)

执行输出:

111

333

222

  1. print(111)
  2. def func1():
  3. print(333)
  4. func2()
  5. print(666)
  6. def func2():
  7. print(444)
  8. def func3():
  9. print(555)
  10. func2()
  11. func1()
  12. print(222)

执行输出:

111

333

444

666

222

分析下面一个例子

  1. def f1():
  2. def f2():
  3. def f3():
  4. print("in f3")
  5. print("in f2")
  6. f3()
  7. print("in f1")
  8. f2()
  9. f1()

执行输出:

in f1

in f2

in f3

1.执行函数 f1(),加载函数变量 f2,执行 print(“in f1”),输出 in f1

2.执行函数 f2(),加载函数变量 f3,执行 print(“in f2”),输出 in f2

3.执行函数 f3(),执行 print(“in f3”),输出 in f3

globals 和 locals 方法

在全局调用 globals 和 locals

在局部调用 locals 和 globals

  1. print(globals()) #全局名称空间:所有变量
  2. print(locals()) #局部名称空间,所有变量
  1. a = 2
  2. b = 3
  3. def func1():
  4. c = 5
  5. d = 6
  6. print(globals()) #全局变量放在一个字典中
  7. print(locals()) #局部变量{'c': 5, 'd': 6}
  8. func1()

执行输出:

{‘doc‘: None, ‘cached‘: None, ‘b’: 3, ‘package‘: None, ‘builtins‘: , ‘file‘: ‘E:/pythonscript/day10/hanshu.py’, ‘a’: 2, ‘spec‘: None, ‘func1’: , ‘name‘: ‘main‘, ‘_loader‘: <_frozen_importlib_external.SourceFileLoader object at 0x000001DE74036CF8>}

{‘c’: 5, ‘d’: 6}

globals()和 locals()一般很少用,在函数逻辑比较复杂的情况下,可能会用到。

  1. def func1():
  2. name = '老男孩'
  3. print(name)
  4. print(name)

执行报错:

NameError: name ‘name’ is not defined

因为 name 是一个局部变量,只有当函数运行时,它才会加载到内存中

global 关键字,nonlocal 关键字

global:

1,声明一个全局变量。

2,在局部作用域想要对全局作用域的全局变量进行修改时,需要用到 global(限于字符串,数字)。

  1. def func1():
  2. global name
  3. name = '老男孩'
  4. print(name)
  5. func1()
  6. print(name)

执行输出:

老男孩

老男孩

  1. name = '金角大王' #全局变量
  2. def func1():
  3. global name #声明局部变量为全局变量
  4. name = '老男孩' #局部变量
  5. #print(name)
  6. func1()
  7. print(name)

执行输出:

老男孩

因为全局变量 name 被函数体的 global name 覆盖了

nonlocal:

1,不能修改全局变量。

2,在局部作用域中,对父级作用域的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。

  1. a = 4
  2. def func1():
  3. nonlocal a
  4. a = 5 #修改全局变量
  5. #print(name)
  6. func1()
  7. print(a)

执行输出:

SyntaxError: no binding for nonlocal ‘a’ found

不允许修改全局变量

  1. def func1():
  2. b = 6
  3. def func2():
  4. print(666)
  5. func2()
  6. func1()

执行输出:

666

  1. def func1():
  2. b = 6
  3. def func2():
  4. nonlocal b #表示可以影响父级,也就是 func1()
  5. b = 666 #重新赋值
  6. print(b)
  7. func2() #这个时候,影响了 b 的值,输出 666
  8. func1()

执行输出:

666

666

  1. def func1():
  2. b = 6
  3. def func2():
  4. b = 666
  5. print(b)
  6. func2()
  7. print(b) #父级不受影响
  8. func1()

执行输出:

666

6

  1. def add_b(): #不受 dd_nonlocal 影响
  2. b = 42
  3. def do_global():
  4. b = 10 #影响子级函数,b 都是 10
  5. print(b)
  6. def dd_nonlocal():
  7. nonlocal b #只能影响父级,也就是 do_global()
  8. b = b + 20 #b=10+20 也就是 30
  9. print(b)
  10. dd_nonlocal()
  11. print(b)
  12. do_global()
  13. print(b)
  14. add_b()

执行输出:

10

30

30

42

课后作业:

  1. 1、继续整理函数相关知识点,写博客。
  2. 2、写函数,接收 n 个数字,求这些参数数字的和。(动态传参)
  3. 3、读代码,回答:代码中,打印出来的值 a,b,c 分别是什么?为什么?
  4. a=10
  5. b=20
  6. def test5(a,b):
  7. print(a,b)
  8. c = test5(b,a)
  9. print(c)
  10. 4、读代码,回答:代码中,打印出来的值 a,b,c 分别是什么?为什么?
  11. a=10
  12. b=20
  13. def test5(a,b):
  14. a=3
  15. b=5
  16. print(a,b)
  17. c = test5(b,a)
  18. print(c)
  19. 相关面试题(先从纸上写好答案,然后在运行):
  20. 1,有函数定义如下:
  21. def calc(a,b,c,d=1,e=2):
  22. return (a+b)*(c-d)+e
  23. 请分别写出下列标号代码的输出结果,如果出错请写出 Error
  24. print(calc(1,2,3,4,5))_____ print(calc(1,2))____print(calc(e=4,c=5,a=2,b=3))___
  25. print(calc(1,2,3))_____ print(calc(1,2,3,e=4))____print(calc(1,2,3,d=5,4))_____
  26. 2,下面代码打印的结果分别是_________,________,________.
  27. def extendList(val,list=[]):
  28. list.append(val)
  29. return list
  30. list1 = extendList(10)
  31. list2 = extendList(123,[])
  32. list3 = extendList('a')
  33. print('list1=%s'%list1)
  34. print('list2=%s'%list2)
  35. print('list3=%s'%list3)

答案:

2、写函数,接收 n 个数字,求这些参数数字的和。(动态传参)

  1. def summation(*args,**kwargs):
  2. the_sum = 0
  3. for i in args:
  4. the_sum += int(i)
  5. for j in kwargs.values(): #kwargs 是字典
  6. the_sum += int(j)
  7. return the_sum
  8. print(summation(1,2,3,c=6,d=7))

执行输出:

19

args 用来接收位置参数,*kwargs 用来接收关键字参数,它的返回值是字典,所以必须要 kwargs.values()

3、读代码,回答:代码中,打印出来的值 a,b,c 分别是什么?为什么?

  1. a=10
  2. b=20
  3. def test5(a,b):
  4. print(a,b)
  5. c = test5(b,a)
  6. print(c)

代码从上向下执行:

1.加载变量 a,b,函数名 test5

2.执行 test5 函数,传 2 个位置参数 20,10

3.函数接收 2 个变量,根据位置变量一一对应。a = 20,b=10

4.执行 print(a,b),输出 20 10

5.执行 print(c),输出 None,因为函数没有给返回值,为什么呢?因为没有 return。

综上所述:

a,b,c 分别是 20,10,None

4、读代码,回答:代码中,打印出来的值 a,b,c 分别是什么?为什么?

  1. a=10
  2. b=20
  3. def test5(a,b):
  4. a=3
  5. b=5
  6. print(a,b)
  7. c = test5(b,a)
  8. print(c)

代码从上向下执行:

1.加载变量 a,b,函数名 test5

2.执行函数 test5,传位置参数 20,10

3.函数 test5 接收 2 个位置参数,根据位置关系匹配,a=20,b=10

4.局部变量 a=3,b=5,重新赋值了,覆盖接收的 2 个参数。

5.执行 print(a,b),输出 3,5

6.执行 print(c),输出 None,因为函数没有 return

综上所述:

a,b,c 分别是 3,5,None

相关面试题(先从纸上写好答案,然后在运行):

1,有函数定义如下:

  1. def calc(a,b,c,d=1,e=2):
  2. return (a+b)*(c-d)+e

请分别写出下列标号代码的输出结果,如果出错请写出 Error。

print(calc(1,2,3,4,5))_ print(calc(1,2))_print(calc(e=4,c=5,a=2,b=3))

print(calc(1,2,3))_ print(calc(1,2,3,e=4))print(calc(1,2,3,d=5,4))_

1.print(calc(1,2,3,4,5))

执行函数 calc,a,b,c,d,e 分别对应 1,2,3,4,5

(a+b)(c-d)+e 等同于 (1+2)(3-4)+5

先计算括号的 3*-1 = -3 最后计算加法 -3+5 = 2

2.print(calc(1,2))

执行函数 calc,a,b,c 参数是必须要传的,这一步只传了 2 个参数,所以执行报错

3.print(calc(e=4,c=5,a=2,b=3))

执行函数 calc,a,b,c,d,e 分别对应 2,3,5,1,4 由于没有传 d,所以为默认值 1

(a+b)(c-d)+e 等同于 (2+3)(5-1)+4

先计算括号的 5*4=20,最后计算加法 20+4 = 24

4.print(calc(1,2,3))

执行函数 calc,a,b,c,d,e 分别对应 1,2,3,1,2 由于没有传 c,d 所以默认值为 1,2

(a+b)(c-d)+e 等同于 (1+2)(3-1)+2

先计算括号的 3*2=6 最后计算加法 6+2 = 8

5.print(calc(1,2,3,e=4))

执行函数 calc,a,b,c,d,e 分别对应 1,2,3,1,4 由于没有传 d,所以为默认值 1

(a+b)(c-d)+e 等同于 (1+2)(3-1)+4

先计算括号的 3*2=6 最后计算加法 6+4 = 10

6.print(calc(1,2,3,d=5,4))

执行函数 calc,由于关键字参数(d=5)必须在位置参数的后面,所以执行报错

所以最后结果为

print(calc(1,2,3,4,5))2_ print(calc(1,2))Errorprint(calc(e=4,c=5,a=2,b=3))24_

print(calc(1,2,3))8_ print(calc(1,2,3,e=4))10print(calc(1,2,3,d=5,4))Error_

2,下面代码打印的结果分别是_,__,__.

  1. def extendList(val,list=[]):
  2. list.append(val)
  3. return list
  4. list1 = extendList(10)
  5. list2 = extendList(123,[])
  6. list3 = extendList('a')
  7. print('list1=%s'%list1)
  8. print('list2=%s'%list2)
  9. print('list3=%s'%list3)

分析代码:

  1. def extendList(val,list=[]):
  2. list.append(val)
  3. print(id(list)) #由于 list 是局部变量,只能在这里打印内存地址
  4. return list
  5. list1 = extendList(10) #var=10,list 为默认参数,它是空列表,执行 append,最终结果 list1 = [10]
  6. list2 = extendList(123,[]) #var=123,list 的值被覆盖,开辟了一个新的内存地址,执行 append,最终结果 list2 = [123]
  7. list3 = extendList('a') #var='a',list 为默认参数,由于默认 list 用的是同一个内存地址,此时 list 为[10],执行 append,最终结果 list3 = [10,'a']
  8. print('list1=%s'%list1) #输出 list1=[10,'a'],因为 list1 和 list3 使用的是默认值,共用一个内存地址。所以这 2 个值是一样的。
  9. print('list2=%s'%list2) #输出 list2=[123]
  10. print('list3=%s'%list3) #输出 list3=[10,'a']

执行输出:

1499634951496

1499634952520

1499634951496

list1=[10, ‘a’]

list2=[123]

list3=[10, ‘a’]

可以看出,list1 和 list3 的 list 局部变量,用的是同一个内存地址,而 list2 用的是新的内存地址。

所以最终结果为:

list1=[10, ‘a’]

list2=[123]

list3=[10, ‘a’]

6,默写:

1,形参的接收顺序。

顺序从左至右:

位置参数,args,默认参数,*kwargs

2,什么是命名空间,什么是全局名称空间,什么是局部名 称空间,什么是内置名称空间。

命名空间:存放名字与值的关系的空间

全局命名空间:代码在运行伊始,创建的存储“变量名与值的关系”的空间

局部命名空间:在函数的运行中开辟的临时的空间

内置名称空间: python 自带的名字,如 print、int、str,解释器启动就会生效

3,什么是全局作用域,什么是局部作用域?

全局作用域:包含内置名称空间、全局名称空间,在整个文件的任意位置都能被引用、全局有效

局部作用域:局部名称空间,只能在局部范围内生效

4,名称空间的加载顺序,取值顺序?

加载顺序:内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)

取值顺序:

在局部调用:局部命名空间->全局命名空间->内置命名空间

在全局调用:全局命名空间->内置命名空间

综上所述,在找寻变量时,从小范围,一层一层到大范围去找寻。

5,解释一下什么是 global,什么是 nonlocal?

global:

1,声明一个全局变量。

2,在局部作用域想要对全局作用域的全局变量进行修改时,需要用到 global(限于字符串,数字)。

nonlocal:

1,不能修改全局变量。

2,在局部作用域中,对父级作用域的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。