python[变量作用域-函数-闭包-装饰器-生成器]

这里写图片描述

摘要:主要对python的系统与关联地复习一下,从最基础的变量作用域,到函数,闭包,装饰器,生成器作相关的复习。

环境:Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)] on win32

0. 辅助代码

用来打印dict的,为了对比较地看,对key作了一个排序。

#这个是辅助工具
def showDic(name, dic):
    print('----------------%s-----------------' % (name))
    items = dic.items()
    a = sorted(items)
    for k, v in a:
        if k.startswith('__'):
            continue
        print('%-10s:%s' % (k, v))

1. 变量的作用域与闭包

变量的作用域分为全局与局部的,读取的规则是如果在局部作用域中可以访问全局的,但如果想修改要加上global; 对于在局部的函数想直接修改非全局的参数不可以的,可以访问,不过像global关键词那样,可以加上一nonlocal关键词也可以。对于访问命令空间规则,先查看自己命令空间,再扩大范围去访问的。
另外,对于闭包,闭包主要是想理解下那个自由变量,在内部函数的命名空间中有空上变量的,不会因为外部函数调完之后就消失了,这个是闭包的一个很重要的特性,可用closure查到这个自由变量。
具体的看下面的注解。

print('----------------%s-----------------' % ('变量的作用范围考察'))
#全局变量
a_string = "This is a global variable"
a_string01 = "This is a global variable01"
a_string02 = "This is a global variable02"


def foo(x, y=4):  # 1 两个局部变量
    i = 5  # 2 定义局部变量

    def inner():  # 3 嵌套函数:定义内部函数,函数是一下对象的角度解释,这个也属于foo的局部变量;
        x = 9  # 4 x是inner的局部变量,这个不是foo上面的x,这个思想与a_string01那个思想相同
        global a_string02
        a_string02 = 'text02'  # 这个修改会对全局变量生效的。
        # i += 1 # 5 不可以对局部外的非全局变量进行修改;如果加了global修改了会出现name 'i' is not defined 或 直接修改会出现:UnboundLocalError: local variable 'i' referenced before assignment
        nonlocal i
        i += 1
        showDic('foo-inner', locals())
        return x

    def inner01():  # 6 嵌套函数--闭包函数--嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间。
        global a_string02
        a_string02 = 'inner01-text02'  # 这个修改会对全局变量生效的。
        showDic('foo-inner01', locals())
        return i  # 7 i为自由变量,为foo函数的局部变量

    global a_string  # 声明了这个是用外部变量,下面的使用与修改都会有所影响
    a = a_string  # 在python3.5这里,使用全局变量,如果前面不加上global a_string,会报错:local variable 'a_string' referenced before assignment
    a_string = 'text'  # 修改了全局变量,在局部的命名空间中没有这个变量的。
    a_string01 = 'text01'  # 8 这个只是增加了一个局部变量
    showDic('foo', locals())
    return (inner(), inner01)  # 9 返回了两种不同的值,一个数值innder(),一个是函数对象inner01;


showDic('globals00', globals())
tu = foo(5)  # 10
f = tu[1]  # 11
showDic('globals01', globals())
v1 = tu[0]
v2 = f()
showDic('globals02', globals())

# 注意闭包中的自由变量,在内部函数的命名空间中有空上变量的,
# 不会因为外部函数调完之后就消失了,这个是闭包的一个很重要的特性,可用__closure__查到这个自由变量。
print('----------------%s-----------------' % ('f.__closure__'))
print(f.__closure__)
----------------变量的作用范围考察-----------------
----------------globals00-----------------
a_string  :This is a global variable
a_string01:This is a global variable01
a_string02:This is a global variable02
foo       :<function foo at 0x0000000000DEA620>
showDic   :<function showDic at 0x0000000000DEA048>
----------------foo-----------------
a         :This is a global variable
a_string01:text01
i         :5
inner     :<function foo.<locals>.inner at 0x0000000000DEA840>
inner01   :<function foo.<locals>.inner01 at 0x0000000000DEA8C8>
x         :5
y         :4
----------------foo-inner-----------------
i         :6
x         :9
----------------globals01-----------------
a_string  :text
a_string01:This is a global variable01
a_string02:text02
f         :<function foo.<locals>.inner01 at 0x0000000000DEA8C8>
foo       :<function foo at 0x0000000000DEA620>
showDic   :<function showDic at 0x0000000000DEA048>
tu        :(9, <function foo.<locals>.inner01 at 0x0000000000DEA8C8>)
----------------foo-inner01-----------------
i         :6
----------------globals02-----------------
a_string  :text
a_string01:This is a global variable01
a_string02:inner01-text02
f         :<function foo.<locals>.inner01 at 0x0000000000DEA8C8>
foo       :<function foo at 0x0000000000DEA620>
showDic   :<function showDic at 0x0000000000DEA048>
tu        :(9, <function foo.<locals>.inner01 at 0x0000000000DEA8C8>)
v1        :9
v2        :6
----------------f.__closure__-----------------
(<cell at 0x00000000002F0B28: int object at 0x0000000058440270>,)

