week3 day2函数高级用法

一. 函数对象

函数和变量有相通之处。变量的使用原则是先定义后引用,函数的使用原则是先定义后使用。变量名绑定了变量值对应的内存空间的内存地址。函数名绑定了函数体代码对应的内存空间的内存地址。因此,适用于变量名使用的场景也适用于函数名的使用场景。

  1. 赋值
    变量
    a=10
    b=a
    
    函数
    def func():
    	print(1)
    f=func
    print(f)----->func的内存地址
    f()----->1
    
  2. 传参
    变量
    a=10
    def func(x):
    	...
    func(a)
    
    函数
    def index():
    	...
    def func(m):
    	...
    func(index)
    
  3. 返回值
    变量
    a=10
    def func():
    	return a
    
    函数
    def index():
    	...
    def func():
    	return index
    res=func()
    res()
    
  4. 在容器型数据结构中按照索引(key)取到
    变量
    a=10
    list1=[a]
    list1[0]
    
    函数
    def func():
    	...
    list1=[func]
    list1[0]()
    

第四种函数对象的使用方法可以让我们之前写过的atm功能更加简洁:

def login():
	...
def register():
	...
def withdraw():
	...
def deposit():
	...
def transfer():
	...
func_info={
	'0':['退出',exit],
	'1':['登录',login],
	'2':['提现',register],
	'3':['存钱',withdraw],
	'4':['转帐',transfer]
}
while True:
	for key,value in func_info:
		print(key,value[0])
	choice=input('请输入您想执行的操作: ').strip()
	if choice in func_info():
		print('您正在执行 {} 操作'.format(func_info[choice][0]))
		func_info[choice][-1]()

二. 函数嵌套

2.1 函数的嵌套定义

def func():
	x=10
	def sub_func():
		print(1)
func()----->None

def func():
	x=10
	def sub_func():
		print(1)
	sub_func()
func()----->1

2.2 函数的嵌套调用

比较输入的多个值的最大值的函数

def max2(x,y):
	if x>y:
		return x
	else:
		return y
def max_many(*args):
	list1=list(args)
	res1=list1[0]
	if len(list1)>=2:
		for i in range(1,len(list1)):
			res1=max2(res1,list1[i])
	print(res1)

三. 名称空间与作用域

3.1 名称空间

名称空间namespaces。名称空间即存放名字与对象映射/绑定关系的地方。对于x=3,Python会申请内存空间存放对象3,然后将名字x与3的绑定关系存放于名称空间中,del x表示清除该绑定关系。在程序执行期间最多会存在三种名称空间,分别为:内置名称空间,全局名称空间,局部名称空间。

名称空间与作用域在定义阶段就确定了,而不是调用阶段

名称空间与作用域在定义阶段就确定了,而不是调用阶段

名称空间与作用域在定义阶段就确定了,而不是调用阶段

名称空间种类定义生命周期
内置名称空间存放内置的名字python解释器调用产生—>python解释器关闭销毁
全局名称空间存放的是顶级的名字python文件运行产生—>python文件停止运行销毁
局部名称空间存放的是函数内的名字调用函数时产生,调用结束销毁

三种名称空间没有任何相交关系,但为了方便理解,可以理解为三层结构。内置名称空间在最外层,内层包含若干个(和python文件个数相同)全局名称空间,全局名称空间内层包含若干个(和python文件中调用函数的个数相同)函数的局部名称空间。

名称空间的加载顺序是:内置名称空间->全局名称空间->局部名称空间,而查找一个名字,必须从三个名称空间之一找到,查找顺序为:局部名称空间->全局名称空间->内置名称空间

LEGB层次。local----->enclosing functions----->global----->built-in functions.

案例一:

def f1():
    x=555
    def f2():
        x=666
        print(x)
    f2()
x=444
f1() # ----->666 从f2的局部名称空间找到的

def f1():
    x=555
    def f2():
        print(x)
    f2()
x=444
f1() # ----->555 从f1的局部名称空间中找到的

def f1():
    def f2():
        print(x)
    f2()
x=444
f1() # ----->444 从全局名称空间中找到的

案例二:

len=10
def func():
    len=2
    print(len)
func() # ----->2 从局部名称空间中找到的

len=10
def func():
    print(len)
func() # ----->10 从全局名称空间中找到的

def func():
    print(len)
func() # ----->len 从内置名称空间中找到的内置函数

案例三:名称空间与作用域只与定义阶段有关,与调用无关

x=111
def f1():
    print(x)
def f2():
    x=222
    f1()
f2() # ----->111 f1在全局名称空间中,没在f2的局部名称空间中

案例四:

x=111
def f1():
    print(x) # local variable 'x' referenced before assignment
    x=222
f1()

print(y) # name 'y' is not defined

案例五:

x=111
def func():
	print(x)
	x=222
func() # ---> local variable 'x' referenced before assignment
确实在局部名称空间中找到了变量x,但是变量x只有运行到print后的那行代码才能确定x的值,因此在打印变量x的时候,局部名称空间中的x还未定义

