美文网首页
A Bite of Python

A Bite of Python

作者: 不_一 | 来源:发表于2018-03-25 13:28 被阅读0次

    yield

    当一个函数中出现了yield关键字时,这个函数就变成了一个生成器,调用这个函数时会返回一个generator对象,接下来可以用next函数来驱动这个生成器往下执行,直至碰到一个yield语句,这时候生成器会返回函数yield语句后面的对象,也可以调用生成器的send方法给生成器发送一个值,这个发送的值会成为yield语句的返回值,当生成器执行完最后一个yield语句后,此时继续驱动生成器就会抛出一个StopIteration的异常,生成器的return语句的返回值会赋值给这个异常实例的value属性上。

    def g():
        a = yield 1
        b = yield a
        yield b
        return "done"
    
    g = g()
    g
    # <generator object g at 0x101d4c990>
    next(g) # equals to `g.send(None)`, variable a will become a `None`
    # 1
    g.send(2)
    # 2
    next(g)
    # None
    try:
        next(g)
    except StopIteration as e:
        print(e.value)
        # done
    

    LEGB

    LEGB规则定义了Python对命名空间的查找顺序。

    具体如下:

    Locals 函数内的命名空间
    Enclosing 外层函数的命名空间
    Globals 所在模块命名空间
    Builtins Python内置模块的名字空间

    def outside():
        v = 2
        def inside():
            print( # B 
            v) # E
        inside() # L
    outside() # G
    globals()
    

    返回当前作用域的全局变量空间

    locals()

    返回当前作用域的本地变量空间

    自省

    自省

    dir:返回指定对象的属性列表。

    a = []
    dir(a)
    '''
    ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
    '''
    

    getattr:获取一个对象中的属性,若不存在则抛出AttributeError异常,如果指定了默认值则返回默认值。

    class A:
        pass
    getattr(A, 'a')
    # AttributeError: type object 'A' has no attribute 'a'
    getattr(A, 'a', 1)
    # 1
    

    hasattr:检查一个对象是否有指定的属性。

    hasattr(A, 'a')
    # False
    

    setattr:给一个对象的设置属性名和属性名的对应值。

    if not hasattr(A, 'a'):
        setattr(A, 'a', 2)
    if hasattr(A, 'a'):
        getattr(A, 'a')
    # 2
    

    inspect:一个非常有用的内置模块,能够非常方便的获取对象信息。

    import inspect
    inspect.getmembers(a)
    

    垃圾回收

    Python的垃圾回收方式主要包括引用计数法和分代回收法。

    • 引用计数

    Python的垃圾回收主要依赖基于引用计数的回收算法,当一个对象的引用数为0时,Python虚拟机就会考虑回收对象占用的内存。

    但引用计数的缺点也十分明显,那就是循环引用的问题,试想有两个容器对象,分别引用了对方作为自己的一个属性,这样在删除其中一个变量后,由于其对象还保留着引用,因此它的内存无法被释放。为了解决这个问题,Python使用了mark-sweep算法,定期扫描跟踪对象的引用情况,释放无用对象的内存。

    • 分代回收

    分代回收算法基于弱代假说的思想,即:相对于存活时间长的对象,GC更加频繁去处理那些年轻的对象。

    Python的分代回收机制把对象按照存活时间分成三个集合,每一个集合即为一个“代”,分别对于一个链表,当垃圾回收器进行收集时,会首先考虑去检查存活时间较短的对象集合。

    容器

    Python包括多种容器数据类型:

    数据类型 可变 有序
    list yes yes
    tuple no yes
    str no yes
    range no yes
    bytes no yes
    bytearray yes yes
    array yes yes
    set yes no
    frozenset no no
    dict yes no
    OrderedDict yes yes

    3.1 函数

    3.1.1 栈帧

    Python通过栈帧来关联函数之间的调用,每次调用函数时,函数的栈帧会放在调用栈上,调用栈的深度随着函数的调用深度增长,栈帧对象包含了函数的code对象,函数的局部变量数据,前一个栈帧和行号等信息。

    def c():
        pass
    
    def b():
        pass
    
    def a():
        b()
        c()
    
    def go():
        a()
    
    go()
    

    在下面这个例子中,f函数通过获取调用链中前一个栈帧的信息,拿到add函数栈帧中局部变量ab的值,使得f函数在不接收参数的情况下完成了透明的加法:

    import inspect
    
    def f():
        current_frame = inspect.currentframe()
        back_frame = current_frame.f_back
        a = back_frame.f_locals['a']
        b = back_frame.f_locals['b']
        return a + b
    
    def add(a, b):
        print(f())
    
    add(1, 2)
    # 3
    

    装饰器

    装饰器是一个函数,它用于给已有函数扩展功能,它通常接收一个函数作为参数,并返回一个新的函数。

    装饰器可配合@符号十分简洁地应用在函数上。

    def tag(func):
        def wrapper(*args, **kwargs):
          func_result = func(*args, **kwargs)
          return '<{tag}>{content}</{tag}>'.format(tag=func.__name__, content=func_result)
        return wrapper
    
    @tag
    def h1(text):
        return text
    
    @tag
    def p(text):
        return text
    
    def div(text):
        return text
    
    html = h1(p('Hello World'))
    print(html)
    # <h1><p>Hello World</p></h1>
    html2 = tag(div)('Hello World')
    print(html2)
    # <div>Hello World</div>
    

    类装饰器

    class Tag:
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args, **kwargs):
            func_result = self.func(*args, **kwargs)
            return '<{tag}>{content}</{tag}>'.format(tag=self.func.__name__, content=func_result)
    
    @Tag
    def h1(text):
        return text
    
    html = h1('Hello World')
    print(html)
    

    yield能把一个函数变成一个generator,与return不同,yield在函数中返回值时会保存函数的状态,使下一次调用函数时会从上一次的状态继续执行,即从yield的下一条语句开始执行。

    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            yield(b)
            a, b = b, a + b
            n = n + 1
    
    f = fib(10)
    next(f)
    # 1
    next(f)
    # 1
    next(f)
    # 2
    f.send(None)
    # 3
    for num in f:
        print(num)
    # 5
    # 8
    # 13
    # 21
    # 34
    # 55
    next(f)
    # StopIteration
    

    可见性

    当一个类的方法名前以单下划线或双下滑线开头时,通常表示这个方法是一个私有方法。

    但这只是一种约定俗成的方式,对于外部来说,以单下划线开头的方法依然是可见的,而双下划线的方法则被改写成_类名__方法名存放在名字空间中,所以如果B继承了A,它是访问不到以双下划线开头的方法的。

    class A:
        def public_method(self):
            pass
    
        def _private_method(self):
            pass
    
        def __private_method(self):
            pass
    
    a = A()
    a.public_method()
    # None
    a._private_method()
    # None
    a.__private_method()
    # AttributeError: 'A' object has no attribute '__A_private_method'
    a._A__private_method()
    # None
    A.__dict__
    # mappingproxy({'_A__private_method': <function __main__.A.__private_method>,
    #               '__dict__': <attribute '__dict__' of 'A' objects>,
    #               '__doc__': None,
    #               '__module__': '__main__',
    #               '__weakref__': <attribute '__weakref__' of 'A' objects>,
    #               '_private_method': <function __main__.A._private_method>,
    #               'public_method': <function __main__.A.public_method>})
    

    函数与绑定方法

    类方法本质上是一个函数,通过绑定对象的方式依附在类上成为方法。

    假如有下面一个类:

    class A:
        def __init__(self):
            self.val = 1
        def f(self):
            return self.val
    

    可以看到f是A的属性列表中的一个函数:

    A.f
    # <function __main__.A.f>
    A.__dict__['f']
    # <function __main__.A.f>
    

    而当直接用A.f()的方式调用f方法时,会抛出一个异常,异常的原因很直接,参数列表有一个self参数,调用时却没有指定这个参数。而用A().f()的方式调用f方法时同样没有指定参数,却能正常执行,这是为什么呢?

    执行A().f语句可以发现,这时候的f不再是一个函数,而是一个绑定方法,而它绑定的对象则是一个A的实例对象,换句话说,这时候f参数中的self已经跟这个实例对象绑定在一起了,所以用实例调用一个普通方法时,无须人为地去指定第一个参数。

    A.f()
    # TypeError: f() missing 1 required positional argument: 'self'
    A().f() # auto binding
    # 1
    A().f
    # <bound method A.f of <__main__.A object at 0x121a3bac8>>
    

    通过调用f函数的__get__(函数的描述器行为)方法来为函数绑定到一个对象上:

    class B:
        def __init__(self):
            self.val = 'B'
    
    A.f.__get__(B(), B)
    # <bound method A.f of <__main__.B object at 0x111f9b198>>
    A.f.__get__(B(), B)()
    # B
    

    通过types模块的MethodType动态把方法附加到一个实例上:

    import types
    def g(self):
        return self.val + 1
    
    a.g = types.MethodType(g, a)
    a.g()
    # 2
    

    实例方法、类方法、静态方法

    类方法分为普通方法静态方法类方法,静态方法和类方法可分别通过classmethodstaticmethod装饰器来定义。

    区别于普通方法,类方法的第一个参数绑定的是类对象,因此可以不需要实例对象,直接用类调用类方法执行。

    而静态方法则没有绑定参数,跟类方法一样,可以通过类或者实例直接调用。

    class C:
        def instance_method(self):
            print(self)
    
        @classmethod
        def class_method(cls):
            print(cls)
    
        @staticmethod
        def static_method():
            pass
    
    C.instance_method
    # <function __main__.C.instance_method>
    C.instance_method()
    # TypeError: instance_method() missing 1 required positional argument: 'self'
    C().instance_method()
    # <__main__.C object at 0x121133b00>
    C.class_method
    # <bound method C.class_method of <class '__main__.C'>>
    C.class_method()
    # <class '__main__.C'>
    C().class_method()
    # <class '__main__.C'>
    C.static_method
    # <function __main__.C.static_method>
    C.static_method()
    # None
    C().static_method()
    # None
    

    通过输出可以看到的一点就是,类方法是一个已经绑定类对象的方法,而普通方法和静态方法在被调用前只是一个普通函数。

    property

    property是一个内置函数,它可以将方法与属性调用绑定在一起。

    看下面这个例子,Person类有个年龄属性age,现在想要在对这个属性赋值时给它加一个类型校验,借助于property函数便能在不需要修改用法的情况下为属性加上getter/setter方法:

    class Person:
      def __init__(self, age):
          self._age = age
    
      @property
      def age(self):
          return self._age
    
      @age.setter
      def age(self, age):
          if not isinstance(age, int):
              raise TypeError('age must be an integer')
          self._age = age
    
    p = Person(20)
    print(p.age)
    # 20
    p.age = 30
    print(p.age)
    # 30
    p.age = '40'
    # Traceback (most recent call last):
    #   File "test.py", line 20, in <module>
    #     p.age = '40'
    #   File "test.py", line 12, in age
    #     raise TypeError('age must be an integer')
    # TypeError: age must be an integer
    

    property还常用于延迟计算以及属性缓存,例如下面Circle类的area属性,只有在被访问到的情况下才进行计算,并把结果缓存到实例的属性上。

    class Circle(object): 
      def __init__(self, radius): 
        self.radius = radius 
    
      @property
      def area(self): 
        if not hasattr(self, '_cache_area'):
            print('evalute')
            setattr(self, '_cache_area', 3.14 * self.radius ** 2)
        return getattr(self, '_cache_area')
    
    c = Circle(4)
    print(c.area)
    # evalute
    # 50.24
    print(c.area)
    # 50.24
    

    描述器

    上面谈论到的property也好,classmethodstaticmethod也好,甚至是所有的函数,本质上都是实现了描述器协议的对象。

    描述器是一个绑定行为的对象属性,如果一个对象定义了__set__()__get__(),那么它就是一个数据描述器,如果只定义了__get__(),那么它就是一个非数据描述器。

    下面定义了一个描述器类Integer,这个类的一个实例作为类属性赋给类C的num变量上,当C的实例访问这个属性时便会触发描述器协议,调用描述器的__get__方法,其中第一个参数是实例本身,而第二个参数是实例所归属的类(是否跟实例方法的调用有异曲同工之妙?),如果属性是以类的形式访问,那么第一个参数的值为None;相似地,当对这个属性进行赋值操作时,则会调用描述器的__set__方法,借用这个特性可以方便对属性做一些校验操作。

    class Integer:
        def __init__(self, name):
            self.name = name
            self.val = None
    
        def __get__(self, obj, obj_type):
            print(obj, obj_type)
            return self.name
    
        def __set__(self, obj, val):
            if not isinstance(val, int):
                raise Exception('value must be an integer!')
            print('set value to {}'.format(val))
            self.val = val
    
    class C:
        num = Integer('I')
    
    c = C()
    c.num
    # <__main__.C object at 0x10cc735c0> <class '__main__.C'>
    c.num = 5
    # set value to 5
    c.num = '5'
    # Exception: value must be an integer!
    

    说到这里已经可以猜到property是如何实现的了,实际上Python官方已经给出了一份模拟property实现的Python代码:

    class Property(object):
        "Emulate PyProperty_Type() in Objects/descrobject.c"
    
        def __init__(self, fget=None, fset=None, fdel=None, doc=None):
            self.fget = fget
            self.fset = fset
            self.fdel = fdel
            if doc is None and fget is not None:
                doc = fget.__doc__
            self.__doc__ = doc
    
        def __get__(self, obj, objtype=None):
            if obj is None:
                return self
            if self.fget is None:
                raise AttributeError("unreadable attribute")
            return self.fget(obj)
    
        def __set__(self, obj, value):
            if self.fset is None:
                raise AttributeError("can't set attribute")
            self.fset(obj, value)
    
        def __delete__(self, obj):
            if self.fdel is None:
                raise AttributeError("can't delete attribute")
            self.fdel(obj)
    
        def getter(self, fget):
            return type(self)(fget, self.fset, self.fdel, self.__doc__)
    
        def setter(self, fset):
            return type(self)(self.fget, fset, self.fdel, self.__doc__)
    
        def deleter(self, fdel):
            return type(self)(self.fget, self.fset, fdel, self.__doc__)
    

    classmethod的实现则更为简单一些:

    class ClassMethod(object):
        "Emulate PyClassMethod_Type() in Objects/funcobject.c"
    
        def __init__(self, f):
            self.f = f
    
        def __get__(self, obj, klass=None):
            if klass is None:
                klass = type(obj)
            def newfunc(*args):
                return self.f(klass, *args)
            return newfunc
    

    相关文章

      网友评论

          本文标题:A Bite of Python

          本文链接:https://www.haomeiwen.com/subject/xkuhcftx.html