• Python学习笔记2


    闭包

    闭包用起来简单,实现起来可不容易。

    另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:

    def count():
        fs = []
        for i in range(1, 4):
            def f():
                 return i*i
            fs.append(f)
        return fs
    
    f1, f2, f3 = count()

    在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。

    你可能认为调用f1()f2()f3()结果应该是149,但实际结果是:

    >>> f1()
    9
    >>> f2()
    9
    >>> f3()
    9

    全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9

    返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

    如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

    >>> def count():
    ...     fs = []
    ...     for i in range(1, 4):
    ...         def f(j):
    ...             def g():
    ...                 return j*j
    ...             return g
    ...         fs.append(f(i))
    ...     return fs
    ... 
    >>> f1, f2, f3 = count()
    >>> f1()
    1
    >>> f2()
    4
    >>> f3()
    9


    装饰器

    在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

    本质上,decorator就是一个返回函数的高阶函数。

    def log(func):
        def wrapper(*args, **kw):
            print 'call %s():' % func.__name__
            return func(*args, **kw)
        return wrapper

    观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

    @log
    def now():
        print '2013-12-25'

    调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

    >>> now()
    call now():
    2013-12-25

    @log放到now()函数的定义处,相当于执行了语句:

    now = log(now)

    由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

    wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

    如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

    def log(text):
        def decorator(func):
            def wrapper(*args, **kw):
                print '%s %s():' % (text, func.__name__)
                return func(*args, **kw)
            return wrapper
        return decorator

    这个3层嵌套的decorator用法如下:

    @log('execute')
    def now():
        print '2013-12-25'

    执行结果如下:

    >>> now()
    execute now():
    2013-12-25

    和两层嵌套的decorator相比,3层嵌套的效果是这样的:

    >>> now = log('execute')(now)

    我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

    以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'

    >>> now.__name__
    'wrapper'

    因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

    不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

    import functools
    
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print 'call %s():' % func.__name__
            return func(*args, **kw)
        return wrapper

    或者针对带参数的decorator:

    import functools
    
    def log(text):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kw):
                print '%s %s():' % (text, func.__name__)
                return func(*args, **kw)
            return wrapper
        return decorator

    import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

    访问限制

    如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

    class Student(object):
    
        def __init__(self, name, score):
            self.__name = name
            self.__score = score
    
        def print_score(self):
            print '%s: %s' % (self.__name, self.__score)

    改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name实例变量.__score了:

    >>> bart = Student('Bart Simpson', 98)
    >>> bart.__name
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Student' object has no attribute '__name'

    这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

    但是如果外部代码要获取name和score怎么办?可以给Student类增加get_nameget_score这样的方法:

    class Student(object):
        ...
    
        def get_name(self):
            return self.__name
    
        def get_score(self):
            return self.__score

    如果又要允许外部代码修改score怎么办?可以给Student类增加set_score方法:

    class Student(object):
        ...
    
        def set_score(self, score):
            self.__score = score

    你也许会问,原先那种直接通过bart.score = 59也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

    class Student(object):
        ...
    
        def set_score(self, score):
            if 0 <= score <= 100:
                self.__score = score
            else:
                raise ValueError('bad score')

    需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

    有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

    双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

    >>> bart._Student__name
    'Bart Simpson'

    但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

  • 相关阅读:
    mysql 获取字符串的长度
    mysql 字符类以及重复元字符
    mysql 字段拼接
    mysql 去除字符串中的空格
    mysql 正则表达式
    mysql 选出前五个元素
    mysql regexp 表达式
    mysql 选择所有同学名字
    mysql 获取数学成绩最高以及最低的同学
    mysql 获取单个科目的平均分
  • 原文地址:https://www.cnblogs.com/alansheng/p/4544166.html
Copyright © 2020-2023  润新知