2024.1.24 Python学习笔记10

作用域

如果一个变量在函数内部定义,那么该变量只能在函数内使用,该变量称之为局部变量,作用域仅限于该函数。

如果一个变量在任何函数的外部定义,那么该变量可以在任何函数内使用,该变量称之为全局变量,作用域为全局。

x=100

def func():
    print(x)   #正常输出变量x值100
    y=200
    print(y)

func()  #函数func调用,输出函数内部变量y值200
print(y)    #报错:NameError: name 'y' is not defined

而如果函数内部定义变量与外部某变量同名,则在函数内外部同名变量会被覆盖,使用函数内部定义的变量。实际上,函数内部外部同名变量并非同一变量,通过id函数可以看出区别。即可以如此理解:全局变量可以在函数内访问,但无法在函数内被修改,因为在试图修改全局变量时,实际修改的是函数内部同名局部变量。

x=100

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

func()  #函数func调用,输出函数内部变量x值200
print(x)    #输出外部定义的变量x值100

使用global,可以明确声明函数内使用变量为全局变量,此时可以在函数内修改全局变量值。

x=100

def func():
    global x    #明确声明函数内使用的是全局变量x,此时可以在函数内修改x值
    x=200
    print(x)

func()  #函数func调用,输出变量x值200
print(x)    #输出200,可见函数内成功修改全局变量x值

使用nonlocal语句,则可以在函数内修改在外一层函数内的变量。该方法可应用于函数内部定义的函数。

def func():
    x=300
    def a():
        nonlocal x
        x=200
        print('In func_a,x=',x)
        print(id(x))
    a()
    print('In func_main,x=',x)  #函数a外输出的同样是x=200,a内对x有进行修改
    print(id(x))    #两者输出的x标识符相同,证明a内外操作的是同一个变量

func()

需要注意,变量名定义时,尽量不要使用内置函数名,虽然这样创建变量名合法,但后续再使用内置函数时,就会因为前面使用的变量名覆盖函数名而致使内置函数无法使用的问题。

str='一个有危险的变量名' #后面调用函数str时,会将str视为指向字符串的变量名
a=str(520)  #出现报错TypeError: 'str' object is not callable
print(a)

闭包

在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。实现闭包步骤是,定义外部函数,定义内部函数并使用外部函数变量,外部函数返回内部函数。

def outer():
    x,y=0,0
    def inner(up=0,right=0):
        nonlocal x,y
        x+=up
        y+=right
        print(f"position=({x},{y})")
    return inner

move=outer()
move(1,1)
#position=(1,1)
move(2,-3)
#position=(3,-2)

例如上例,外层函数outer通过返回内层函数inner,使变量move指向内层函数实现内层函数功能。观察可见,实际使用时,内层函数记忆了外层函数变量值,在第二次使用move函数时,移动起始位置是第一次移动后的坐标(1,1)。

记忆的原因,便是因为嵌套函数外层作用域被保存,因此outer函数调用结束后,其中的x、y因为内层嵌套函数inner被保存,在后续调用中依然存在。通过这种方法,可以将数据存储在外层函数变量x、y中。

可见,通过闭包,可以隐藏一些变量避免被其他函数影响,在例如游戏开发等环节有一定作用。

装饰器

一个范例:

import time
def doing():
    time.sleep(2)

def timer_func(func):
    print(f"Begin.")
    start=time.time()
    func()
    end=time.time()
    print(f"The function took {(end-start):.2f} seconds.")

timer_func(doing)
#Begin.
#The function took 2.01 seconds.

上面的代码实现了对函数doing运行时间的测量,观察输出可见函数运行时间为2.01秒。函数timer_func中参数func即用于测量的函数的函数名,通过start、end存储函数doing进行前后时间,计算即可得到函数doing执行时间。本段代码中,函数doing调用时间测量需要显式调用函数timer_func。

而在这段代码中,则不需要显式调用时间测量函数即可测量函数执行时间。

import time

def time_master(func):
    def timer_func():
        print(f"Begin.")
        start = time.time()
        func()
        end = time.time()
        print(f"End.")
        print(f"The function took {(end-start):.2f} seconds.")
    return timer_func

@time_master
def doing():
    time.sleep(2)
    pass

doing()
#Begin.
#End.
#The function took 2.01 seconds.

在上面代码中,@timer_master就是装饰器,通过该语法,实际就相当于使用函数闭包,即:

