函数二

函数二

默认参数的陷阱

def func(li=[]):
    li.append(1)
    print(li)

func()
func([])
func()
func()

上述代码运行结果

[1]
[1]
[1, 1]
[1, 1, 1]

我们可以发现这是什么鬼!!!

如果我们的默认参数是一个可变数据类型 (列表、字典),我们要知道如果我们没有传递数据,即我们使用的是默认的列表,那么当我们多次调用时可以发现,这个列表是公用的

命名空间

什么是命名空间

命名空间,是python解释器在内存中开辟的一块 用于 存储 变量名和内存地址的对应关系的空间。

内置命名空间
概念

python解释器启动时创建的命名空间,用于存储python解释器内置的方法,例如:print input……

内置的名字在启动解释器的时候被加载进内存里

可操作空间范围

不能使用局部和全局的名字的

全局命名空间
概念

当我们执行写的代码时,开辟的命名空间,用于存储我们自己创建的变量和方法名

全局的名字在执行代码的时候被加载到内存里

可操作空间范围

可以使用内置命名空间中的名字,但是不能用局部中使用

局部命名空间
概念

当我们自己的函数被调用的时候,开辟的命名空间,用于存储函数内部的变量和方法名

当函数调用的时候才会被加载到内存里

可操作空间范围

可以使用全局、内置命名空间中的名字

命名空间的查询顺序

当一行代码要使用变量 x 的值时,Python 会到所有可用的名字空间去查找变量,按照如下顺序:

  1. 局部命名空间:特指当前函数或类的方法。如果函数定义了一个局部变量 x,或一个参数 x,Python 将使用它,然后停止搜索。
  2. 全局命名空间:特指当前的模块。如果模块定义了一个名为 x 的变量,函数或类,Python 将使用它然后停止搜索。
  3. 内置命名空间:对每个模块都是全局的。作为最后的尝试,Python 将假设 x 是内置函数或变量。
  4. 如果 Python 在这些名字空间找不到 x,它将放弃查找并引发一个 NameError 异常,如,NameError: name ‘aa’ is not defined。
嵌套函数的情况:
  1. 先在当前 (嵌套的或 lambda) 函数的命名空间中搜索
  2. 然后是在父函数的命名空间中搜索
  3. 接着是模块命名空间中搜索
  4. 最后在内置命名空间中搜索

命名空间的生命周期

不同的命名空间在不同的时刻创建,有不同的生存期。

  1. 内置命名空间在 Python 解释器启动时创建,会一直保留,不被删除。
  2. 模块的全局命名空间在模块定义被读入时创建,通常模块命名空间也会一直保存到解释器退出。
  3. 当函数被调用时创建一个局部命名空间,当函数返回结果 或 抛出异常时,被删除。每一个递归调用的函数都拥有自己的命名空间。

命名空间的访问

global

对于不可变数据类型 在局部可是查看全局作用域中的变量

但是不能直接修改

如果想要修改,需要在程序的一开始添加global声明

如果在一个局部(函数)内声明了一个global变量,那么这个变量在局部的所有操作将对全局的变量有效

a = 1
def outer():
    a = 1
    def inner():
        def inner2():
            global a
            a += 1
            print(a)
        inner2()
    inner()
    print('--', a)
outer()
print(a)

在局部空间中声明一个全局 变量,并操作该变量

nonlocal
a = 1
def outer():
    a = 1
    def inner():
        a = 10
        print(a)

        def inner2():
            nonlocal a
            a += 1
            print(a)
        inner2()
        print('==',a)
    inner()
    print('--', a)
outer()

nonlocal 局部命名空间中 内层函数对外层函数中的变量进行修改(作用仅限最近的一层)

1.外部必须有这个变量
2.在内部函数声明nonlocal变量之前不能再出现同名变量
3.内部修改这个变量如果想在外部有这个变量的第一层函数中生效

LEGB

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

前面讲到,Python的命名空间是一个字典,字典内保存了变量名称与对象之间的映射关系,因此,查找变量名就是在命名空间字典中查找键-值对
Python有多个命名空间,因此,需要有规则来规定,按照怎样的顺序来查找命名空间,LEGB就是用来规定命名空间查找顺序的规则

LEGB规定了查找一个名称的顺序为:local–>enclosing function locals–>global–>builtin

关于重名函数

