Day04:函数参数、对象、嵌套、闭包函数和装饰器

上节课复习:
    1.什么是函数
        函数就是具备某一功能的工具
    2.为何用函数
        1.程序的组织结构和可读性
        2.减少代码冗余
        3.扩展性强
    3.如何用函数
        函数分为两大类:
            内置函数:len
            自定义函数: 1.有参 2.无参 3.空函数
        语法:
            def函数名(arg1,arg2)
                '''
                注释
                '''
                code1
                code2
                return 返回值
        调用函数: 函数名(1,2,3)
        函数必须遵循:先定义,后调用
        定义阶段:只检测语法,与执行函数体代码
        调用阶段:通过函数名找到函数,然后执行函数整体代码
        函数名:用来访问函数的内存地址,加上括号用来执行函数
        函数参数:是外调用者为函数体传值的媒介
        函数体代码:是函数功能的具体实现
        函数返回值是函数体执行的成果,不写return默认返回None
            1.返回值没有类型限制
            2.返回值没有个数限制
                无return,默认返回None
                return值1:返回1
                return值1,2,3,:返回值(1,2,3)
            return注意点:是函数结束标志,函数体内可以有多个return,但是只要执行一次return,即代表函数结束

今日内容:
1.函数参数的使用
2.函数对象
3.函数嵌套
4.名称空间与作用域
5.闭包函数(2+3+4)
6.装饰器
一.函数参数的类型:
  1.形参与实参
    形参:定义函数时,括号内指定的参数,本质就是调用名
    实参:调用函数时,括号内传入的值
    只有在调用函数时才会在函数体内发生实参(值)与形参(变量名)的绑定关系
    该绑定关系只在调用函数时临时生效,在调用函数结束时解除绑定
 
1 def foo(x,y): #括号内x,y 为形参
2     print(x)
3     print(y)
4 foo(1,2) # 括号内1,2为实参

输出:

1
2

  2.位置参数:
    位置形参:在定义函数时,从左往右的顺序依次定义形参称之为位置形参
        注意:1.但凡是按照位置定义的形参,在调用函数时必须为其传值,多一个少一个都不行
    位置实参:在调用函数时,从左往右的顺序依次传入值称之为位置实参
        注意:1.在传值时按照顺序与形参一一对应

1 def foo(x,y,z): #括号内x,y,z 为形参
2     print(x)
3     print(y)
4 foo(1,2) # 括号内1,2为实参

输出:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-b10bbf76aa4f> in <module>()
2     print(x)
3     print(y)
----> 4 foo(1,2) # 括号内1,2为实参

TypeError: foo() missing 1 required positional argument: 'z'

 3.关键字实参:
    在调用函数时,按照key=value的形式定义的实参,称之为关键字实参
    注意:1.在传值时可以完全打乱顺序,但仍然能指名道姓地指定参数的传值
         2.可以在调用函数时,混用位置实参与关键字实参,但是位置实参必须在关键字实参的左边,并且不能为某个形参重复性传值
       

1 def foo(x,y): #括号内x,y为形参
2     print(x)
3     print(y)
4 foo(y=3,x=2) # 指定关键字实参,可打乱顺序

输出:

2

3

4.默认参数:
        在定义函数时,就已经为某些参数绑定值,称之为默认参数
        注意:
            1.在定义阶段就已经有值,意味在调用阶段可以不用为其传值
            2.默认形参必须放到位置形参的后面
            3.默认形参的值只在定义阶段生效一次
            4.默认形参的值通常应该是不可变类型

1 def foo(x,y,z=3):
2     print(x)
3     print(y)
4     print(z)
5     
6 foo(1,2)

输出:

1

2

3

    归纳对比:默认形参vs位置形参:
        默认形参:大多数情况值不变
        位置形参:大多数情况值都是不一样的

1 m=10
2 def foo(x,y,z=m):
3     print('x:%s' %x)
4     print('y:%s' %y)
5     print('z:%s' %z)
6     
7 m=1111111
8 foo(1,2)

输出:

x:1
y:2
z:10

1 def foo(name,hobby,l=[]):
2     l.append(hobby)
3     print('%s 的爱好是 %s' % (name,l))
4     
5 foo('andy','read')
6 foo('tom','run')
7 foo('alex','sing')

输出:

