zoukankan      html  css  js  c++  java
  • 摘选改善Python程序的91个建议

    1、理解Pythonic概念

    Pythonic

    Tim Peters 的 《The Zen of Python》相信学过 Python 的都耳熟能详,在交互式环境中输入import this可以查看,其实有意思的是这段 Python 之禅的源码:

    d = {}
    for c in (65, 97):
        for i in range(26):
            d[chr(i+c)] = chr((i+13) % 26 + c)
     
    print "".join([d.get(c, c) for c in s])

    书中还举了一个快排的例子:

    def quicksort(array):
        less = []
        greater = []
        if len(array) <= 1:
            return array
        pivot =array.pop()
        for x in array:
            if x <= pivot:
                less.append(x)
            else:
                greater.append(x)
        return quicksort(less) + [pivot] + quicksort(greater)

    8、利用assert语句来发现问题

    >>> y = 2
    >>> assert x == y, "not equals"
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AssertionError: not equals
    >>> x = 1
    >>> y = 2
    # 以上代码相当于
    >>> if __debug__ and not x == y:
    ...     raise AssertionError("not equals")
    ... 
    Traceback (most recent call last):
      File "<stdin>", line 2, in <module>
    AssertionError: not equals

    运行时加入-O参数可以禁用断言。

     9、数据交换不推荐使用中间变量
    x, y = 1, 2
    
    x, y = y, x

    原理:右值创建元组,左值接收元组的对应元素。

    10、充分利用Lazy evaluation 的特性

    def fib():
        a, b = 0, 1
        while True:
            yield a
            a, b = b, a + b

    11、理解枚举替代实现的缺陷

      利用Python的动态特性可以实现枚举:

    # 方式一
    class Seasons:
        Spring, Summer, Autumn, Winter = range(4)
    # 方式二
    def enum(*posarg, **keysarg):
        return type("Enum", (object,), dict(zip(posarg, range(len(posarg))), **keysarg))
    Seasons = enum("Spring", "Summer", "Autumn", Winter=1)
    Seasons.Spring
    # 方式三
    >>> from collections import namedtuple
    >>> Seasons = namedtuple('Seasons', 'Spring Summer Autumn Winter')._make(range(4))
    >>> Seasons.Spring
    0
    # 但通过以上方式实现枚举都有不合理的地方
    >>> Seasons._replace(Spring=2)                                             │
    Seasons(Spring=2, Summer=1, Autumn=2, Winter=3)  
    # Python3.4 中加入了枚举,仅在父类没有任何枚举成员的时候才允许继承
    View Code

    12、不推荐使用type来进行类型检查、而使用isinstance()

    14、警惕eval()的安全漏洞

    # 合理正确地使用
    >>> eval("1+1==2")
    True
    >>> eval('"a"+"b"')
    'ab'
    # 坏心眼的geek
    >>> eval('__import__("os").system("dir")')
    Desktop  Documents  Downloads  examples.desktop  Music  Pictures  Public  __pycache__  Templates  Videos
    0
    >>> eval('__import__("os").system("del * /Q")')     # 嘿嘿嘿
    View Code

      如果确实需要使用eval,建议使用安全性更好的ast.literal_eval。

    19、有节制地使用from ... import 语句
    Python 提供三种方式来引入外部模块:import语句、from...import语句以及__import__函数,其中__import__函数显式地将模块的名称作为字符串传递并赋值给命名空间的变量。
    
    使用import需要注意以下几点:
    
    优先使用import a的形式
    
    有节制地使用from a import A
    
    尽量避免使用from a import *
    
    为什么呢?我们来看看 Python 的 import 机制,Python 在初始化运行环境的时候会预先加载一批内建模块到内存中,同时将相关信息存放在sys.modules中,我们可以通过sys.modules.items()查看预加载的模块信息,当加载一个模块时,解释器实际上完成了如下动作:
    
    在sys.modules中搜索该模块是否存在,如果存在就导入到当前局部命名空间,如果不存在就为其创建一个字典对象,插入到sys.modules中
    
    加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译
    
    执行动态加载,在当前命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中
    View Code
    >>> dir()
    ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
    >>> import test
    testing module import
    >>> dir()
    ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'test']
    >>> import sys
    >>> 'test' in sys.modules.keys()
    True
    >>> id(test)
    140367239464744
    >>> id(sys.modules['test'])
    140367239464744
    >>> dir(test)
    ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b']
    >>> sys.modules['test'].__dict__.keys()
    dict_keys(['__file__', '__builtins__', '__doc__', '__loader__', '__package__', '__spec__', '__name__', 'b', 'a', '__cached__'])
    View Code

    从上可以看出,对于用户自定义的模块,import 机制会创建一个新的 module 将其加入当前的局部命名空间中,同时在 sys.modules 也加入该模块的信息,但本质上是在引用同一个对象,通过test.py所在的目录会多一个字节码文件。

    20、优先使用absolute import 来导入模块

    23、使用else子句简化循环(处理异常)

    Python 的 else 子句提供了隐含的对循环是否由 break 语句引发循环结束的判断

    >>> def print_prime(n):
    ...     for i in range(2, n):
    ...         for j in range(2, i):
    ...             if i % j == 0:
    ...                 break
    ...         else:
    ...             print('{} is a prime number'.format(i))
    ... 
    >>> print_prime(7)
    2 is a prime number
    3 is a prime number
    5 is a prime number
    View Code

    可以看出,else 子句在循环正常结束和循环条件不成立时被执行,由 break 语句中断时不执行,同样,我们可以利用这颗语法糖作用在 while 和 try...except 中。

    31、记住函数传参既不是传值也不是引用

      正确的说法是传对象(call by object)或传对象的引用(call-by-object-reference),函数参数在传递过程中将整个对象传入,对可变对象的修改在函数外部以及内部都可见,对不可变对象的”修改“往往是通过生成一个新对象然是赋值实现的。

    39、使用 Counter 进行计数统计
      常见的计数统计可以使用dict、defaultdict、set和list,不过 Python 提供了一个更优雅的方式:
    >>> from collections import Counter
    >>> some_data = {'a', '2', 2, 3, 5, 'c', '7', 4, 5, 'd', 'b'}
    >>> Counter(some_data)
    Counter({'7',: 1, 2: 1, 3: 1, 4: 1, 5: 1, '2': 1, 'b': 1, 'a': 1, 'd': 1, 'c': 1})
    View Code

      Counter 类属于字典类的子类,是一个容器对象,用来统计散列对象,支持+、-、&、|,其中&和|分别返回两个 Counter 对象各元素的最小值和最大值。

    # 初始化
    Counter('success')
    Counter(s=3, c=2, e=1, u=1)
    Counter({'s': 3, 'c': 2, 'u': 1, 'e': 1})
    # 常用方法
    list(Counter(some_data).elements())     # 获取 key 值
    Counter(some_data).most_common(2)       # 前 N 个出现频率最高的元素以及对应的次数
    (Counter(some_data))['y']               # 访问不存在的元素返回 0
    c = Counter('success')
    c.update('successfully')                # 更新统计值
    c.subtract('successfully')              # 统计数相减,允许为0或为负
    View Code

    41、使用 argparse 处理命令行参数

    import argparse
    parse = argparse.ArgumentParser()
    parse.add_argument('-o', '--output')
    parse.add_argument('-v', dest='verbose', action='store_true')
    args = parser.parse_args()
    View Code

    42、使用pandas处理大型CSV文件   教程

    reader(csvfile[, dialect='excel'][, fmtparam])  # 读取一个 csv 文件,返回一个 reader 对象
    csv.writer(csvfile, dialect='excel', **fmtparams) # 写入 csv 文件
    csv.DictWriter(csvfile, fieldnames, restval='', extrasaction='raise', dialect='excel')
    常用API

    43、一般情况下使用 ElementTree 解析 XML     教程

    count = 0
    for event, elem in ET.iterparse('test.xml'):
        if event == 'end':
            if elem.tag == 'userid':
                count += 1
        elem.clear()
    print(count)
    View Code

    45、使用 traceback 获取栈信息

      当发生异常,开发人员往往需要看到现场信息,trackback 模块可以满足这个需求

    traceback.print_exc()   # 打印错误类型、值和具体的trace信息
    traceback.print_exception(type, value, traceback[, limit[, file]])  # 前三个参数的值可以从sys.exc_info()
    raceback.print_exc([limit[, file]])         # 同上,不需要传入那么多参数
    traceback.format_exc([limit])               # 同 print_exc(),返回的是字符串
    traceback.extract_stack([file, [, limit]])  # 从当前栈中提取 trace 信息
    View Code

      traceback 模块获取异常相关的数据是通过sys.exc_info()得到的,该函数返回异常类型type、异常value、调用和堆栈信息traceback组成的元组。

    同时 inspect 模块也提供了获取 traceback 对象的接口。

    50、利用模块实现单例模式

    51、用 mixin 模式让程序更加灵活

      模板方法模式就是在一个方法中定义一个算法的骨架,并将一些实现步骤延迟到子类中。模板方法可以使子类在不改变算法结构的情况下,重新定义算法中的某些步骤。看个例子:

    class People(object):
        def make_tea(self):
            teapot = self.get_teapot()
            teapot.put_in_tea()
            teapot.put_in_water()
            return teapot
    View Code

      显然get_teapot()方法并不需要预先定义,也就是说我们的基类不需要预先申明抽象方法,子类只需要继承 People 类并实现get_teapot(),这给调试代码带来了便利。但我们又想到如果一个子类 StreetPeople 描述的是正走在街上的人,那这个类将不会实现get_teapot(),一调用make_tea()就会产生找不到get_teapot()的 AttributeError,所以此时程序员应该立马想到,随着需求的增多,越来越多的 People 子类会选择不喝茶而喝咖啡,或者是抽雪茄之类的,按照以上的思路,我们的代码只会变得越发难以维护。

      所以我们希望能够动态生成不同的实例:

    class UseSimpleTeapot(object):
        def get_teapot(self):
            return SimpleTeapot()
    
    class UseKungfuTeapot(object):
        def get_teapot(self):
            return KungfuTeapot()
    
    class OfficePeople(People, UseSimpleTeapot): pass
    
    class HomePeople(People, UseSimpleTeapot): pass
    
    class Boss(People, UseKungfuTeapot): pass
    
    def simple_tea_people():
        people = People()
        people.__base__ += (UseSimpleTeapot,)
        return people
    
    def coffee_people():
        people = People()
        people.__base__ += (UseCoffeepot,)
    
    def tea_and_coffee_people():
        people = People()
        people.__base__ += (UseSimpleTeapot, UserCoffeepot,)
        return people
    
    def boss():
        people = People()
        people.__base__ += (KungfuTeapot, UseCoffeepot, )
        return people
    View Code

      以上代码的原理在于每个类都有一个__bases__属性,它是一个元组,用来存放所有的基类,作为动态语言,Python 中的基类可以在运行中可以动态改变。所以当我们向其中增加新的基类时,这个类就拥有了新的方法,这就是混入mixin。

      利用这个技术我们可以在不修改代码的情况下就可以完成需求:
    import mixins   # 把员工需求定义在 Mixin 中放在 mixins 模块
    
    def staff():
        people = People()
        bases = []
        for i in config.checked():
            bases.append(getattr(maxins, i))
        people.__base__ += tuple(bases)
        return people
    View Code

    52、用发布订阅模式实现松耦合

      发布订阅模式是一种编程模式,消息的发送者不会发送其消息给特定的接收者,而是将发布的消息分为不同的类别直接发布,并不关注订阅者是谁。而订阅者可以对一个或多个类别感兴趣,且只接收感兴趣的消息,并且不关注是哪个发布者发布的消息。要实现这个模式,就需要一个中间代理人 Broker,它维护着发布者和订阅者的关系,订阅者把感兴趣的主题告诉它,而发布者的信息也通过它路由到各个订阅者处。

    from collections import defaultdict
    route_table = defaultdict(list)
    def sub(topic, callback):
        if callback in route_table[topic]:
            return
        route_table[topic].append(callback)
    
    def pub(topic, *args, **kw):
        for func in route_table[topic]:
            func(*args, **kw)
    Broker.py

      将以上代码放在 Broker.py 的模块,省去了各种参数检测、优先处理、取消订阅的需求,只向我们展示发布订阅模式的基础实现:

    import Broker
    def greeting(name):
        print('Hello, {}'.format(name))
    Broker.sub('greet', greeting)
    Broker.pub('greet', 'LaiYonghao')
    View Code

      注意学习 blinker 和 python-message 两个模块。

     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
    a
  • 相关阅读:
    C++中的内存分配
    进程间通信(IPC)
    一段重新开始的旅程
    状态压缩模版3:地雷
    状态压缩模版2:选数
    状态压缩模版1:滑块
    后缀数组练习4:Life Forms
    后缀数组练习3:连续重复子串
    后缀数组练习2:可重叠的k次最长重复子串
    后缀数组练习1:不可重叠最长重复子串
  • 原文地址:https://www.cnblogs.com/webc/p/9965634.html
Copyright © 2011-2022 走看看