首先我们要知道在python中并没有重载的概念(什么是重载:方法名相同,但是参数不同)

对于在命名空间中存在重名函数

def input():
    print('input is running now')

input()

input函数是python 中内置的函数,存在于内置命名空间,如果我们自己又定义了一个input函数会怎么样呢?我们定义的input函数存在于全局命名空间

运行结果
input is running now

并没有执行内置的input 而是执行了 我们自行定义的input。

结论

在正常情况下,直接使用内置的名字

当我们在全局定义了和内置名字空间中同名的名字时,会使用全局的名字(当我自己有的时候 我就不找我的上级要了如果自己没有 就找上一级要 上一级没有再找上一级 如果内置的名字空间都没有 就报错

多个函数应该拥有多个独立的局部名字空间,不互相共享

补充:python中为什么没有重载

重载是为了解决静态语言的灵活性,python这种语言需要什么重载

首先我们要知道函数的重载主要是为了解决两个问题:

1、可变参数类型.

2、可变参数个数

另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。

1、首先对于python 变量是没有类型的,他可以接受任意类型。

2、对于可变参数,之前有说过动态参数 *args **kwargs

作用域

全局作用域 —— 作用在全局 —— 内置和全局名字空间中的名字都属于全局作用域 ——globals()
局部作用域 —— 作用在局部 —— 函数(局部名字空间中的名字属于局部作用域) ——locals()

locals()和globals()
def demo():
    a = 1
    b = 2
    def inner():
        z =1
        print(globals())
        print(locals())
    inner()
demo()

-------------
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x01105590>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/untitled/oldBody/day10/work.py', '__cached__': None, 'demo': <function demo at 0x02D325D0>}

{'z': 1}

locals()是当前环境的命名空间中的内容

globals()是全局命名空间和内置命名空间的内容

作用域链和函数的嵌套

函数的嵌套调用

def max(a,b):
    return a if a>b else b

def the_max(x,y,z):  #函数的嵌套调用
    c = max(x,y)
    return max(c,z)

print(the_max(1,2,3))

函数的嵌套定义

函数嵌套就是函数中定义一个函数

def func1():
    a = 1
    def func2():
        print(a)
    func2()

func1()

内部函数可以使用外部函数的变量

函数的本质

函数名本质上就是函数的内存地址

可以被引用


def func():
    print('in func')

f = func
print(f)

---------------
<function func at 0x00B02588>

可以被当作容器类型的元素

def f1():
    print('f1')


def f2():
    print('f2')


def f3():
    print('f3')

l = [f1,f2,f3]
d = {'f1':f1,'f2':f2,'f3':f3}
#调用
l[0]()
d['f2']()

---------------

f1
f2

可以当作函数的参数和返回值

def func1():
    print('asd')

def demo(f):
    f()

demo(func1)

-------

asd
def func1():
    print('asd')

def demo(f):
    return f

print(demo(func1))

--------

<function func1 at 0x02BC25D0>

第一类对象(first-class object)指

1.可在运行期创建
2.可用作函数参数或返回值
3.可存入变量的实体。

变量就是第一类对象

闭包

内部函数包含对外部作用域而非全剧作用域名字的引用,该内部函数称为闭包函数

def demo():
    a = 1
    def inner():
        print(a)
    return inner

f = demo() # f 就是 inner函数的内存地址值
f()     

这里调用f() 可以读取到a的值 ,是因为 inner是个闭包, 当执行完f = demo()时 a并没有随着这行代码的执行结束而被回收。
我们调用f()时,可以一直使用a的内存而不需要多次创建了

判断是否是闭包

#输出的__closure__有cell元素 :是闭包函数
def func():
    name = 'eva'
    def inner():
        print(name)
    print(inner.__closure__)
    return inner

f = func()
f()

#输出的__closure__为None :不是闭包函数
name = 'egon'
def func2():
    def inner():
        print(name)
    print(inner.__closure__)
    return inner

f2 = func2()
f2()

闭包的嵌套

def wrapper():
    money = 1000
    def func():
        name = 'eva'
        def inner():
            print(name,money)
        return inner
    return func

f = wrapper()   #  拿到 func 的地址 并赋值给f
i = f()         #  执行f() 拿到inner的地址并赋值给i
i()             #  执行i
i()
i()

闭包不管嵌套多少层 执行多少次 像上述代码中的 name money 都只创建一次
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值