函数对象与闭包(笔记整理)

发布时间 2023-06-25 11:19:50作者: 王献运

一、函数对象

1.什么是函数对象

  • 函数对象是指:将函数作为变量保存在内存中的一种对象。就是把函数当成变量去使用,就是在函数调用阶段,将调用的函数赋一个变量名
def inner():
    print('函数名也是不加括号,其实就是一个地址')

# print(inner)  # <function inner at 0x7f80180d9ea0> 【 function(函数)  inner (内部)  at (在) 0x7f80180d9ea0 】
# print(inner())
"""
执行结果
函数名也是不加括号,其实就是一个地址 =加括号成功执行函数体
None = 由于此函数没有返回值所以执行print 会附加一个 None
"""

2.如何使用函数对象

  • 使用函数对象有四种方法:
    • 把函数当成边变量去使用,可以给函数名赋值
    • 函数名可以作为参数传入另外一个函数
    • 函数的返回值可以是一个函数
    • 函数可以作为容器类型里面的其中一个元素,就是把函数名当成一个元素使用
"""函数对象(把函数当成变量去使用)"""
1.函数可以被引用,即函数可以赋值给一个变量,函数名可以赋变量名,可以随意起名字
    def func(x, y):
        return x + y
    ass = func(1, 2)  # 如果有return返回值,就不能直接执行函数名加括号,因为需要有一个变量来接收你这个return给的返回值
    print(ass)  # run:3    所以这边就需要打印这个函数的变量,才可以执行所以这个ass就是被引用的函数,也是函数的对象
 
 
2.函数可以作为容器类型的元素,就是把函数名当成一个元素使用
    可以存放的数据类型大概有list,dict,set,int,float,str可以作存放到容器对象中
    def foo():
        print('from foo')
        dic = {'func': foo}  # 函数名foo 可以在函数体内和元素放在一起并且可以成功打印不冲突,而且函数名没有加括号就不会执行
    foo()  # run:from foo
     
 
3.函数可以作为参数传入另外一个函数
    就是说,我把外面的函数名当作一个参数给这个参数一个名字放在我自己的函数体内加上括号就可以运行,但是需要给我自己的函数名括号内写上这个名字
    def func():
        print('from func')
    def foo(x):#2、 x = func的内存地址
        x() #3、这个时候这个x就可以家括号运行了,实际上就等于func(),
    foo(func)#1、 func = 一个内存地址 ,本质就是foo放了一个func的内存地址然后给了x
     
 
4.函数的返回值可以是一个函数
    def func():
        print("from func")
     funk1 的 return 返回的函数加括号时, func 没有 return 的时候默认func1返回的是 none
    def func1(x):
        return x
     
    res = func1(func)#在调用阶段吧函数名当成一个返回值
    print(res)  #run:<function func at 0x7fed600d8ea0>    func的内存地址
    res()#这个时候就可以调用func
如果 函数没有
     

3.函数对象的属性(先了解基础阶段不容易懂)

函数也是对象的一种,其实现方式和普通对象不同,是由 function 类来实现的

  1. name表示函数的名称,在定义时默认为函数名,用于标识函数对象
  2. doc表示函数的文档字符串,可以使用 help() 函数来查看,用于描述函数的功能和使用方法
  3. code表示函数的字节码对象,用于操作函数的字节码指令。code 属性是函数对象的核心属性之一,它返回的是一个 code 对象,表示函数的字节码对象
  4. defaults表示函数参数的默认值,是一个元组类型。
  5. kwdefaults表示函数关键字参数的默认值,是一个字典类型。
  6. globals表示函数全局命名空间,即函数所在模块的命名空间。
def greet(name):
    """
    定义一个简单的函数用于问候用户
    """
    print("Hello, " + name + "!")

# 内置属性
print(greet.__name__)          # 输出函数名 greet
print(greet.__doc__)           # 输出函数文档字符串
print(greet.__module__)        # 输出函数所在模块名 __main__
print(greet.__defaults__)      # 输出默认参数值 (None,)

# 自定义属性
greet.counter = 0              # 定义计数器属性并初始化为 0
greet.words = ["Hello", "Hi"]  # 定义问候语列表

# 测试自定义属性
greet.counter += 1             # 每次调用计数器加 1
print("Counter:", greet.counter)
print("Words:", greet.words)

4.函数对象的方法(先了解基础阶段不容易懂)

  • **call(self, *args, kwargs):表示调用函数对象,即执行函数体代码并返回结果。
  • getattribute(self, name):返回指定属性名的属性值。
  • setattr(self, name, value):设置指定属性名的属性值。
  • delattr(self, name):删除指定属性名的属性值。

二、闭包函数

# 给外部的用户暴露出来一个接口,告诉他都有什么功能
def all_func(type):
    def login():
        print('login')

    def transfer():
        print('login')

    def withdraw():
        print('login')

    def shopping():
        print('login')

    def shopping1():
        print('login')

    if type == 1:
        login()
    elif type == 2:
        transfer()
    elif type == 3:
        withdraw()
    elif type == 4:
        shopping()


all_func(1)
all_func(2)
all_func(3)