3.2 作用域

  1. 全局作用域:内置名称空间+全局名称空间
    特点:全局存活,全局有效
    x=10
    def func():
    	global x
    	# 在函数内,无论嵌套多少层,都可以查看到全局作用域的名字,若要在函数内修改全局名称空间中名字的值,当值为不可变类型时,则需要用到global关键字
    	# 此时修改实参的值为不可变类型,当修改可变类型数据的值时,函数体内对该值的修改将直接反应到原值,无需声明global
    	x=20
    print('before func:',x)----->10
    func()
    print('after func:',x)----->20
    
  2. 局部作用域:局部名称空间
    特点:局部存活,局部有效
    x=10
    def index():
    	x=20
    	def func():
    		nonlocal x
    		# 对于嵌套多层函数,使用nonlocal关键字可以将名子声明为来自外部嵌套函数定义的作用域(非全局)
    		x=10
    	print('before func:',x)
    	func()
    	print('after func:',x)
    index()----->20
    		----->10
    

3.3 作用域与名字查找的优先级

在局部作用域查找名字时,起始位置是局部作用域,所以先查找局部作用域,没有找到,再去全局作用域查找:先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常。

x=100 #全局作用域的名字x
def foo():
    x=300 #局部作用域的名字x
    print(x) #在局部找x
foo()#结果为300

在全局作用域查找名字时,起始位置便是全局作用域,所以先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常。

x=100
def foo():
    x=300 #在函数调用时产生局部作用域的名字x
foo()
print(x) #在全局找x,结果为100

四. 函数对象

函数对象指的是函数可以被当作“数据”来处理,具体可以分为四个方面的使用。

4.1 函数可以被引用

deff add(x,y):
	return x+y
func=add
func(1,2) # ---> 3

4.2 函数可以作为容器类型的元素

dic={'add':add,'max':max}
print(dic) # ---> {'add': <function add at 0x100661e18>, 'max': <built-in function max>}
dic['add'](1,2) # ---> 3

4.3 函数可以作为参数传入另外一个函数

def foo(x,y,func):
	return func(x,y)
foo(1,2,add) # ---> 3

4.4 函数的返回值可以是一个函数

def bar():
	return add
func=bar()
func(1,2) # --->3

五. 闭包函数

5.1 闭包概念

基于函数对象的概念,可以将函数返回到任何位置去调用,但作用域的关系是定义完函数时就已经被确定了的,与函数的调用位置无关。

闭:定义在函数内的函数

包:该函数引用了一个外层函数作用域的名字

闭包=函数的名称空间与作用域+函数对象+函数的嵌套定义

def outter():
	x=10
	def wrapper():
		print(x)
	return wrapper

可以通过函数的closure属性,查看到闭包函数所包裹的外部变量。闭包函数其实就是另一种给函数传参的方式

5.2 为函数体传参

为函数传参的两种方式

  1. 利用实参为形参传值
    def wrapper(x,y):
    	print(x+y)
    wrapper(1,2)----->3
    
  2. 闭包函数传值
    def outter(x,y):
    	def wrapper():
    		print(x+y)
    	return wrapper
    f=outter(3,4)
    f() # --->7
    

六. 装饰器

6.1 什么是装饰器?

装饰器就是为被装饰对象添加新功能的工具。

6.2 为何要用装饰器?

软件的设计应该遵循开放封闭原则,即对外扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。

软件包含的所有功能的源代码以及调用方式,都应该可以避免,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的产品,

开放封闭原则
开放:对扩展新功能开放
封闭:对修改原代码封闭

原则:
1.不修改原函数的原代码
2.不改变原函数的调用方法

6.3 怎么使用装饰器?

无参装饰器模板

def outter(func):
	def wrapper(*args,**kwargs):
		# 为其增加的新功能
		res=func(*args,**kwargs)
		# 为其增加的新功能
		return res
	return wrapper

有参装饰器模板

def chuancan(args=xyz): # 直接传参
	def outter(func):
		def wrapper(*args,**kwargs):
			# 为其增加的新功能
			res=func(*args,**kwargs)
			# 为其增加的新功能
			return res
		return wrapper
	return outter
了解:将原函数的所有属性都转移给新函数,即保留原函数的所有属性。functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用法如下
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper

6.4 语法糖

为了是使用装饰器的时候调用更加简便,引入了语法糖的概念。

import time
def outter(func):
	def wrapper(*args,**kwargs):
		start=time.time()
		res=func(*args,**kwargs)
		end=time.time()
		print(end-start)
		return res
	return wrapper

@outter # index=outter(index)
def index():
	time.sleep(1)
	print(123)
index()----->123  1.0028467

6.5 叠加多个装饰器的运算规则

def deco1(func1): # func1=wrapper2
	def wrapper1(*args,**kwargs):
		print(111)
		res1=func1(*args,**kwargs)
		print('end1')
		return res1
	return wrapper1
def deco2(func2): # func2=wrapper3
	def wrapper2(*agrs,**kwargs):
		print(222)
		res2=func2(*args,**kwargs)
		print('end2')
		return res2
	return wrapper2
def deco3(func3): # func3=index
	def wrapper3(*args,**kwargs):
		print(333)
		res3=func3(*args,**kwargs)
		print('end3')
		return res3
	return wrapper3

@deco1 # deco1(wrapper2)--->return --->wrapper1
@deco2 # deco2(wrapper3)--->return--->wrapper2
@deco3 # deco3(index)--->return--->wrapper3
def index():
	print('from index')

index()----->111
			 222
			 333
			 from index
			 end3
			 end2
			 end1

装饰器只需要看最内层的函数体代码就可以了,多个装饰器嵌套执行顺序从上到下
装饰器只需要看最内层的函数体代码就可以了,多个装饰器嵌套执行顺序从上到下
装饰器只需要看最内层的函数体代码就可以了,多个装饰器嵌套执行顺序从上到下

上面例子的具体原理图可以看下图:
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值