doing=timer_master(doing)
doing()

在不使用@timer_master时,上述调用doing效果与装饰器代码完全相同。

装饰器同样属于一种语法糖。很多时候代码修改希望可以增加新功能,例如身份验证、时间统计,装饰器即可实现不修改已有函数源代码与调用方式的情况下,为其增加新功能。上面的例子便是为一个函数增加其时间统计的功能,在程序开发处理客户多种稀奇古怪还自相矛盾还麻烦的需求时可以较为简单地满足。

一个函数可以使用多个装饰器,多个装饰器调用顺序如下范例可见:

def add(func):
    def inner():
        x=func()
        x+=1
        print(f"In func_add,x={x}.")
        return x
    return inner

def square(func):
    def inner():
        x=func()
        x*=x
        print(f"In func_square,x={x}.")
        return x
    return inner

def cube(func):
    def inner():
        x=func()
        x=x**3
        print(f"In func_cube,x={x}.")
        return x
    return inner

@add
@square
@cube
def return_2():
    print("x=2.")
    return 2

print(return_2())
'''
x=2.
In func_cube,x=8.
In func_square,x=64.
In func_add,x=65.
65
'''

实际可以认为,一个装饰器装饰了一个函数a后,生成的是一个新的函数b,再增加装饰器,装饰的是新的函数b,在生成新的函数c,之后又是新函数c被装饰......通过上面逻辑可以理解多个装饰器时调用顺序。

如果希望装饰器可以传入参数,则可以通过在原来的嵌套函数外部再嵌套一层含参函数即可实现。

import time

def logger(msg):
    def time_master(func):
        def inner():
            start=time.time()
            func()
            end=time.time()
            print(f"函数{msg}执行时间为{(end-start):.2f}秒.")
        return inner
    return time_master

@logger(msg='A')    #相当于funcA=logger(msg='A')(funcA)
def funcA():
    time.sleep(2)
    print("funcA")

@logger(msg='B')    #相当于funcB=logger(msg='B')(funcB)
def funcB():
    time.sleep(1)
    print("funcB")

funcA()
funcB()
'''
funcA
函数A执行时间为2.01秒.
funcB
函数B执行时间为1.01秒.
'''

生成器

生成器即使用了yield的函数,yield是一个关键字,用于定义生成器函数。

生成器函数是一种特殊的函数,可以在迭代过程中逐步产生值,而不是一次性返回所有结果。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

当在生成器函数中使用yield语句时,函数的执行将会暂停,并将yield后面的表达式作为当前迭代的值返回。然后,每次调用生成器的next方法或使用for循环进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到yield语句。这样,生成器函数可以逐步产生值,而不需要一次性计算并返回所有结果。

def counter():
    i=0
    while i<5:
        yield i
        i+=1
    return

for i in counter():
    print(i)
'''
0
1
2
3
4
'''

如果需要实现,再次调用函数时,函数中变量继续可以使用函数上次调用时各个变量值,可以考虑借助于迭代器。例如生成一个斐波那契数列:

def fib():
    fib1, fib2 = 0, 1
    while True:
        yield fib1
        fib1, fib2 = fib2, fib1 + fib2

f=fib()
for i in range(10):
    print(next(f))  #输出:0,1,1,2,3,5,8,13,21,34

而使用闭包或者全局变量也可以实现:

# 使用闭包输出斐波那契数列

def fib_a():
    fib1, fib2 = 0, 1

    def inner():
        nonlocal fib1, fib2
        print(fib1)
        fib1, fib2 = fib2, fib2 + fib1
    return inner


f1 = fib_a()
for i in range(10):
    f1()

# 使用全局变量输出斐波那契数列

fib1, fib2 = 0, 1

def fib_b():
    global fib1, fib2
    print(fib1)
    fib1, fib2 = fib2, fib2 + fib1
    return

for i in range(10):
    fib_b()

列表有列表表达式,生成器也有生成器表达式,语法:

( for  in )

学习元组时会疑惑为什么没有想象中的元组表达式,上面的语法看似是元组表达式,实际却是生成器表达式。

t=(i**2 for i in range(5))
print(type(t))
# ,说明t属于生成器
for i in range(5):
    print(next(t))
    # 依次输出0,1,4,9,16

你可能感兴趣的:(python学习笔记,学习,笔记,python)