andy 的爱好是 ['read']
tom 的爱好是 ['read', 'run']
alex 的爱好是 ['read', 'run', 'sing']

 1 def foo(name,hobby,l=None):
 2     if l is None:
 3         l=[]
 4     l.append(hobby)
 5     print('%s 的爱好是 %s' % (name,l))
 6 l1=[]   
 7 foo('andy','read',l1)
 8 foo('andy','music',l1)
 9 foo('andy','run',l1)
10 l2=[]
11 foo('lily','run',l2)
12 l3=[]
13 foo('alex','sing',l3)

输出:

andy 的爱好是 ['read']
andy 的爱好是 ['read', 'music']
andy 的爱好是 ['read', 'music', 'run']
lily 的爱好是 ['run']
alex 的爱好是 ['sing']

 5.可变长度的参数:
        可变长度指的是在调用函数时,函数参数的个数可以不固定
        然而实参终究是要为形参传值的,针对两种形式实参个数不固定,对应形参也必须要有两种解决方案来分别处理溢出位置实参(*)与溢出的关键字实参(**)

1 #*会将溢出的位置实参存成元组,然后赋值给紧跟其后的变量名
2 #1.1 形参中带*
3 def foo(x,y,*z): #*z=(3,4,5,6,7,8), 
4     print(x)
5     print(y)
6     print(z)
7 foo(1,2,3,4,5,6,7,8)

输出:

1
2
(3, 4, 5, 6, 7, 8)
1
2
('h', 'e', 'l', 'l', 'o')
h
e
('l', 'l', 'o')

1 #1.3 实参中带*, 窍门:但凡碰到实参中带*,都先将其打散成位置实参,然后再考虑传值
2 def foo(x,y,z):  
3     print(x,y,z)
4 l=['alex','tom','andy']
5 foo(*l)   #*后被打散

输出:

alex tom andy

 1 #1.1 形参中带**
 2 #**会将溢出的关键字实参存成字典,然后赋值给紧跟其后的变量名
 3 
 4 def foo(x,y,m,n,**z): #**z={'a': 1, 'b': 2, 'c': 3}, 
 5     print(x)
 6     print(y)
 7     print(m)
 8     print(n)
 9     print(z)
10 foo(1,2,n=10,m=20,a=1,b=2,c=3)

输出:

1
2
20
10
{'a': 1, 'b': 2, 'c': 3}

 1 #1.2 形参中带**,实参中带**
 2 #**会将溢出的关键字实参存成字典,然后赋值给紧跟其后的变量名
 3 #窍门:但凡在实参中带**,都将其打散称关键字实参,然后考虑传值
 4 
 5 def foo(x,y,m,n,**z): #**z={'a': 1, 'b': 2, 'c': 3}, 
 6     print(x)
 7     print(y)
 8     print(m)
 9     print(n)
10     print(z)
11 foo(1,2,n=10,m=20,**{'a':1,'b':2,'c':3})

输出:

1
2
20
10
{'a': 1, 'b': 2, 'c': 3}

1 def foo(x,y,m,n,**z): #**z={'a': 1, 'b': 2, 'c': 3}, 
2     print(x)
3     print(y)
4     print(m)
5     print(n)
6     print(z)
7 foo(1,n=10,m=20,**{'a':1,'b':2,'c':3,'y':111})

输出:

1
111
20
10
{'a': 1, 'b': 2, 'c': 3}

1 #1.3 实参中带**
2 def foo(x,y,z): #**z={'a': 1, 'b': 2, 'c': 3}, 
3     print(x)
4     print(y)
5     print(z)
6 foo(**{'x':1,'y':1,'z':2})

输出:

1
1
2

1 #如果需要将外层的函数的参数格式原封不动地转嫁给内部调用的函数,就需要用到下述方法:
2 def index(name,age,sex):
3     print('name:%s age:%s sex:%s' %(name,age,sex))
4 
5 def wrapper(*args,**kwargs): #args=(1,2,3),kwargs={'a': 10, 'b': 20, 'c': 30}
6     print(args,kwargs)
7     index(*args,**kwargs) #index(*(1,2,3),**{'a': 10, 'b': 20, 'c': 30}
8 #我们虽然调用的是wrapper函数,但是我们遵循的其实是index函数的参数规则
9 wrapper(1,2,3,a=10,b=20,c=30)

输出:

(1, 2, 3) {'a': 10, 'b': 20, 'c': 30}
 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-31-02b3f05d6305> in <module>()
      5     print(args,kwargs)
      6     index(*args,**kwargs)
