名称空间、作用域、闭包

名称空间namespace

存放名字的地方,是对栈区的划分,有了名称空间之后,就可以在栈区中存放相同的名字,详细的,名称空间分为三种

内置名称空间

存放的名字:存放的是python解释器内置的名字

举例说明

In [2]: input
Out[2]: <function input(prompt=None, /)>

In [3]: print
Out[3]: <function print>

存活周期: python解释器启动则产生,python解释器关闭则销毁(python程序结束后)

全局名称空间

存放的名字: 只要不是函数内定义,也不是内置的,剩下的都是全局名称空间的名字。

存活周期: python文件执行则产生,python文件运行完毕后销毁

下面的模块名,非函数内部的变量名都是全局名称空间

import os

x = 10
if 13 > 3:
    y = 20
    if 3 == 3:
        z = 30


# func=函数的内部地址
def func():
    a = 111
    b = 222


class Foo:
    pass

局部名称空间

存放的名字: 在调用函数时,运行函数体代码过程中产生的函数内的名字。

存活周期: 在调用时存活,函数调用完毕则销毁,同一个函数调用多变,调用几次,产生几次名称空间。

def func(a, b):
    pass


# 产生4个名称空间
func(10, 1)
func(11, 12)
func(13, 14)
func(15, 16)

案例

COUNTRY = "中国"
CITY_LIST = ["北京", "上海", "深圳"]


def download():
    url = "http://www.xxx.com"
    CITY_LIST = ["河北", "河南", "山西"]
    print(url)
    print(COUNTRY)
    print(CITY_LIST)


def upload():
    file_name = "rose.zip"
    print(COUNTRY)
    print(CITY_LIST)


print(COUNTRY)
print(CITY_LIST)
download()
upload()

COUNTRY = "中华人民共和共国"
CITY_LIST = [11, 22, 33]

download()
upload()
中国
['北京', '上海', '深圳']
http://www.xxx.com
中国
['河北', '河南', '山西']
中国
['北京', '上海', '深圳']
http://www.xxx.com
中华人民共和共国
['河北', '河南', '山西']
中华人民共和共国
[11, 22, 33]

名称空间的加载顺序

1内置名称空间 > 2全局名称空间 > 3局部名称空间

名称空间销毁顺序

1局部名称空间 > 2全局名称空间 > 3内置名称空间

名字查找的优先级

当前所在的位置(三种名称空间)向上一层一层查找

内置名称空间

全局名称空间

局部名称空间

如果当前在局部名称空间: 局部名称空间 -> 全局名称空间 -> 内置名称空间

input = 333


def func():
    input = 444
    print(input)


func()

>>> 444

如果当前在全局名称空间:全局名称空间 -> 内置名称空间

input = 333


def func():
    # input = 444
    print(input)


func()

>>> 333
# input = 333
def func():
    # input = 444
    print(input)


func()

>>> <built-in function input>
input = 333


def func():
    input = 444


func()
print(input)

>>> 333

挖个坑,注意

def func():
	print(x)
	
	
x = 111
func()

>>> 111
def func():
	print(x)
	
	
func()
x = 111
# 结果报错

名称空间的“嵌套”关系

是以函数定义阶段为准,与调用位置无关

x = 1


def func():
    print(x)


def foo():
    x = 222
    func()


foo()

>>> 1

函数嵌套定义

image-20231208140448971

input = 111
def f1():
    input = 222
    def f2():
        input = 333
        print(input)

    f2()

f1()

>>> 333
input = 111
def f1():
    input = 222
    def f2():
        # input = 333
        print(input)

    f2()

f1()

>>> 222
input = 111
def f1():
    # input = 222
    def f2():
        # input = 333
        print(input)

    f2()

f1()

>>> 111
# input = 111
def f1():
    # input = 222
    def f2():
        # input = 333
        print(input)

    f2()

f1()

>>> <built-in function input>

示例 结果会报错

x = 111
def func():
    print(x)
    x = 222

func()

# 结果报错

作用域 --> 作用范围

全局作用域:内置名称空间、全局名称空间

  • 全局存活

  • 全局有效:被所有函数共享

x = 111
def foo():
	print(x, id(x))
	
def bar():
	print(x, id(x))
	
foo()
bar()
print(x, id(x))

# 结果如下
111 2671133742896
111 2671133742896
111 2671133742896

局部作用域:局部名称空间的名字

  • 临时存活

  • 局部有效:函数内有效

LEGB (了解)

LEGB含义解释:

L-Local(function);函数内的名字空间
E-Enclosing function locals;外部嵌套函数的名字空间(例如closure)
G-Global(module);函数定义所在模块(文件)的名字空间
B-Builtin(Python);Python内置模块的名字空间

# 最外层是Builtin
# global
def f1():
    # Enclosing
    def f2():
        # Enclosing
        def f3():
            # local
            pass

global和nonlocal

global

如果在局部想要修改全局的名字对应的值(不可变类型),需要用global

x = 111
def func():
    x = 222

func()
print(x)

>>> 111
x = 111
def func():
	# 声明x是全局的名字,不需要再造新值了
    global x
    x = 222

func()
print(x)

>>> 222

可变类型,通常情况下不需要用global

l = [111, 222]
def func():
    l.append(333)

func()
print(l)

>>> [111, 222, 333]

案例

COUNTRY = "中国"
CITY_LIST = ["北京", "上海", "深圳"]


def download():
    url = "http://www.xxx.com"

    global CITY_LIST
    CITY_LIST = ["河北", "河南", "山西"]
    print(CITY_LIST)

    global COUNTRY
    COUNTRY = "中华人民共和国"
    print(COUNTRY)


