Python高级编程

第一章:准备工作

  • Python的交互式命令行工具会在启动时查找环境变量PYTHONSTARTUP,并执行该变量中所指定的文件里的程序代码。
    如一般的添加自动补全的功能readline.parse_and_bind(‘tab: complete’)。
  • iPython是一个增强型的命令行工具。

  • Python中的setuptools相当于Debian中的apt,easy_install就是调用setuptools的工具。

第二章:语法最佳实现-低于类级

  • 每当要对序列中的内容进行循环处理时,就应该尝试用 列表推导式

  • 迭代器生成器 ,现代语言很多都有生成器,使用关键字 yield

    """ 迭代器的约定在于:
            next()方法返回下一个元素
            迭代完成后抛出StopIteration异常
            __iter__()方式返回迭代器本身
    """
    class MyIterator(object):
        def __init__(self):
            self.i = 0
    
        def next(self):
            if self.i > 10:
                return StopIteration
    
            self.i += 2
            return self.i
    
        def __iter__(self):
            return self
    
    [i for i in MyIterator()]
    
    """生成器返回一个特殊的对象:generator对象,它知道如何保存正在执行的环境。
    """
    def f():
        i = 0
        while True:
            i += 1
            yield i
    
    my_iter = f()
    [my_iter.next() for i in range(5)]
    
    >>> [2, 4, 6, 8, 10]
    >>> [1, 2, 3, 4, 5]
    
    生成器的工作原理是:

    每次遇到yield时,就相当于函数返回值似的返回这个值并保存当前的运行状态;当对应的迭代器再次被调用next()时,就恢复保存的状态并继续运行。

    一次返回一个元素,在很多时候可以提高整体性能,不必像传统迭代方式那样一次完成所有的计算。

  • 输入式生成器

    使用 (yield) 来转换为输入式生成器,等待 send()方法 传递参数再继续执行:

    >>> def f():
    ...   while True:
    ...     i = (yield)
    ...     print i
    ...
    >>> a=f()
    >>> a.next()
    >>> a.send(45)
    45
    >>> a.send(445)
    445
    
    send(123)方法相当于将(yield)赋值为123再调用next()继续。同理还有2个姐妹方法 throw() , close()
    throw()相当于抛出一个异常再调用next()继续,而close()相当于抛出GeneratorExit异常再调用next()继续。

    GeneratorExit异常在生成器中无法捕获,因为编译器需要用它来确定调用close()时是否正常退出。所以如果代码与这个异常关联,那么解释程序将抛出一个系统错误并退出。

  • 生成器表达式 :类型于列表推导式,用圆括号代替中括号。

    它和列表解析的语法很像,但是在大数据量处理时,生成器表达式的优势就体现出来了,因为它的内存使用方式更好,效率更高, 它并不创建一个列表,只是返回一个生成器

    >>> (i + 1 for i in range(10) if i % 2)
    <generator object <genexpr> at 0x011DC5D0>
    
  • 装饰器 : 它是函数和方法的封装(接收一个函数并返回增强版本的新函数):

    def mydec(i):
        def _mydec2(func):
            def __mydec3(*argv, **kw):
                print "Begin", i
                res = func(*argv, **kw)
                print "End"
                return res
            return __mydec3
        return _mydec2
    
    @mydec(4)
    def MyF1():
        print "MyF1() body..."
    
    @mydec(9)
    def MyF2():
        print "MyF2() body..."
    
    MyF1()
    MyF2()
    
    >>> Begin 4
    >>> MyF1() body...
    >>> End
    >>> Begin 9
    >>> MyF2() body...
    >>> End
    

    装饰器应该关注于所封装的函数或方法接收和返回的参数,如果需要,应该尽可能限制其内省工作。

  • with语法,解决资源释放问题的新语法,相当于 try...expect...finally... 的简写形式:

    class Context(object):
        def __enter__(self):
            print "Enter ..."
        def __exit__(self, exception_type, exception_value, exception_traceback):
            print "Leaving ..."
    
    with Context():
        print "I am in zone..."
    
    >>> Enter ...
    >>> I am in zone...
    >>> Leaving ...
    

    能够使用with语句来处理的类就是满足with协议的类,即实现了 __enter__ , __exit__ 两个方法。