2. 函数参数

参数中,主要注意两个参数,*args与**kwargs,一个元组的,一个是字典的。

print('----------------%s-----------------' % ('函数参数'))

def func01(*args):
    print(args)  # 1 列表

def func02(x, y, *args):  # 2 带有指定的列表
    print(x, y, args)

def func03(**kwargs):  # 字典
    print(kwargs)

def func04(x, y):  # 验证字典可以自动匹配
    return x + y

func01()
func01(1, 2, 3)
func02('a', 'b', 'c')
func03()
func03(x=1, y=3)
dct = {'x': 1, 'y': 2}
func04(**dct)

结果

----------------函数参数-----------------
()
(1, 2, 3)
a b ('c',)
{}
{'y': 3, 'x': 1}

3. 装饰器###

装饰器的实现,可以采用方法去实现,也可以采用类去实现。
主要实现了可以接受一个callable对象,并返回一个callable对象就可以。
装饰器也可以带有参数,可以应用functools import wraps中的wraps去解决函数名不一致的问题,想用得更方便与更酷,可以用第三方的装饰器wrapt,
http://wrapt.readthedocs.io/en/latest/quick-start.html 【wrapt的官网】

print('----------------%s-----------------' % ('装饰器[方法实现]'))

# 装饰器:装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版函数。
# 它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
# 我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
def logger(func):  # @这个东西会默认地把修改的函数名转过来
    def wrapper(*args, **kwargs):  # 1 可以应用所有函数了
        print("参数为: %s, %s" % (args, kwargs))
        return func(*args, **kwargs)  # 2
    return wrapper

@logger
def add(x, y, kwargs):
    print('ffdfd')
    print(x, y)
    return x + y

print(add(2, 3, dct))

@logger  # 等同写法:func05 = logger(func05)
def func05(x, z, **kwargs):  # 字典
    print(x, z, kwargs)

func05(x=1, y=2, z=(1, 2))

print('----------------%s-----------------' % ('带参数的装饰器[方法实现]'))

def loggerp(lev):  # @这个东西会默认地把修改的函数名转过来
    def logger(func):
        def wrapper(*args, **kwargs):  # 1 可以应用所有函数了
            print('lev=%s,fname=%s,args=%s,kwargs=%s' % (lev, func.__name__, args, kwargs))
            return func(*args, **kwargs)

        return wrapper

    return logger


@loggerp(lev='debug')
def func06(x, z, **kwargs):  # 字典
    print(x, z, kwargs)


func06(x=1, y=2, z=(1, 2))

print('----------------%s-----------------' % ('装饰器的函数名问题'))
print('func05.__name__:', func05.__name__)
print('func06.__name__:', func06.__name__)

print('修正之后...')

from functools import wraps
def logger01(func):
    @wraps(func)  # 从标准库中增加这个装饰器
    def wrapper(*args, **kwargs):
        print("参数为: %s, %s" % (args, kwargs))
        return func(*args, **kwargs)  # 2

    return wrapper

@logger01  # 等同写法:func05 = logger(func05)
def func07(x, z, **kwargs):  # 字典
    """this is func07"""
    print(x, z, kwargs)

print('func07.__name__:', func07.__name__)
print('func07.__doc__:', func07.__doc__)

# 装饰器要求接受一个callable对象,并返回一个callable对象,
# 基于类的实现主要是运用内置的__call__方法
# print('----------------%s-----------------' % ('装饰器[类实现]'))
# print('----------------%s-----------------' % ('内置方法__call__'))