def upload():
    file_name = "rose.zip"
    print(COUNTRY)
    print(CITY_LIST)


download()
upload()
['河北', '河南', '山西']
中华人民共和国
中华人民共和国
['河北', '河南', '山西']

nonlocal(了解)

修改函数外层函数包含的名字对应的值(不可变类型)

从当前函数的外一层开始找

需求:修改f1内的x

x = 0
def f1():
    x = 111
    def f2():
        global x
        x = 222
    f2()
    print('f1内的x:', x)

f1()
print(x)

# 结果如下
f1内的x: 111
222
x = 0
def f1():
    x = 111
    def f2():
        nonlocal x
        x = 222
    f2()
    print('f1内的x:', x)

f1()
print(x)

# 结果如下
f1内的x: 222
0

针对可变类型,一样不需要nonlocal

def f1():
    x = []
    def f2():
        x.append(111)
    f2()
    print('f1内的x:', x)

f1()

>>> f1内的x: [111]

嵌套注意

名称空间的嵌套关系决定了名字的查找顺序 而名称空间的嵌套关系是以函数定义阶段为准的, 即函数的嵌套关系与名字的查找顺序是在定义阶段就已经确定好的。

截止到函数运行之前

input = 111


def f1():
    def f2():
        print(input)

    input = 222

	f2()
input = 333

f1()

>>> 222

image-20231208140528964

案例2

name = "小满"


def outer():
    # 局部作用域
    age = 3
    print(f"name is {name} age is {age}")

    # inner函数 : 既然是函数, 就是一串内存地址
    def inner():
        # 嵌套作用域
        sex = "女"
        print(f"name is {name} age is {age} sex is {sex}")

    print(f"内部inner的内存地址 >>> {id(inner)}")  # 打印inner 的内存地址
    return inner  # 返回的其实就是内层函数 inner 的内存地址


inner = outer()
print(f"外部inner的内存地址 >>> {id(inner)}")
inner()
name is 小满 age is 3
内部inner的内存地址 >>> 2187947076640
外部inner的内存地址 >>> 2187947076640
name is 小满 age is 3 sex is 女

闭包函数

大前提

闭包函数 = 名称空间与作用域 + 函数嵌套 + 函数对象
核心点:名字的查找关系是以函数定义阶段位为准

什么是闭包

“闭”函数指的是该函数是内嵌函数
“包”函数指的是该函数包含对外层函数作用域的引用(不是对全局作用域),就是不从自己这里找,从上一层函数找.

一言以蔽之:把你用一个麻袋装起来,你肚子饿了,只能从麻袋里面去找吃的.

实例:闭包函数之名称空间与作用域+函数嵌套

def f1():
    x = 33333333

    def f2():   
        print(x)

    f2()


x = 11111


def bar():
    x = 444444
    f1()


def foo():
    x = 2222222
    bar()


foo()

>>> 33333333

实例:闭包函数之函数对象

def f1():
    x = 33333333

    def f2():
        print('函数f2:', x)

    return f2


f = f1()
print(f)
print(f.__name__)


def foo():
    x = 66666
    f()
    print(f.__name__)


foo()


# 结果如下
<function f1.<locals>.f2 at 0x0000027C5B1CECB0>
f2
函数f2: 33333333
f2

让我们逐步解释这段代码:

def f1():
    x = 33333333
    def f2():
        print('函数f2:', x)
    return f2

f = f1()
print(f)

在这段代码中,我们定义了一个函数f1(),并在其中定义了另一个函数f2()。在f1()函数内部,我们定义了变量x并赋值为33333333。然后,我们定义了嵌套函数f2(),它打印出变量x的值。

接下来,我们调用f1()函数并将返回的函数对象赋值给变量f。此时,f引用了函数f2()

然后,我们打印变量f的值。这里打印的是函数f2的内存地址,而不是函数f2的内部变量x的值。

接下来,我们定义了另一个函数foo()

def foo():
    x = 66666
    f()

foo()函数中,我们定义了一个局部变量x并赋值为66666。然后,我们调用了之前保存在变量f中的函数对象。

最后,我们调用foo()函数:

foo()

这将触发函数f2()的调用,并打印出函数f2: 33333333。这里打印的是函数f2内部变量x的值。

所以,先打印出的是函数f2的内存地址,然后再打印了f2的内部变量x的值。对于打印函数对象本身,可以通过打印f.__name__来获取函数的名称。

为何要有闭包函数

闭包函数的应用

两种函数的体传参方式

直接把函数体需要的参数定义成形参

def f2(x):
	print(x)

f2(1)
f2(2)
f2(3)
def f1():
	x = 3
	def f2():
		print(x)
	return f2

x = f1()
print(x)
x()

# 结果如下
<function f1.<locals>.f2 at 0x0000028297DAD790>
3

传参的方案1

import requests

def get(url):
    response = requests.get(url)
    print(len(response.text))

get('http://www.baidu.com')
get('http://www.cnbolgs.com/ccsvip')

# 结果如下
2381
481

传参的方式2

可以定义一个变量名,如果要重复获取更加直观

import requests

def outter(url):
    def get():
        response = requests.get(url)
        print(len(response.text))
    return get

baidu = outter('http://www.baidu.com')
baidu()
baidu()
baidu()

cnblogs = outter('http://www.cnbolgs.com/ccsvip')
cnblogs()


# 结果如下
2381
2381
2381
481
posted @ 2023-12-08 14:08  小满三岁啦  阅读(4)  评论(0编辑  收藏  举报