第三章:语法最佳实现-类级

  • 最佳实践

    • 避免使用多重继承
    • super的使用必须一致,大家都倾向于不使用super,我也认为传统的根据类名来调用比较好,super带来的坏处大于好处
    • 不要混用老式的和新式的类,因为它们的MRO(方法解析顺序)不同
    • 调用父类时,为了避免出现问题,快速的使用__mro__检查一下类层次
  • 在Python中,类的私有属性是用前导_来实现命名改编的

  • 描述符 定义在引用一个对象的特征时所应该完成的事情,其实非常简单易解,就是实现了三个特殊的方法的类。 __get__在特性被读取时调用;__set__在特性被赋值时调用;__del__在特征被del时调用。 示例代码如下:

    class UpperString(object):
        def __init__(self):
            self.value = ''
        def __get__(self, instance, klass):
            return self.value
        def __set__(self, instance, value):
            self.value = value.upper()
    
    class MyClass(object):
        my_attr = UpperString()
    
    a_instance = MyClass()
    a_instance.my_attr = "tanchuanqi"
    print a_instance.my_attr
    
    >>> TANCHUANQI
    
  • 属性 把几个方法合并成一个属性,很好理解,也都是语法糖:

    class MyClass(object):
        def __init__(self):
            self.v = 0
        def _v_get(self):
            return self.v
        def _v_set(self, value)
            self.v = value
        def _v_delete(self):
            print "del self.v"
    
        my_property = property(_v_get, _v_set, _v_delete, "This is doc string")
    

    属性有一点不直观的就是:property()只会绑定当前类中的方法,绝不会绑定派生类中的方法,无论派生类是否重载了相关的函数:

    class FirstClass(object):
        def _get_v(self):
            return "FirstClass"
        value = property(_get_v)
    
    class SecondClass(FirstClass):
        def _get_v(self):
            return "SecondClass"
        #value = property(_get_v)         重新定义一次value property是解决办法
    
    SecondClass().value
    
    >>> 'FirstClass'
    

    这不合逻辑,所以不要在派生类中重载被关联为property的方法。解决这个问题的办法是再SecondClass中再重新定义一次value property。

  • 极少使用,它允许使用__solts__特性来为指定的类设置一个静态列表,并且跳过每个类实例中__dict__列表的创建工作。

  • __new__方法 它是特殊的元构造程序,与__init__不同的地方在于它一定会被调用,子类初始化时也会调用父类的__new__方法。 所以,很多初始化的工作都应该放在__new__方法中处理。

  • type 动态生成类型 内建类型type是内建的基本工厂:

    def method(self):
        return 1
    
    klass = type('MyClass', (object,), {'method':method})
    instance = klass()
    print instance.method
    
    >>> 1
    
  • __metaclass__方法 设置类级别的在类型创建时的操作:

    def equip(classname, base_types, dict):
        pass
    
    class MyClass(object):
        __metaclass__ = equip
        pass
    

    任何情况下都要记得,__metaclass__和其它的动态处理类的方法都要谨慎使用,很容易使类的结构变的一团糟!

第四章:选择好的名称

  • 命名风格:

    • 常量:使用大写+下划线的形式(常量最好统一放在包中的一个独立文件,像Django的config.py)
    • 变量:使用小写+下划线的形式
    • 函数和方法:也应该使用小写+下划线的风格
    • 属性:也是小写+下划线的形式
    • 特殊的方法(__str__,__item__,__repr__, …)应该被放在类定义的最前面
    • 类名:应该总是使用CamelCase的命名风格,当定义模块的私有类时,使用前导下划线
    • 模块和包:除__init__外,模块名称都应该使用 不带下划线的 小写字母命名
  • 私有元素用一个前导下划线标记_private,私有的变量、方法、类名、模块或包都应该用前导下划线标示为私有。

    私有元素的争论:

    一个前导下划线是约定为私有元素,而2个前导下划线则会强制进行命名改编(name mailing) 。所以很多人使用2个前导下划线来标识私有元素。 这其实是不合适的,因为 python中的name mailing的动机不是为了提供私有机制,而是为了确保基类隐式地避免子类中的冲突

    >>> class A:
    ...   def _abc(self):
    ...     pass
    ...
    >>> dir(A)
    ['__doc__', '__module__', '_abc']
    >>> class B:
    ...   def __abc(self):
    ...     pass
    ...
    >>> dir(B)
    ['_B__abc', '__doc__', '__module__']
    

    所以,正确的方法是使用1个前导下划线,而不是用2个来画蛇添足,因为2个前导下划线也同样保证不了私有机制。

  • 参数设计的3原则:

    • 根据设计来构建参数,即必须站在用户的角度来进行参数的设计。
    • 在任何情况下,断言assert都必须小心的进行,并且不应该导致Python变成一种静态类型的语言。
      唯一的使用场景就是保护代码不被无意义的调用。
    • 小心使用*args和**kw魔法参数,如果你觉得现在必须使用魔法参数,那么一般意味着这个方法需要被分解或重构!
    • 类及它的特性之间应当避免冗余,如SMTP.smtp_send()就是冗余,正确的命名应该是SMTP.send()
  • 设计API的原则:

    • 避免冗长,形成高级接口和低级接口的组合
    • 构建命名空间树
    • 分解代码:一个函数不应该超过一个屏幕;类的方法一般不超过10个;一个模块一般不超过500行;
    • 使用Egg:为了避免整个代码库都在一个目录下,并能够单独发布每个包,就可以使用Egg。它提供了构建”命名空间的包”的简单方法,就像Java中的JAR一样。
    • 使用deprecation过程(及warning模块)