Skip to content

Latest commit

 

History

History
817 lines (545 loc) · 33.6 KB

File metadata and controls

817 lines (545 loc) · 33.6 KB

四、选择好名字

大多数标准库的构建都考虑了可用性。例如,使用内置类型是自然完成的,并且设计为易于使用。在本例中,可以将 Python 与您在处理程序时可能想到的伪代码进行比较。大部分代码都可以大声读出。例如,任何人都应该能理解这个片段:

my_list = []

if 'd' not in my_list:
    my_list.append('d')

这就是为什么与其他语言相比,编写 Python 如此容易的原因之一。当你在编写一个程序时,你的思想流会很快转化为一行行代码。

本章重点介绍通过以下方式编写易于理解和使用的代码的最佳实践:

  • PEP 8中描述的命名约定的使用
  • 命名最佳实践集
  • 允许您检查是否符合样式指南的流行工具的简短摘要

政治公众人物 8 和命名最佳实践

政治公众人物 8(http://www.python.org/dev/peps/pep-0008 提供了编写 Python 代码的风格指南。除了一些基本规则,如空格缩进、最大行长和其他有关代码布局的细节外,PEP 8 还提供了一节关于大多数代码库遵循的命名约定的内容。

本节简要介绍了该 PEP,并为每种元素添加了命名最佳实践指南。你仍然应该认为 PEP 8 文档是强制性的。

为什么以及何时遵循政治公众人物 8?

如果您正在创建一个新的打算开源的软件包,那么答案很简单:总是。PEP8 实际上是 Python 中大多数开源软件的标准代码样式。如果您想接受其他程序员的任何协作,那么您肯定应该坚持 PEP8,即使您对最佳代码风格指南有不同的看法。这样做的好处是让其他开发人员更容易直接进入您的项目。对于新手来说,代码将更容易阅读,因为它与大多数其他 Python 开源软件包的风格是一致的。

此外,从完全遵守 PEP 8 开始,您可以在将来节省时间和麻烦。如果你想向公众发布你的代码,你最终会面临其他程序员的建议,要求你改用 PEP8。关于是否真的有必要为某一特定项目这样做的争论往往是永无休止的不可能获胜的火焰之战。这是一个悲哀的事实,但为了不失去贡献者,你最终可能会被迫遵守本风格指南。

此外,如果整个项目的代码库处于成熟的开发状态,则重新对其进行排序可能需要大量的工作。在某些情况下,这种重新排序可能需要更改几乎每一行代码。虽然大多数更改可以自动化(缩进、换行和尾随空格),但这种大规模的代码大修通常会在每个基于分支的版本控制工作流中引入大量冲突。同时要审查这么多的变化也是非常困难的。这就是为什么许多开源项目都有一条规则,即样式修复更改应始终包含在单独的拉/合并请求或补丁中,而这些请求或补丁不会影响任何功能或 bug。

超越 PEP 8–团队特定风格指南

尽管提供了一套全面的风格指南,PEP8 仍然给开发者留下了一些自由。特别是在嵌套数据文本和需要长参数列表的多行函数调用方面。一些团队可能会决定他们需要额外的样式规则,最好的选择是将它们形式化为某种文档,每个团队成员都可以使用。

此外,在某些情况下,在一些没有定义样式指南的旧项目中,严格遵守 PEP 8 可能是不可能的,或者在经济上是不可行的。这些项目仍将受益于实际编码约定的形式化,即使它们没有反映 PEP 8 规则的正式集合。记住,比与 PEP 8 保持一致更重要的是项目内部的一致性。如果规则是形式化的,并且可以作为每个程序员的参考,那么在项目和组织中保持一致性就更容易了。

命名风格

Python 中使用的不同命名样式有:

  • 驼峰命名法
  • 混合病例
  • 大写字母和带下划线的大写字母
  • 带下划线的小写和小写字母
  • _ 前导和尾随下划线,有时双下划线

小写和大写元素通常是一个单词,有时几个单词连在一起。带下划线的短语通常是缩写短语。使用一个单词更好。前导和尾随下划线用于标记隐私和特殊元素。

这些样式应用于:

  • 变量
  • 功能和方法
  • 性质
  • 班级
  • 模块
  • 包装

变量

Python 中有两种类型的变量:

  • 常数
  • 公共和私人变量

常数

对于常量全局变量,使用带下划线的大写字母。它通知开发人员给定的变量表示一个常量值。

在 Python 中没有真正的常数,如 C++中的,可以使用您可以更改任何变量的值。这就是 Python 使用命名约定将变量标记为常量的原因。

例如,doctest模块提供选项标志和指令的列表(https://docs.python.org/2/library/doctest.html )这些都是小句子,清楚地定义了每个选项的用途:

from doctest import IGNORE_EXCEPTION_DETAIL
from doctest import REPORT_ONLY_FIRST_FAILURE

这些变量名看起来相当长,但清楚地描述它们很重要。它们的用法主要是在初始化代码中,而不是在代码本身中,因此这种冗长并不烦人。

缩写名称在大多数情况下会使代码混淆。当缩写看起来不清楚时,不要害怕使用完整的单词。

一些常量的名称也受底层技术的驱动。例如,os模块使用一些在 C 端定义的常量,例如定义 Unix 出口代码号的EX_XXX系列。可以在系统的sysexits.hC 头文件中找到相同的名称代码:

import os
import sys

sys.exit(os.EX_SOFTWARE)

使用常量时的另一个良好做法是将它们收集在使用它们的模块顶部,并在用于此类操作时将它们合并到新变量下:

import doctest
TEST_OPTIONS = (doctest.ELLIPSIS |
                doctest.NORMALIZE_WHITESPACE | 
                doctest.REPORT_ONLY_FIRST_FAILURE)

命名和用法

常量用于定义程序依赖的一组值,如默认配置文件名。

一个好的做法是在包中的单个文件中收集所有常量。例如,Django 就是这样工作的。名为settings.py的模块提供了所有常量:

# config.py
SQL_USER = 'tarek'
SQL_PASSWORD = 'secret'
SQL_URI = 'postgres://%s:%s@localhost/db' % (
    SQL_USER, SQL_PASSWORD
)
MAX_THREADS = 4

另一种方法是使用可通过ConfigParser模块解析的配置文件,或者使用ZConfig等高级工具,这是 Zope 中用于描述其配置文件的解析器。但有些人认为,在 Python 这样的语言中使用另一种文件格式是一种过分的做法,在这种语言中,文件可以像文本文件一样轻松地编辑和更改。

对于类似于标志的选项,通常的做法是将它们与布尔运算相结合,就像doctestre模块所做的那样。取自doctest的模式非常简单:

OPTIONS = {}

def register_option(name):
    return OPTIONS.setdefault(name, 1 << len(OPTIONS))

def has_option(options, name):
    return bool(options & name)

# now defining options
BLUE = register_option('BLUE')
RED = register_option('RED')
WHITE = register_option('WHITE')

您将获得:

>>> # let's try them
>>> SET = BLUE | RED
>>> has_option(SET, BLUE)
True
>>> has_option(SET, WHITE)
False

创建此类新的常量集时,避免使用通用前缀,除非模块有多个常量集。模块名称本身是一个通用前缀。另一个解决方案是使用内置enum模块中的Enum类,只需依赖set集合而不是二进制运算符。不幸的是,Enum类在针对旧 Python 版本的代码中的应用有限,因为enum模块是在 Python 3.4 版本中提供的。

使用二进制位操作组合选项在 Python 中很常见。inclusive OR(|运算符将允许您在单个整数中组合多个选项,and(&运算符将允许您检查整数中是否存在该选项(请参阅has_option函数)。

公共和私人变量

对于通过导入可自由使用且可变的全局变量,当需要保护时,应使用带下划线的小写字母。但是这些类型的变量并不经常使用,因为模块通常提供 getter 和 setter 在需要保护时使用它们。在这种情况下,前导下划线可以将变量标记为包的私有元素:

_observers = []

def add_observer(observer):
    _observers.append(observer)

def get_observers():
    """Makes sure _observers cannot be modified."""
    return tuple(_observers)

位于函数和方法中的变量遵循相同的规则,并且从不标记为私有,因为它们是上下文的局部变量。

对于类或实例变量,只有在使变量成为公共签名的一部分不会带来任何有用信息或是多余的情况下,才必须使用私有标记(前导下划线)。

换句话说,如果变量在方法内部使用以提供公共特性,并且专用于此角色,则最好将其设置为私有。

例如,为财产提供动力的属性是良好的私人公民:

class Citizen(object):
    def __init__(self):
        self._message = 'Rosebud...'

    def _get_message(self):
        return self._message

    kane = property(_get_message)

另一个例子是一个保持内部状态的变量。此值对于代码的其余部分不有用,但会参与类的行为:

class UnforgivingElephant(object):
    def __init__(self, name):
        self.name = name
        self._people_to_stomp_on = []

    def get_slapped_by(self, name):
        self._people_to_stomp_on.append(name)
        print('Ouch!')

    def revenge(self):
        print('10 years later...')
        for person in self._people_to_stomp_on:
            print('%s stomps on %s' % (self.name, person))

以下是您将在交互式会话中看到的内容:

>>> joe = UnforgivingElephant('Joe')
>>> joe.get_slapped_by('Tarek')
Ouch!
>>> joe.get_slapped_by('Bill')
Ouch!
>>> joe.revenge()
10 years later...
Joe stomps on Tarek
Joe stomps on Bill

功能与方法

函数和方法应使用带下划线的小写字母。在旧的标准库模块中,这条规则并不总是正确的。Python3 对标准库进行了大量的重新组织,因此它的大多数函数和方法都具有一致的情况。尽管如此,对于像threading这样的一些模块,您可以访问使用mixedCase的旧函数名(例如currentThread。这样做是为了更容易实现向后兼容性,但是如果不需要在较旧版本的 Python 中运行代码,那么应该避免使用这些旧名称。

这种书写方法的方式在小写规范成为标准之前很常见,一些框架,如 Zope 和 Twisted,也使用mixedCase作为方法。与他们合作的开发人员社区仍然很大。因此,mixedCase和带下划线的小写字母之间的选择显然是由您使用的库决定的。

作为 Zope 开发人员,要保持一致性并不容易,因为构建一个混合纯 Python 模块和导入 Zope 代码的模块的应用程序是困难的。在 Zope 中,一些类混合了这两种约定,因为代码库仍在发展中,Zope 开发人员试图采用许多人接受的通用约定。

在这种库环境中,一个不错的做法是只对框架中公开的元素使用mixedCase,并将其余代码保持为 PEP 8 样式。

值得注意的是,Twisted 项目的开发人员对这个问题采取了完全不同的方法。Twisted 项目与 Zope 相同,早于 PEP8 文档。它是在没有关于代码风格的官方指导方针的时候开始的,所以它有自己的风格。有关缩进、文档字符串、行长度等的文体规则可以很容易地采用。另一方面,更新所有代码以匹配 PEP8 中的命名约定将导致完全破坏向后兼容性。对于像 Twisted 这样的大型项目,这样做是不可行的。因此 Twisted 尽可能多地采用了 PEP8,并将变量、函数和方法的mixedCase作为其自身编码标准的一部分。这完全符合 PEP 8 的建议,因为它特别指出,项目内的一致性比 PEP 8 风格指南的一致性更重要。

私人纠纷

对于私有方法和函数,通常会添加一个前导下划线。这条规则非常有争议,因为 Python 中的名称混乱特性。当一个方法有两个前导下划线时,解释器会动态重命名它,以防止与任何子类中的方法发生名称冲突。

因此,有些人倾向于对其私有属性使用双前导下划线,以避免子类中的名称冲突:

class Base(object):
    def __secret(self):
        print("don't tell")

    def public(self):
        self.__secret()

class Derived(Base):
    def __secret(self):
        print("never ever")

你会看到:

>>> Base.__secret
Traceback (most recent call last):
 File "<input>", line 1, in <module>
AttributeError: type object 'Base' has no attribute '__secret'
>>> dir(Base)
['_Base__secret', ..., 'public']
>>> Derived().public()
don't tell

在 Python 中命名名字的最初动机不是提供一种私密的伎俩,比如 C++,但是要确保一些基类隐含地避免子类中的冲突,尤其是在多继承上下文中。但是对每个属性使用它会使代码在私有空间中变得模糊,这根本不是 Pythonic。

因此,一些人认为,应始终使用明确的名称 mangling:

class Base:
    def _Base_secret(self):  # don't do this !!!
        print("you told it ?")

这会在整个代码中重复类名,因此应首选__

但最佳实践,如BDFL(Guido,仁慈的终身独裁者,参见http://en.wikipedia.org/wiki/BDFL 表示,在子类中编写方法之前,通过查看类的__mro__(方法解析顺序)值,避免使用名称混乱。更改基类私有方法必须小心。

关于这个主题的更多信息,许多年前 Python 开发人员邮件列表中出现了一条有趣的线索,人们在那里争论名称混乱的效用及其在语言中的命运。可在找到 http://mail.python.org/pipermail/python-dev/2005-December/058555.html

特殊方法

特殊方法(https://docs.python.org/3/reference/datamodel.html#special-方法名以双下划线开始和结束,正常方法不应使用此约定。一些开发人员过去将它们称为dunder方法,作为双下划线的 portmanteau。它们用于运算符重载、容器定义等。为了便于阅读,应在类定义开始时收集:

class WeirdInt(int):
    def __add__(self, other):
        return int.__add__(self, other) + 1

    def __repr__(self):
        return '<weirdo %d>' % self

    # public API
    def do_this(self):
        print('this')

    def do_that(self):
        print('that')

对于普通方法,永远不要使用这些类型的名称。因此,不要为这样的方法发明名称:

class BadHabits:
    def __my_method__(self):
        print('ok')

论点

参数是小写的,需要时加下划线。它们遵循与变量相同的命名规则。

性质

属性的名称为小写,或小写加下划线。大多数情况下,它们代表一个对象的状态,可以是名词或形容词,必要时也可以是一个小短语:

class Connection:
    _connected = []

    def connect(self, user):
        self._connected.append(user)

    @property

    def connected_people(self):
        return ', '.join(self._connected)

在交互式会话上运行时:

>>> connection = Connection()
>>> connection.connect('Tarek')
>>> connection.connect('Shannon')
>>> print(connection.connected_people)
Tarek, Shannon

课程

类的名称始终为 CamelCase,当它们是模块专用的时,可能会有一个前导下划线。

类和实例变量通常是名词短语,并使用动词短语的方法名称形成使用逻辑:

class Database:
    def open(self):
        pass

class User:
    pass

以下是交互式会话中的示例用法:

>>> user = User()
>>> db = Database()
>>> db.open()

模块和包装

除特殊模块__init__外,模块名称为小写,无下划线。

以下是标准库中的一些示例:

  • os
  • sys
  • shutil

当模块是包的专用模块时,会添加一个前导下划线。编译的 C 或 C++模块通常用下划线命名,并导入纯 Python 模块。

包名称遵循相同的规则,因为它们的行为类似于更结构化的模块。

命名指南

可以对变量、方法、函数和属性应用一组通用的命名规则。类和模块的名称在名称空间构造中也起着重要的作用,进而提高代码的可读性。本小指南提供了用于选择其名称的常见模式和反模式。

对布尔元素使用 has 或 is 前缀

当元素包含布尔值时,ishas前缀提供了一种自然的方式,使其在名称空间中更具可读性:

class DB:
    is_connected = False
    has_cache = False

对集合中的变量使用复数

当元素包含集合时,最好使用复数形式。当某些映射像序列一样公开时,它们也可以从中受益:

class DB:
    connected_users = ['Tarek']
    tables = {
        'Customer': ['id', 'first_name', 'last_name']
    }

为字典使用显式名称

当变量持有映射时,应尽可能使用显式名称。例如,如果dict包含一个人的地址,则可以将其命名为persons_addresses

persons_addresses = {'Bill': '6565 Monty Road', 
                     'Pamela': '45 Python street'}
persons_addresses['Pamela']
'45 Python street'

避免通用名称

如果您的代码没有构建新的抽象数据类型,那么即使对局部变量使用listdictsequenceelements等术语也是有害的。这使得代码难以阅读、理解和使用。还必须避免使用内置名称,以避免在当前命名空间中隐藏它。还应避免使用泛型动词,除非它们在名称空间中有意义。

相反,应使用特定于域的术语:

def compute(data):  # too generic
    for element in data:
        yield element ** 2

def squares(numbers):  # better
    for number in numbers:
        yield number ** 2

还有一个前缀和后缀列表,尽管这些前缀和后缀在编程中非常常见,但实际上应该在函数名和类名中避免使用:

  • 经理
  • 对象
  • 做、处理或执行

这样做的原因是它们模糊、不明确,并且不会给实际名称添加任何值。话语和堆栈溢出的联合创始人杰夫·阿特伍德(Jeff Atwood)在这个话题上有一篇非常好的文章,可以在他的博客上找到 http://blog.codinghorror.com/i-shall-call-it-somethingmanager/

还有一个应该避免使用的包名称列表。从长远来看,任何不提供任何关于内容线索的东西都会对项目造成很大危害。像misctoolsutilscommoncore这样的名称有一种非常强烈的趋势,即成为无数袋质量非常差的不相关代码片段,它们的大小似乎呈指数级增长。在大多数情况下,这样一个模块的存在是懒惰或缺乏足够设计努力的标志。这些模块名称的爱好者可以简单地预先预测未来,并将它们重命名为trashdumpster,因为这正是他们的队友最终对待这些模块的方式。

在大多数情况下,拥有更多的小模块几乎总是更好的,即使内容很少,但名称能够很好地反映内部内容。老实说,像utilscommon这样的名字没有本质上的错误,并且可以负责任地使用它们。但现实表明,在许多情况下,它们反而成为迅速扩散的危险结构反模式的存根。如果你行动不够快,你可能永远无法摆脱他们。因此,最好的方法就是避免这种有风险的组织模式,并在项目中工作的其他人引入时将其扼杀在萌芽状态。

避免使用现有名称

使用上下文中已经存在的名称是一种不好的做法,因为这会使阅读和调试变得非常混乱:

>>> def bad_citizen():
...     os = 1
...     import pdb; pdb.set_trace()
...     return os
...
>>> bad_citizen()
> <stdin>(4)bad_citizen()
(Pdb) os
1
(Pdb) import os
(Pdb) c
<module 'os' from '/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/os.pyc'>

在本例中,os名称被代码隐藏。应避免使用标准库中的内置名和模块名。

尝试创建原始名称,即使它们是上下文的本地名称。对于关键字,尾随下划线是避免冲突的一种方法:

def xapian_query(terms, or_=True):
    """if or_ is true, terms are combined with the OR clause"""
    ...

注意,class经常被klasscls替换:

def factory(klass, *args, **kwargs):
    return klass(*args, **kwargs)

争论的最佳实践

函数和方法的签名是代码完整性的守护者。他们驱动它的使用并构建它的 API。除了我们前面看到的命名规则外,还必须特别注意参数。这可以通过三个简单的规则来实现:

  • 通过迭代设计构建参数
  • 相信这些参数和测试
  • 小心使用*args**kwargs魔术参数

通过迭代设计构建参数

每个函数都有一个固定的和定义良好的参数列表,这使得代码更加健壮。但这不能在第一个版本中完成,所以参数必须通过迭代设计来构建。它们应该反映创建元素的精确用例,并相应地发展。

例如,当附加某些参数时,它们应尽可能具有默认值,以避免任何回归:

class Service:  # version 1
    def _query(self, query, type):
        print('done')

    def execute(self, query):
        self._query(query, 'EXECUTE')

>>> Service().execute('my query')
done

import logging

class Service(object):  # version 2
    def _query(self, query, type, logger):
        logger('done')

    def execute(self, query, logger=logging.info):
        self._query(query, 'EXECUTE', logger)

>>> Service().execute('my query')    # old-style call
>>> Service().execute('my query', logging.warning)
WARNING:root:done

当必须更改公共元素的参数时,将使用弃用过程,这将在本节后面介绍。

相信你的论点和测试

鉴于 Python 的动态类型特性,一些开发人员在其函数和方法的顶部使用断言,以确保参数具有适当的内容:

def division(dividend, divisor):
    assert isinstance(dividend, (int, float))
    assert isinstance(divisor, (int, float))
    return dividend / divisor

>>> division(2, 4)
0.5
>>> division(2, None)
Traceback (most recent call last):
 File "<input>", line 1, in <module>
 File "<input>", line 3, in division
AssertionError

这通常是由习惯于静态类型的开发人员完成的,他们觉得 Python 中缺少了一些东西。

这种检查参数的方法是合同设计DbC,参见的一部分 http://en.wikipedia.org/wiki/Design_By_Contract )编程风格,在实际运行代码之前检查前提条件。

这种方法的两个主要问题是:

  • DbC 的代码解释了应该如何使用它,从而降低了它的可读性
  • 这会使它变得更慢,因为每次调用都会进行断言

后者可以通过解释器的"-O"选项避免。在这种情况下,在创建字节码之前,所有断言都将从代码中删除,因此检查将丢失。

在任何情况下,断言都必须小心完成,不应用于将 Python 转换为静态类型语言。唯一的用例是保护代码不被无意义地调用。

健康的测试驱动开发风格在大多数情况下提供了健壮的基础代码。在这里,功能测试和单元测试验证了为其创建代码的所有用例。

当库中的代码被外部元素使用时,做出断言可能很有用,因为传入的数据可能会破坏甚至造成损坏。处理数据库或文件系统的代码会发生这种情况。

另一种方法是模糊测试http://en.wikipedia.org/wiki/Fuzz_testing),将随机数据发送到程序以检测其弱点。当发现新的缺陷时,可以修复代码以处理该缺陷,并进行新的测试。

让我们注意遵循 TDD 方法的代码库朝着正确的方向发展,并且变得越来越健壮,因为每次出现新的故障时都会对其进行调优。当以正确的方式完成时,测试中的断言列表在某种程度上与前置条件列表类似。

小心使用*args 和**kwargs 魔术参数

*args**kwargs参数可能会破坏函数或方法的健壮性。它们使签名变得模糊,代码通常开始构建一个小参数解析器,它不应该:

def fuzzy_thing(**kwargs):

    if 'do_this' in kwargs:
        print('ok i did')

    if 'do_that' in kwargs:
        print('that is done')

    print('errr... ok')

>>> fuzzy_thing(do_this=1)
ok i did
errr... ok
>>> fuzzy_thing(do_that=1)
that is done
errr... ok
>>> fuzzy_thing(hahaha=1)
errr... ok

如果参数列表变得又长又复杂,那么很容易添加神奇的参数。但这更多的是一个弱函数或方法的迹象,应该分解或重构。

当使用*args处理函数中以相同方式处理的元素序列时,请求唯一的容器参数,例如iterator更好:

def sum(*args):  # okay
    total = 0
    for arg in args:
        total += arg
    return total

def sum(sequence):  # better!
    total = 0
    for arg in sequence:
        total += arg
    return total

对于**kwargs,同样的规则适用。最好修复命名参数,使方法的签名有意义:

def make_sentence(**kwargs):
    noun = kwargs.get('noun', 'Bill')
    verb = kwargs.get('verb', 'is')
    adj = kwargs.get('adjective', 'happy')
    return '%s %s %s' % (noun, verb, adj)

def make_sentence(noun='Bill', verb='is', adjective='happy'):
    return '%s %s %s' % (noun, verb, adjective)

另一个有趣的方法是创建一个容器类,该容器类将几个相关参数分组,以提供执行上下文。该结构不同于*args**kwargs,因为它可以提供在值范围内工作的内部构件,并且可以独立演化。将其用作参数的代码不必处理其内部。

例如,传递给函数的 web 请求通常由类的实例表示。此类负责保存 web 服务器传递的数据:

def log_request(request):  # version 1
    print(request.get('HTTP_REFERER', 'No referer'))

def log_request(request):  # version 2
    print(request.get('HTTP_REFERER', 'No referer'))
    print(request.get('HTTP_HOST', 'No host'))

魔法参数有时是无法避免的,尤其是在元编程中。例如,在创建处理具有任何类型签名的函数的装饰器时,它们是不可或缺的。更全面地说,在处理刚刚遍历函数的未知数据的任何地方,神奇的参数都非常棒:

import logging

def log(**context):
    logging.info('Context is:\n%s\n' % str(context))

类名

类的名称必须简洁、准确,以便足以理解该类的功能。一种常见的做法是使用后缀来通知其类型或性质,例如:

  • SQL引擎
  • Mime类型
  • 字符串小部件
  • 测试案例

对于基类或抽象类,可以使用基类抽象类前缀,如下所示:

  • 饼干
  • 摘要格式化程序

最重要的是与类属性保持一致。例如,尝试避免类及其属性名称之间的冗余:

>>> SMTP.smtp_send()  # redundant information in the namespace
>>> SMTP.send()       # more readable and mnemonic 

模块和包名称

模块和包名称说明了其内容的用途。名称简短,小写,无下划线:

  • sqlite
  • postgres
  • sha1

如果他们正在实施协议,通常会在后面加上lib

import smtplib
import urllib
import telnetlib

它们还需要在名称空间内保持一致,以便更容易使用:

from widgets.stringwidgets import TextWidget  # bad
from widgets.strings import TextWidget        # better

同样,始终避免使用与标准库中某个模块相同的名称。

当一个模块变得复杂,并且包含很多类时,最好创建一个包并在其他模块中拆分模块的元素。

__init__模块也可用于将一些 API 放回顶层,因为它不会影响的使用,但有助于将代码重新组织成更小的部分。例如,考虑一个包含以下内容的 PosiT2A.包中的 Ty1 T1 模块:

from .module1 import feature1, feature2
from .module2 import feature3

这将允许用户直接导入功能,如下代码所示:

from foo import feature1, feature2, feature3

但是要注意,这会增加您获得循环依赖关系的机会,并且__init__ 模块中添加的代码将被实例化。所以要小心使用。

有用的工具

可以使用以下工具控制和制定以前的部分约定和实践:

  • Pylint:这是一个非常灵活的源代码分析器
  • pep8flake8:这是一个小代码样式检查器,以及一个包装器,它为其添加了一些更有用的特性,如静态分析和复杂性度量

派林

除了一些质量保证指标外,Pylint 还允许您检查给定的源代码是否遵循命名约定。其默认设置对应于 PEP 8,Pylint 脚本提供 shell 报告输出。

要安装 Pylint,您可以使用pip

$ pip install pylint

在此步骤之后,命令可用,并且可以使用通配符针对一个或多个模块运行。让我们在 Buildout 的bootstrap.py脚本上试试:

$ wget -O bootstrap.py https://bootstrap.pypa.io/bootstrap-buildout.py -q
$ pylint bootstrap.py
No config file found, using default configuration
************* Module bootstrap
C: 76, 0: Unnecessary parens after 'print' keyword (superfluous-parens)
C: 31, 0: Invalid constant name "tmpeggs" (invalid-name)
C: 33, 0: Invalid constant name "usage" (invalid-name)
C: 45, 0: Invalid constant name "parser" (invalid-name)
C: 74, 0: Invalid constant name "options" (invalid-name)
C: 74, 9: Invalid constant name "args" (invalid-name)
C: 84, 4: Import "from urllib.request import urlopen" should be placed at the top of the module (wrong-import-position)

...

Global evaluation
-----------------
Your code has been rated at 6.12/10

Real Pylint 的输出稍长,在这里被截断。

请注意,Pylint 可能会给您带来不好的费率或投诉。例如,模块本身的代码不使用的 import 语句在某些情况下是完美的(在名称空间中提供)。

对使用 mixedCase for 方法的库进行调用也会降低您的评级。在任何情况下,整体评估都不那么重要。Pylint 只是一个指出可能的改进的工具。

微调 Pylint 的第一件事是在 projects 目录中创建一个.pylinrc配置文件,使用–generate-rcfile选项:

$ pylint --generate-rcfile > .pylintrc

此配置文件是自我记录的(每个可能的选项都用注释描述),并且应该已经包含每个可用的配置选项。

除了检查是否符合某些任意编码标准外,Pylint 还可以提供有关总体代码质量的其他信息,如:

  • 代码复制度量
  • 未使用的变量和导入
  • 缺少函数、方法或类 docstring
  • 函数签名太长

默认情况下启用的可用检查列表非常长。重要的是要知道有些规则是任意的,不容易应用于每个代码库。记住,一致性总是比遵守某些武断的标准更有价值。幸运的是,Pylint 是非常可调的,因此,如果您的团队使用了一些与默认情况不同的命名和编码约定,您可以轻松地对其进行配置,以检查与这些约定的一致性。

pep8 和 FLAKE 8

pep8是一个只有一个目的的工具:它只提供与 PEP 8 中的代码约定相对应的样式检查。这是与 Pylint 的主要区别,Pylint 有许多附加功能。对于那些只对 PEP 8 标准的自动代码样式检查感兴趣的程序员来说,这个是最好的选择,而不需要任何额外的工具配置,就像派林的例子一样。

pep8可与pip一起安装:

$ pip install pep8

在构建的bootstrap.py脚本上运行时,它将给出一个简短的代码样式冲突列表:

$ wget -O bootstrap.py https://bootstrap.pypa.io/bootstrap-buildout.py -q
$ pep8 bootstrap.py
bootstrap.py:118:1: E402 module level import not at top of file
bootstrap.py:119:1: E402 module level import not at top of file
bootstrap.py:190:1: E402 module level import not at top of file
bootstrap.py:200:1: E402 module level import not at top of file

与 Pylint 输出的主要区别在于其长度。pep8只关注样式,因此不提供任何其他警告,如未使用的变量、太长的函数名或缺少 docstring。它也没有给出任何评级。这真的很有意义,因为不存在所谓的部分一致性。任何,甚至是最轻微的,违反风格准则的行为都会使代码立即变得不一致。

pep8的输出比 Pylint 的更简单,也更容易解析,因此如果您想将其与一些连续集成解决方案(如 Jenkins)集成,它可能是一个更好的选择。如果您缺少一些静态分析功能,则有flake8包是pep8上的包装器,其他一些工具很容易扩展,并提供更广泛的功能套件:

  • McCabe 复杂性度量
  • 通过pyflakes进行静态分析
  • 使用注释禁用整个文件或单行

总结

本章通过引用官方的 Python 风格指南(PEP8 文档)解释了最受欢迎的编码约定。官方的风格指南还补充了一些命名建议,这些建议将使您未来的代码更加明确,还有一些有用的工具,这些工具对于保持代码风格的一致性是必不可少的。

所有这些都让我们为本书的第一个实用主题——编写和分发软件包做好了准备。在下一章中,我们将学习如何在公共 PyPI 存储库上发布我们自己的包,以及如何利用私有组织中打包生态系统的力量。