----> 7 wrapper(1,2,3,a=10,b=20,c=30)

<ipython-input-31-02b3f05d6305> in wrapper(*args, **kwargs)
      4 def wrapper(*args,**kwargs): #args=(1,2,3),kwargs={'a': 10, 'b': 20, 'c': 30}
      5     print(args,kwargs)
----> 6     index(*args,**kwargs)
      7 wrapper(1,2,3,a=10,b=20,c=30)

TypeError: index() got an unexpected keyword argument 'a'
1 #如果需要将外层的函数的参数格式原封不动地转嫁给内部调用的函数,就需要用到下述方法:
2 def index(name,age,sex):
3     print('name:%s age:%s sex:%s' %(name,age,sex))
4 
5 def wrapper(*args,**kwargs): #args=(1,2,3),kwargs={'a': 10, 'b': 20, 'c': 30}
6     print(args,kwargs)
7     index(*args,**kwargs) #index(*(1,2,3),**{'a': 10, 'b': 20, 'c': 30}
8 #我们虽然调用的是wrapper函数,但是我们遵循的其实是index函数的参数规则
9 wrapper('andy',sex='male',age=18)

输出:

('andy',) {'sex': 'male', 'age': 18}
name:andy age:18 sex:male

1 #命名关键字参数(了解):但凡在*后**之前定义的参数称之为命名关键字参数
2 #注意点:
3 #在调用函数时,传值的形式必须是key=value
4 def foo(x,y,*,m=1,n):
5     print(x,y,m,n)
6 foo(1,2,n=3,m=4)

输出:

1 2 4 3

1 #函数是第一类对象,意味着函数可以当作变量去使用
2 #3.可以当作函数的返回值
3 def bar():
4     return foo
5 f=bar()
1 #4.可以当作容器类型的元素
2 def f1():
3     print('from f1')
4 def f2():
5     print('from f2')
6 l=[f1,f2]
7 print(l)
8 l[1]()

输出:

[<function f1 at 0x000001ED436887B8>, <function f2 at 0x000001ED43688620>]
from f2

 1 def pay():
 2     print('pay function')
 3 
 4 def withdraw():
 5     print('withdraw function')
 6 
 7 def auth():
 8     print('auth function')
 9 
10 def shopping():
11     print('shopping function')
12 
13 def transfer():
14     print('transfer function')
15     
16 func_dic={
17     '1':pay,
18     '2':withdraw,
19     '3':auth,
20     '4':shopping,
21     '5':transfer
22 }
23 while True:
24     print("""
25     0 退出
26     1 支付
27     2 取款
28     3 认证
29     4 购物
30     5 转账
31     """)
32     choice=input('请输入您要执行的操作:').strip() #choice='123'
33     if choice == '0':break
34     if choice not in func_dic:
35         print('输入错误的指令,请重新输入')
36         continue
37 
38     func_dic[choice]() #
39         
40     
1 #函数的嵌套调用:在调用一个函数时,其内部代码又调用其他函数
2 def bar():
3     print('from bar')
4 def foo():
5     print('from foo')
6     bar()
7 foo()
 1 def mar2(x,y):
 2     if x>y:
 3         return x
 4     else:
 5         return y
 6 def max4(a,b,c,d):
 7     res1=max2(a,b)
 8     res2=max2(res1,c)
 9     res3=max2(res2,d)
10     return res3
11 print (max4(1,2,3,4))

1.名称空间与作用域:namespaces
    名称空间是存放名字与值绑定关系的地方
    要取到值必须通过名字才能找,而名字又在名称空间中放着,所以在取值时需要先到名称空间中找到名字自然就拿到值的内存地址了
2.名称空间分为三种:
    a.内置名称空间:存放python解释器自带的名字,生命周期:在解释器启动时产生,关闭时回收
    b.全局名称空间:除了内置的与局部的之外的名字都属于全局名称空间,生命周期:在程序文件执行时就立刻产生,程序执行完毕后就回收
    c.局部名称空间:存放的时函数内部定义的名字,生命周期:在调用函数时临时生效,在调用函数结束时立刻回收
   
    加载顺序:内置名称空间--》全局名称空间--》局部名称空间
    加载名称空间的目的:为了将名字与值的绑定关系存放起来,而存的目的时为了取,也就是说,当我们在查找名字时,必然时在三者其一中查找
    查找顺序:局部名称空间--》全局名称空间--》内置名称空间
    基于当前所在位置往后查找