结果:

----------------装饰器[方法实现]-----------------
参数为: (2, 3, {'y': 2, 'x': 1}), {}
ffdfd
2 3
5
参数为: (), {'y': 2, 'z': (1, 2), 'x': 1}
1 (1, 2) {'y': 2}
----------------带参数的装饰器[方法实现]-----------------
lev=debug,fname=func06,args=(),kwargs={'y': 2, 'z': (1, 2), 'x': 1}
1 (1, 2) {'y': 2}
----------------装饰器的函数名问题-----------------
func05.__name__: wrapper
func06.__name__: wrapper
修正之后...
func07.__name__: func07
func07.__doc__: this is func07

4. 生成器

生成器生成可用括号去生成,或者写一个带有yield的函数。
对于括号生成生成器,要与List区别开来,list的数据是现成的,而生成器的数据是用的时候才有的,例如在遍历的时候,通过next函数来产生。
另外,对于带有yield的函数,这个要与函数区别开来,这个东西形式与函数一样的,也可以用在装饰上,但与函数比,有一个很大的不同就是,对于函数,函数名后加上括号就运行了,对于生成器,加了括号只是生成器的创建与生成,不会运行,如果运行得循环或next或send. 记住何时运行很重要。
对于next与send区别是,next不可以向生成器发送数据,send可以向生成器发送数据,当g.send(None)与next也就相同了。
对于带yield的函数运行,当生成器g被next(g)或g.send(None)后,生成器g就开始运行了,直到在函数中遇到了yield,函数那边不运行了,把yield后面的数据返回next或send这边,当再遇到next或send时,生成器函数再恢复运行,直到遇到下一个yield或结束。下面的例子很清楚可以看到。。
对于把数据,转到生成器函数中,send可以,也可以以抛出异常的方式向生成函数那里抛去,它里面可以对函数的异常的处理。
对于正常send过来的数据,在yield前面加上等号就可以接收了,例如val = yield 1。

class Malfunction(Exception):
    pass

def my_generator01():
    yield 1
    yield 2
    yield 3
    yield 4

def my_generator02():
    yield 'a'
    yield 'b'
    yield 'c'
    yield 'd'

def my_generator():
    print('starting up')

    val = yield 1
    print('got:', val)

    val = yield 2
    print('got:', val)

    # 可以接受异常,并进行处理
    try:
        yield 3
    except Malfunction:
        print('malfunction!')

    yield 4

    print('done')


if __name__ == '__main__':
    print('################用括号生成生成器##############')
    #创建了generator后,可通过for循环来迭代它
    g = (x * x for x in range(3))
    print(g)
    for i in g:
        print(i)

    print('################my_generator01/my_generator02##############')
    #注意了,这个'()'当不会引起函数的调用, 这个会生成一个生成器。
    gens = [my_generator01(),my_generator02()]
    while gens:
        for g in gens[:]:
            try:
                n = next(g)
            except StopIteration:
                gens.remove(g)
            else:
                print(n)

    print('################my_generator##############')
    gen = my_generator()

    print('main:', gen.send(None))  # start the generator
    print('main:', gen.send(10))  # send the value 10
    print('main:', gen.send(20))  # send the value 20
    print('main:', gen.throw(Malfunction()))  # raise an exception inside the generator

try:
    next(gen)
except StopIteration:
    pass

运行结果

################用括号生成生成器##############
<generator object <genexpr> at 0x00000000006B2A98>
0
1
4
################my_generator01/my_generator02##############
1
a
2
b
3
c
4
d
################my_generator##############
starting up
main: 1
got: 10
main: 2
got: 20
main: 3
malfunction!
main: 4
done

粗略复习与疏理了了一下python,如有不足,请多多指教。这个因为这几天在看scrapy源码引发的一系列活动,scrapy中应用了twisted框架,这个框架应用了内联回调,内联回调用到了闭包装饰器生成器,顺着就回看这些。。

【作者:happyprince,http://blog.csdn.net/ld326/article/details/78746384

