美文网首页
改善 Python 程序的一些建议

改善 Python 程序的一些建议

作者: adc9c8f3920d | 来源:发表于2017-05-22 17:06 被阅读39次

    建议 12:不推荐使用 type 来进行类型检查

    作为动态语言,Python 解释器会在运行时自动进行类型检查并根据需要进行隐式类型转换,当变量类型不同而两者之间又不能进行隐式类型转换时便抛出TypeError异常。

    >>> def add(a, b):

    ...     return a + b

    ...

    >>> add(1, 2j)

    (1+2j)

    >>> add('a', 'b')

    'ab'

    >>> add(1, 2)

    3

    >>> add(1.0, 2.3)

    3.3

    >>> add([1, 2], [3, 4])

    [1, 2, 3, 4]

    >>> add(1, 'a')

    Traceback (most recent call last):

    File "", line 1, in 

    File "", line 2, in add

    TypeError: unsupported operand type(s) for +: 'int' and 'str'

    所以实际应用中,我们常常需要进行类型检查,但是不推荐使用type(),因为基于内建类型扩展的用户自定义类型,type()并不能准确返回结果:

    class UserInt(int):

    def __init__(self, val=0):

    self._val = int(val)

    def __add__(self, val):

    if isinstance(val, UserInt):

    return UserInt(self._val + val._val)

    return self._val + val

    def __iadd__(self, val):

    raise NotImplementedError("not support operation")

    def __str__(self):

    return str(self._val)

    def __repr__(self):

    return "Integer %s" % self._val

    >>> n = UserInt()

    >>> n

    Integer 0

    >>> print(n)

    0

    >>> m = UserInt(2)

    >>> print(m)

    2

    >>> type(n) is int

    False                   # 显然不合理

    >>> isinstance(n, int)

    True

    我们可以使用isinstance来检查:isinstance(object, classinfo)

    建议 13:尽量转换为浮点类型后再做除法

    # 计算平均成绩绩点

    >>> gpa = ((4*96+3*85+5*98+2*70)*4) / ((4+3+5+2)*100)

    >>> gpa

    3.625714285714286   # 终于知道自己的绩点是咋算的了

    建议 14:警惕 eval() 的安全漏洞

    eval(expression[, globals[, locals]])将字符串 str 当成有效的表达式来求值并返回计算结果,globas为字典形式,locals为任何映射对象,它们分别表示全局和局部命名空间,两者都省略表达式将在调用的环境中执行,为什么需要警惕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")')     # 嘿嘿嘿

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

    建议 15:使用 enumerate() 获取序列迭代的索引和值

    >>> li = ['a', 'b', 'c', 'd', 'e']

    >>> for i, e in enumerate(li):

    ...     print('index: ', i, 'element: ', e)

    ...

    index:  0 element:  a

    index:  1 element:  b

    index:  2 element:  c

    index:  3 element:  d

    index:  4 element:  e

    # enumerate(squence, start=0) 内部实现

    def enumerate(squence, start=0):

    n = start

    for elem in sequence:

    yield n, elem   # 666

    n += 1

    # 明白了原理我们自己也来实现一个反序的

    def reversed_enumerate(squence):

    n = -1

    for elem in reversed(sequence):

    yield len(sequence) + n, elem

    n -= 1

    建议 16:分清 == 与 is 的适用场景

    操作符意义isobject identity==equal

    is的作用是用来检查对象的标示符是否一致,也就是比较两个对象在内存中是否拥有同一块内存空间,相当于id(x) == id(y),它并不适用于判断两个字符串是否相等。==才是用来判断两个对象的值是否相等,实际是调用了内部的__eq__,所以a==b相当于a.__eq__(b),也就是说==是可以被重载的,而is不能被重载。

    >>> s1 = 'hello world'

    >>> s2 = 'hello world'

    >>> s1 == s2

    True

    >>> s1 is s2

    False

    >>> s1.__eq__(s2)

    True

    >>> a = 'Hi'

    >>> b = 'Hi'

    >>> a == b

    True

    >>> a is b

    True

    咦~怎么上例中的a, b又是“同一对象”了?这跟 Python 的 string interning 机制有关,为了提高系统性能,对于较小的字符串会保留其值的一个副本,当创建新的字符串时直接指向该副本,所以a和b的 id 值是一样的,同样对于小整数[-5, 257)也是如此:

    >>> id(a)

    140709793837832

    >>> id(b)

    140709793837832

    >>> x = -5

    >>> y = -5

    >>> x is y

    True

    >>> id(x) == id(y)

    True

    建议 17:考虑兼容性,尽可能使用 Unicode

    我之前也总结过编码的问题。由于最早的编码是 ASCII 码,只能表示 128 个字符,显然这对其它语言编码并不适用,Unicode就是为了不同的文字分配一套统一的编码。

    建议 18:构建合理的包层次来管理 module

    本质上每一个 Python 文件都是一个模块,使用模块可以增强代码的可维护性和可重用性,在较大的项目中,我们需要合理地组织项目层次来管理模块,这就是包(Package)的作用。

    一句话说包:一个包含__init__.py 文件的目录。包中的模块可以通过.进行访问,即包名.模块名。那么这个__init__.py文件有什么用呢?最明显的作用就是它区分了包和普通目录,在该文件中申明模块级别的 import 语句从而变成了包级别可见,另外在该文件中定义__all__变量,可以控制需要导入的子包或模块。

    这里给出一个较为合理的包组织方式,是FlaskWeb 开发:基于Python的Web应用开发实战一书中推荐而来的:

    |-flasky

    |-app/                      # Flask 程序

    |-templates/            # 存放模板

    |-static/               # 静态文件资源

    |-main/

    |-__init__.py

    |-errors.py         # 蓝本中的错误处理程序

    |-forms.py          # 表单对象

    |-views.py          # 蓝本中定义的程序路由

    |-__init__.py

    |-email.py              # 电子邮件支持

    |-models.py             # 数据库模型

    |-migrations/               # 数据库迁移脚本

    |-tests/                    # 单元测试

    |-__init__.py

    |-test*.py

    |-venv/                     # 虚拟环境

    |-requirements/

    |-dev.txt               # 开发过程中的依赖包

    |-prod.txt              # 生产过程中的依赖包

    |-config.py                 # 储存程序配置

    |-manage.py                 # 启动程序以及其他的程序任务

    第 3 章:基础语法

    建议 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中

    加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译

    执行动态加载,在当前命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中

    >>> 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__'])

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

    建议 20:优先使用 absolute import 来导入模块

    建议 21: i+=1 不等于 ++i

    首先++i或--i在 Python 语法上是合法,但并不是我们通常理解的自增或自减操作:

    >>> ++1     # +(+1)

    1

    >>> --1     # -(-1)

    1

    >>> +++2

    2

    >>> ---2

    -2

    原来+或-只表示正负数符号。

    建议 22:使用 with 自动关闭资源

    对于打开的资源我们记得关闭它,如文件、数据库连接等,Python 提供了一种简单优雅的解决方案:with。

    先来看with实现的原理吧。

    with的实现得益于一个称为上下文管理器(context manager)的东西,它定义程序运行时需要建立的上下文,处理程序的进入和退出,实现了上下文管理协议,即对象中定义了__enter__()和__exit__(),任何实现了上下文协议的对象都可以称为一个上下文管理器:

    __enter__():返回运行时上下文相关的对象

    __exit__(exception_type, exception_value, traceback):退出运行时的上下文,处理异常、清理现场等

    包含with语句的代码块执行过程如下:

    with 表达式 [as 目标]:

    代码块

    # 例

    >>> with open('test.txt', 'w') as f:

    ...     f.write('test')

    ...

    4

    >>> f.__enter__

    >>> f.__exit__

    计算表达式的值,返回一个上下文管理器对象

    加载上下文管理器对象的__exit__()以备后用

    调用上下文管理器对象的__enter__()

    将__enter__()的返回值赋给目标对象

    执行代码块,正常结束调用__exit__(),其返回值直接忽略,如果发生异常,会调用__exit__()并将异常类型、值及 traceback 作为参数传递给__exit__(),__exit__()返回值为 false 异常将会重新抛出,返回值为 true 异常将被挂起,程序继续执行

    于此,我们可以自定义一个上下文管理器:

    >>> class MyContextManager(object):

    ...     def __enter__(self):

    ...         print('entering...')

    ...     def __exit__(self, exception_type, exception_value, traceback):

    ...         print('leaving...')

    ...         if exception_type is None:

    ...             print('no exceptions!')

    ...             return False

    ...         elif exception_type is ValueError:

    ...             print('value error!')

    ...             return True

    ...         else:

    ...             print('other error')

    ...             return True

    ...

    >>> with MyContextManager():

    ...     print('Testing...')

    ...

    entering...

    Testing...

    leaving...

    no exceptions!

    >>> with MyContextManager():

    ...     print('Testing...')

    ...     raise(ValueError)

    ...

    entering...

    Testing...

    leaving...

    value error!

    Python 还提供contextlib模块,通过 Generator 实现,其中的 contextmanager 作为装饰器来提供一种针对函数级别上的上下文管理器,可以直接作用于函数/对象而不必关心__enter__()和__exit__()的实现。

    推荐文章

    建议 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

    可以看出,else 子句在循环正常结束和循环条件不成立时被执行,由 break 语句中断时不执行,同样,我们可以利用这颗语法糖作用在 while 和 try...except 中。学好python你需要一个良好的环境,一个优质的开发交流群,群里都是那种相互帮助的人才是可以的,我有建立一个python学习交流群,在群里我们相互帮助,相互关心,相互分享内容,这样出问题帮助你的人就比较多,群号是301,还有056,最后是051,这样就可以找到大神聚合的群,如果你只愿意别人帮助你,不愿意分享或者帮助别人,那就请不要加了,你把你会的告诉别人这是一种分享。

    相关文章

      网友评论

          本文标题:改善 Python 程序的一些建议

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