1.什么是闭包函数

  • 满足两个条件:闭:函数内部定义函数,至少是两层函数。包:内部的函数使用外部的函数的变量或者参数并返回了这个内部函数
  • 使用场景:它是第二种传参的方式,一次传参,多次调用,如果函数被当作数据做处理,始终已自身的作用域为准
  • 闭包函数的定义:当一个函数内部定义了另一个函数,并且内部函数引用了外部函数的局部变量或参数时,就形成了一个闭包。
  • 具体来说,闭包函数的特点如下:
    • 内部函数可以访问外部函数的局部变量或参数。
    • 外部函数返回内部函数对象。
    • 内部函数继承了外部函数的参数和局部变量,即使外部函数已经执行完毕,这些变量仍然会保存在内存中,供内部函数使用。
    • 闭包函数是基于函数对象去使用的,就是将函数返回到任意位置去使用,但是作用域的关系在函数定义阶段就确定了,与调用位置无关
  • 闭包的意义:返回的函数对象,不仅是函数对象,同时该函数对象还带了一层作用域,则该函数无论在何处调用,优先使用自己外层包裹的作用域。
  • 闭包函数的与函数的嵌套:
    • 函数嵌套:指的是一个函数在函数内部定义了一个函数,可以访问外部函数的变量,嵌套多个函数会逐层访问,但是不能改值
def inner():
    print('外部函数')
    def upper():
        print('内部函数')
    upper()
inner()
  • 闭包函数:指的是一个函数在函数内部定义了一个函数,可以访问外部函数的变量和参数,并且可以通过外部函数返回内部函数的方式改值
def upper():
    print('外部函数')
    def inner():
        print('内部函数')
    return inner
res = upper()
res()

2.如何使用闭包函数

*********************************************************************************************************
案例一
def outer_func(x):           # 外部函数 outer_func 接受一个参数 x
    def inner_func(y):       # 内部函数 inner_func 接受一个参数 y
        return x + y        # 内部函数引用外部函数的变量 x 并返回 x+y 的结果
    return inner_func       # 外部函数返回内部函数对象
    
1. outer_func 是一个外部函数,它接受一个参数 x,并返回一个内部函数 return inner_func。
2. 内部函数 inner_func 接受一个参数 y,它可以访问外部函数的参数 x。
3. 外部函数将内部函数作为返回值返回,这就形成了一个闭包

new_func = outer_func(10)  # 创建一个新的闭包函数对象 new_func,传入参数 10
result = new_func(2)      # 调用闭包函数对象 new_func,并传入参数 2
print(result)             # 输出 12


1. 调用外部函数 outer_func 并传入参数 10,这个函数返回了内部函数 inner_func 的引用。
2. 将内部函数引用赋值给变量 new_func,从而得到一个闭包函数对象。
3. 对闭包函数对象 new_func 使用圆括号并传入参数 2,就会触发它的执行并返回结果 12。

"""需要注意的是,在闭包中,内部函数可以访问外部函数的参数和局部变量,但不能修改它们。如果要修改外部函数的变量,必须通过外部函数来实现。"""

*********************************************************************************************************

案例二
1.同为全局作用域的函数中的变量引用
x = 1                 # 定义全局变量 x,赋值为 1
def f1():
    def f2():
        print(x)    # 在内部函数中引用外部函数的变量 x
    return f2        # 外部函数返回内部函数 f2
def f3():
    x = 3            # 定义局部变量 x,赋值为 3
    f2 = f1()        # 调用外部函数 f1() 并将返回的内部函数,对象赋值为 f2
    f2()             # 执行 f2 函数,输出引用的外部变量 x 的值
f3()                 # 调用函数 f3,执行闭包函数,并输出 1
"""闭包函数并不是按照调用位置来确定外部变量的值的,而是按照函数定义时的作用关系去执行"""

*********************************************************************************************************
 
 2.只有局部作用域的就不会引用到全局的变量了,只会引用局部作用域的变量
x = 1
def outer():
    x = 2
    def inner():
        print(x)
    return inner
func = outer()
func()  # 结果为2
 """
 闭包的特性:__closure__
1.  __closure__是内部函数的一个属性,用来保存环境变量,用type()函数看一下,__closure__是一个tuple, 
2.  还以上边的代码为例,我们看一下环境变量都包含什么,什么样的变量可以记录到环境变量中得以保存:
第一步:   定义闭包之前,__closure__这个属性没有值
第二步:最关键的一步: 我们发现,在闭包声明结束的时候,内部函数的还存在的变量就已经确定下来了,确定下来的变量无法修改,不需要等到执行内部函数才确定环境变量
 """

print(func.__closure__)#返回的是outer的内存地址
print(func.__closure__[0].cell_contents)#返回两个2
print(outer().__closure__)#与func返回的内存地址一样
 
*********************************************************************************************************
3.闭包的用途
目前有两种为函数体传值的方式,一种是直接将值以参数的形式传入,另外一种就是将值包给函数
import requests
方式一:
def get(url):#直接将值以参数的形式传入
    return requests.get(url).text
方式二:
def page(url):#将值包给函数
    def get():
        return requests.get(url).text
 
    return get
提示:requests模块是用来模拟浏览器向网站发送请求并将页面内容下载到本地,需要事先安装:pip3 install requests
 
对比两种方式,方式一在下载同一页面时需要重复传入url,而方式二只需要传一次值,就会得到一个包含指定url的闭包函数,以后调用该闭包函数无需再传url
方式一下载同一页面
get('https://www.python.org')
get('https://www.python.org')
get('https://www.python.org')
……
 
方式二下载同一页面
python=page('https://www.python.org')
python()
python()
python()
……
这个时候闭包函数的作用就比这种多次传输要好太多,闭包函数的这种特性有时又称为惰性计算。使用将值包给函数的方式