参考:
http://blog.csdn.net/tao01230/article/details/45972763【闭包出错】
http://python.jobbole.com/81683/【12步轻松搞定python装饰器】
https://www.cnblogs.com/cicaday/p/python-decorator.html 【详解Python的装饰器】
http://wrapt.readthedocs.io/en/latest/quick-start.html 【wrapt的官网】

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python函数中的变量作用域指的是变量的可见范围。在函数中定义的变量可以分为两种:局部变量和全局变量。 局部变量指的是在函数内部定义的变量,只能在函数内部使用,函数外部无法访问。当函数执行完毕后,局部变量的值会被销毁。 全局变量指的是在函数外部定义的变量,可以在函数内部和外部使用。但是,在函数内部如果要修改全局变量的值,需要使用global关键字进行声明。 在Python中,变量作用域遵循LEGB规则,即:Local(局部变量)-> Enclosing(闭包函数外的函数中的变量)-> Global(全局变量)-> Built-in(内置变量)。 当函数内部使用变量时,Python会按照LEGB规则从内到外查找变量,直到找到为止。如果在函数内部没有找到变量,则会继续向外查找,直到找到为止。如果最终还是没有找到变量,则会抛出NameError异常。 因此,在编写Python函数时,需要注意变量作用域,避免出现变量名冲突等问题。 ### 回答2: Python函数中,变量作用域并不像其他编程语言那样严格。在Python中,变量作用域很容易受到内层作用域的影响,而无法访问外层的变量,这部分属于局部变量。下面我们从全局变量和局部变量两个方面来讲解变量作用域。 一、全局变量作用域Python中,如果变量未定义在任何函数内,即在全局作用域内,那么在各个函数内都可以访问该变量。 例如: ``` count = 0 def test(): global count count += 1 print(count) test() ``` 以上代码中,count变量未定义在函数内部,属于全局作用域,在调用函数`test()`时,可以使用`global`关键字来声明该变量为全局变量,然后在函数内部可以直接对该变量进行修改和访问。 二、局部变量作用域Python中,如果变量定义在函数内部,则该变量作用域只限于函数内部,外部无法访问该变量,称为局部变量。 例如: ``` def test(): count = 0 count += 1 print(count) test() ``` 以上代码中,count变量定义在函数`test()`内部,属于局部变量。在函数内部对count进行修改和访问也是可以的,但是在函数外部是无法访问到该变量的,否则会报错。 需要注意的是,函数内的变量名如果和全局变量变量名相同,那么在函数内访问该变量时,默认会访问局部变量,而非全局变量。如果仍要在函数内部访问全局变量,可以使用`global`关键字进行声明。 例如: ``` count = 0 def test(): count = 1 print("count in local:", count) test() print("count in global:", count) ``` 以上代码中,函数内部定义了一个名为count的局部变量,调用函数后,输出的是局部变量count的值,而不是全局变量count的值0。如果要访问全局变量count的值,可以在函数内部使用`global count`声明该变量为全局变量,再进行访问。 总之,Python变量作用域相对比较宽松,可以根据具体情况进行灵活使用,但是在使用局部变量和全局变量时要避免命名冲突,同时合理使用`global`关键字来声明全局变量,以免出现意想不到的错误。 ### 回答3: 在Python中,变量作用域指的是变量所能被访问到的范围。在一个函数中定义的变量只能在函数内部被访问到,而在函数外定义的变量则可以在整个程序中被访问到。 Python中的变量作用域分为两种:局部作用域和全局作用域。局部变量指的是在一个函数内部定义的变量,只能在该函数内部访问。全局变量指的是在函数外部定义的变量,可以在整个程序中被访问到。如果在函数内部要访问全局变量,则需要使用global关键字进行声明。 在Python中,变量作用域可以遵循 LEGB 原则,即 Local(局部)、Enclosing(闭包)、Global(全局)、Built-in(内置)的顺序进行查找。这意味着变量首先在函数内部被查找,然后在函数外部被查找,之后在内置变量中被查找。 当在函数内部定义与全局变量同名的变量时,Python会优先使用局部变量而不是全局变量。如果需要在函数内部修改全局变量,则必须使用global关键字声明。 在使用闭包时,可以通过在函数内部再定义一个函数,内部函数可以访问外部函数中的变量。这样的变量作用域称为嵌套作用域。在Python中,使用nonlocal关键字可以实现在内部函数中修改外部函数中定义的变量。 总之,学习变量作用域对于编写规范化的程序来说非常重要,特别是在编写复杂的函数时。了解变量作用域可以帮助我们更好地管理变量,并避免不必要的错误和问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值