1 x=100
2 y=200
3 #强调,函数的形参名属于局部名称空间
4 def foo(x,y):
5     print(x,y)
6 foo(1,2)
1 def f1():
2     x=1
3     def f2():
4         print('from f2')
5 f1()

  4.作用域
       作用域指的是作用范围
       分为:
       全局作用范围:包含内置名称空间与全局名称空间中的名字
              特点:全局有效
       局部作用范围:包含的局部名称空间的名字
              特点:局部有效,临时存活

1 x=11111
2 def foo():
3     y=2
4 print(globals()) #返回的是全局作用域中的名字
5 print(dir(globals)['__builtins__']) #返回的是全局作用域中的内置名称空间的名字
6 print(locals() is globals())
7 foo()
 1 #函数的作用域关系在函数定义阶段就已经固定,与函数的调用位置无关
 2 #即在调用函数时一定要跑到定义函数的位置寻找作用域关系
 3 x=222# 3.找全局函数
 4 def outter():
 5     x=111 #2.找外部函数
 6     def inner():
 7         x=0 #1.先找函数体内部
 8         print('from inner %s' %x)
 9     return inner
10 x=333 #4.找全局函数
11 f=outter() #f=指向outter.locals.inner
12 print(f)
13 def foo():
14     x=444
15     f()
16 foo()#返回000,只看定义函数的位置找
1 #global 名字声明时来自于全局的
2 x=1
3 def func():
4     global x
5     x=2
6 func()
7 print(x)
 1 #nonlocal 声明变量时来自于当前外层的,必须是在函数内
 2 x=222
 3 def f1():
 4     def f2():
 5         nonlocal x
 6         x=0
 7     f2()
 8     print('f1-->',x)
 9 f1()
10 print(x
 
 

函数的作用域关系是在函数定义阶段就已经固定死的
#闭包函数
1.闭指的是-》定义在函数的内部函数
2.包指的是-》该内部函数包含对其外层作用域名字的引用
闭包函数通常需要结合函数对象的概念,将闭包函数返回到外部使用

 1 def outter():
 2     x=100
 3     def inner():
 4         print(x)
 5     return inner()
 6 f=outter()
 7 #x=200
 8 #f()
 9 
10 def foo():
11     x=300
12     def bar():
13         x=400
14     bar()
15 foo()
1 x=1
2 y=1
3 def outer(x,y):
4     def sum():
5         res=x+y
6         return res
7     return sum
8 f=outer(1,2)
9 f()
 1 #闭包函数范例
 2 import requests
 3 def outter(url):
 4     def get():
 5         response=requests.get(url)
 6         print(len(response.text))
 7     return get
 8 jd=outter('https://www.jd.com')
 9 jd()
10 baidu=outter('https://www.baidu.com')
11 baidu()

装饰器:
    装饰指的是为被装饰对象添加新的功能
    器指的是工具
    装饰器本身可以任意可调用的对象,被装饰的对象也可以是任意可调用的对象
    目标:
        写一个函数用来为另一个函数添加新功能,需要遵循开放封闭原则(对修改时封闭的,对扩展时封闭的)
        1.不修改被装饰对象的源代码
        2.不修改被装饰对象的调用方式

 1 import time
 2 def index():
 3     time.sleep(3)
 4     print('welcome to index page')
 5 def home(name):
 6     time.sleep(3)
 7     print('welcome to index page %s' %name)
 8 def outer(func): #func=最原始的index
 9     def timer(*args,**kwargs):
10         start=time.time()
11         func(*args,**kwargs) #最原始的index()
12         stop=time.time()
13         print('run time is %s' %(stop-start))
14     return timer
15 #f=outer(index)
16 #f()
17 index=outer(index)
18 home=outer(home)
19 index()
20 home('alex')
 1 #装饰器的语法糖:在被装饰对象正上方单独一行写上@装饰器名字
 2 import time
 3 def index():
 4     time.sleep(3)
 5     print('welcome to index page')
 6 def home(name):
 7     time.sleep(3)
 8     print('welcome to index page %s' %name)
 9 def timer(func): #func=最原始的index
10     def wrapper(*args,**kwargs):
11         start=time.time()
12         res=func(*args,**kwargs) #最原始的index()
13         stop=time.time()
14         print('run time is %s' %(stop-start))
15         return res
16     return wrapper
17 #f=outer(index)
18 #f()
19 res=index()
20 print(res)