构建Python包的五个简单准则简介

创建一个软件包(package)似乎已经足够简单了,也就是在文件目录下搜集一些模块,再加上一个__init__.py文件,对吧?我们很容易看出来,随着时间的推移,通过对软件包的越来越多的修改,一个设计很差的软件包可能会出现循环依赖问题,或是可能变得不可移植和不可靠。
1. __init__.py 仅为导入服务

对于一个简单的软件包,你可能会忍不住把工具方法,工厂方法和异常处理都丢进__init__.py,千万别这样!

一个结构良好的__init__.py文件,仅为一个非常重要的目的来服务:从子模块导入。你的__init__.py应该看起来像这个样子:

# ORDER MATTERS HERE -- SOME MODULES ARE DEPENDANT ON OTHERS
# 导入顺序要考虑——一些模块会依赖另外的一些
from exceptions import FSQError, FSQEnvError, FSQEncodeError,
            FSQTimeFmtError, FSQMalformedEntryError,
            FSQCoerceError, FSQEnqueueError, FSQConfigError,
            FSQPathError, FSQInstallError, FSQCannotLockError,
            FSQWorkItemError, FSQTTLExpiredError,
            FSQMaxTriesError, FSQScanError, FSQDownError,
            FSQDoneError, FSQFailError, FSQTriggerPullError,
            FSQHostsError, FSQReenqueueError, FSQPushError 

# constants relies on: exceptions, internal
import constants

# const relies on: constants, exceptions, internal
from const import const, set_const
# has tests

# path relies on: exceptions, constants, internal
import path
# has tests

# lists relies on: path
from lists import hosts, queues

#...

2.使用__init__.py来限制导入顺序

  • 把方法和类置于软件包的作用域中,这样用户就不需要深入软件包的内部结构,使你的软包变得易用。
  • 作为调和导入顺序的唯一地方。

使用得当的话,__init__.py 可以为你提供重新组织内部软件包结构的灵活性,而不需要担心由内部导入子模块或是每个模块导入顺序所带来的副作用。因为你是以一个特定的顺序导入子模块,你的__init__.py 对于他程序员来讲应该简单易懂,并且能够明显的表示该软件包所能提供的全部功能。

文档字符串,以及在软件包层面对__all__属性的赋值应当是__init__.py中唯一的与导入模块不相关的代码:

__all__ = [ 'FSQError', 'FSQEnvError', 'FSQEncodeError', 'FSQTimeFmtError',
      'FSQMalformedEntryError', 'FSQCoerceError', 'FSQEnqueueError',
      'FSQConfigError', 'FSQCannotLock', 'FSQWorkItemError',
      'FSQTTLExpiredError', 'FSQMaxTriesError', 'FSQScanError',
      'FSQDownError', 'FSQDoneError', 'FSQFailError', 'FSQInstallError',
      'FSQTriggerPullError', 'FSQCannotLockError', 'FSQPathError',
      'path', 'constants', 'const', 'set_const', 'down', 'up',

# ...
     ]

3.使用一个模块来定义所有的异常

你也许已经注意到了,__init__.py中的第一个导入语句从exceptions.py子模块中导入了全部的异常。从这里出发,你将看到,在大多数的软件包中,异常被定义在引起它们的代码附近。尽管这样可以为一个模块提供高度的完整性,一个足够复杂的软件包会通过如下两种方式,使得这一模式出现问题。

通常一个模块/程序需要从一个子模块导入一个函数, 利用它导入代码并抛出异常。为了捕获异常并保持一定的粒度,你需要导入你需要的模块,以及定义了异常的模块(或者更糟,你要导入一系列的异常)。这一系列衍生出来的导入需求,是在你的软件包中编织一张错综复杂的导入之网的始作俑者。你使用这种方式的次数越多,你的软件包内部就变的越相互依赖,也更加容易出错。
    随着异常数量的不断增长,找到一个软件包可能引发的全部异常变的越来越难。把所有的异常定义在一个单独的模块中,提供了一个方便的地方,在这里,程序员可以审查并确定你的软件包所能引发全部潜在错误状态。

你应该为你的软件包的异常定义一个基类:

class APackageException(Exception):

'''root for APackage Exceptions, only used to except any APackage error, never raised'''
  pass

然后确保你的软件包在任何错误状态下,只会引发这个基类异常的子类异常,这样如果你需要的话,你就可以阻止全部的异常:

try:

'''bunch of code from your package'''
except APackageException:

'''blanked condition to handle all errors from your package'''

对于一般的错误状态,这里有一些重要的异常处理已经被包括在标准库中了(例如,TypeError, ValueError等)

灵活地定义异常处理并保持足够的粒度:

# from fsq
class FSQEnvError(FSQError):

'''An error if something cannot be loaded from env, or env has an invalid

value'''
  pass

class FSQEncodeError(FSQError):

'''An error occured while encoding or decoding an argument'''
  pass
# ... and 20 or so more

在你的异常处理中保持更大的粒度,有利于让程序员们在一个try/except中包含越来越大的,互相不干涉的代码段。

# this
try:
  item = fsq.senqueue('queue', 'str', 'arg', 'arg')
  scanner = fsq.scan('queue')
except FSQScanError:

'''do something'''
except FSQEnqueueError:

'''do something else'''

# not this
try:
  item = fsq.senqueue('queue', 'str', 'arg', 'arg')
except FSQEnqueueError:

'''do something else'''
try:
  scanner = fsq.scan('queue')
except FSQScanError:

'''do something'''

# and definitely not
try:
  item = fsq.senqueue('queue', 'str', 'arg', 'arg')
  try:
    scanner = fsq.scan('queue')
  except FSQScanError:

'''do something'''
except FSQEnqueueError:

'''do something else'''

在异常定义时保持高度的粒度,会减少错综复杂的错误处理,并且允许你把正常执行指令和错误处理指令分别开来,使你的代码更加易懂和更易维护。
4. 在软件包内部只进行相对导入

在子模块中你时常见到的一个简单错误,就是使用软件包的名字来导入软件包。

# within a sub-module
from a_package import APackageError

这样做会导致两个不好的结果:

  1. 子模块只有当软件包被安装在 PYTHONPATH 内才能正确运行。
  2. 子模块只有当这个软件包的名字是 a_package 时才能正确运行。

尽管第一条看上去并不是什么大问题,但是考虑一下,如果你在 PYTHONPATH 下的两个目录中,有两个同名的软件包。你的子模块可能最终导入了另一个软件包,你将无意间使得某个或某些对此毫无戒备的程序员(或是你自己)debug 到深夜。

# within a sub-module
from . import FSQEnqueueError, FSQCoerceError, FSQError, FSQReenqueueError,
       constants as _c, path as fsq_path, construct,
       hosts as fsq_hosts, FSQWorkItem
from .internal import rationalize_file, wrap_io_os_err, fmt_time,
           coerce_unicode, uid_gid
# you can also use ../... etc. in sub-packages.

5. 让模块保持较小的规模

你的模块应当比较小。记住,那个使用你软件包的程序员会在软件包作用域进行导入,同时你会使用你的 __init__.py 文件来作为一个组织工具,来暴露一个完整的接口。

好的做法是一个模块只定义一个类,伴随一些帮助方法和工厂方法来协助建立这个模块。

class APackageClass(object):

'''One class'''

def apackage_builder(how_many):
  for i in range(how_many):
    yield APackageClass()

如果你的模块暴露了一些方法,把一些相互依赖的方法分为一组放进一个模块,并且把不相互依赖的方法移动到单独的模块中:

####### EXPOSED METHODS #######
def enqueue(trg_queue, item_f, *args, **kwargs):

'''Enqueue the contents of a file, or file-like object, file-descriptor or

the contents of a file at an address (e.g. '/my/file') queue with

arbitrary arguments, enqueue is to venqueue what printf is to vprintf

'''
  return venqueue(trg_queue, item_f, args, **kwargs)

def senqueue(trg_queue, item_s, *args, **kwargs):

'''Enqueue a string, or string-like object to queue with arbitrary

arguments, senqueue is to enqueue what sprintf is to printf, senqueue

is to vsenqueue what sprintf is to vsprintf.

'''
  return vsenqueue(trg_queue, item_s, args, **kwargs)

def venqueue(trg_queue, item_f, args, user=None, group=None, mode=None):

'''Enqueue the contents of a file, or file-like object, file-descriptor or

the contents of a file at an address (e.g. '/my/file') queue with

an argument list, venqueue is to enqueue what vprintf is to printf

if entropy is passed in, failure on duplicates is raised to the caller,

if entropy is not passed in, venqueue will increment entropy until it

can create the queue item.

'''

# setup defaults
  trg_fd = name = None

# ...

上面的例子是 fsq/enqueue.py,它暴露了一系列的方法来为同一个功能提供不同的接口(就像 simplejson 中的l oad/loads)。尽管这个例子足够直观,让你的模块保持较小规模需要一些判断,但是一个好的原则是:

当你有疑问的时候,就去创建一个新的子模块吧。

(0)

相关推荐

  • 给Python入门者的一些编程建议

    Python是一种非常富有表现力的语言.它为我们提供了一个庞大的标准库和许多内置模块,帮助我们快速完成工作.然而,许多人可能会迷失在它提供的功能中,不能充分利用标准库,过度重视单行脚本,以及误解Python基本结构等.本文是一个关于Python新手可能会陷入的一些陷阱的不完全列表. 不知道Python版本 这是一个在StackOverflow上反复出现的问题.许多人能写出在某个版本上完美工作的代码,但在他们在自己的系统上安装有不同版本的Python.要确保你知道你正在使用的Python版本. 你

  • 构建Python包的五个简单准则简介

    创建一个软件包(package)似乎已经足够简单了,也就是在文件目录下搜集一些模块,再加上一个__init__.py文件,对吧?我们很容易看出来,随着时间的推移,通过对软件包的越来越多的修改,一个设计很差的软件包可能会出现循环依赖问题,或是可能变得不可移植和不可靠. 1. __init__.py 仅为导入服务 对于一个简单的软件包,你可能会忍不住把工具方法,工厂方法和异常处理都丢进__init__.py,千万别这样! 一个结构良好的__init__.py文件,仅为一个非常重要的目的来服务:从子模

  • ROS系统将python包编译为可执行文件的简单步骤

    python install模式开发规则 本文章讲述ROS系统下如何将python编译为可以执行文件,步骤比较简单,请严格执行避免疏漏 1.下载必须文件 git clone https://gitee.com/alen2020/ros_python_install_mode_files ##取该目录下的cmake目录和setup.py,install.sh到自己节点的目录 cp -r cmake setup.py car_mqtt_api/ 如果用到动态参数,还需要拷贝install.sh,并做

  • 五个简单有效的Python清理数据脚本分享

    目录 将 PDF 转换为 CSV 合并 CSV 文件 从 CSV 文件中删除重复的行 拆分 CSV 列 合并不同的数据集 最后 将 PDF 转换为 CSV 在机器学习中,我们应该少一些“数据清理”,多一些“数据准备”.当我们需要从白皮书.电子书或其他PDF文档中抓取数据时,这个脚本为我节省了很多时间. import tabula #获取文件 pdf_filename = input ("Enter the full path and filename: ") # 提取PDF的内容 fr

  • Python基于jieba库进行简单分词及词云功能实现方法

    本文实例讲述了Python基于jieba库进行简单分词及词云功能实现方法.分享给大家供大家参考,具体如下: 目标: 1.导入一个文本文件 2.使用jieba对文本进行分词 3.使用wordcloud包绘制词云 环境: Python 3.6.0 |Anaconda 4.3.1 (64-bit) 工具: jupyter notebook 从网上下载了一篇小说<老九门>,以下对这篇小说进行分词,并绘制词云图. 分词使用最流行的分词包jieba,参考:https://github.com/fxsjy/

  • 提高Python生产力的五个Jupyter notebook插件

    在本文中,我将分享五个 Jupyter 扩展来提大家的工作效率. 欢迎收藏学习,喜欢点赞支持.技术交流群文末提供,欢迎畅聊. 让我们开始吧! 1. jupyter-resource-usage 您是否遇到过由于内存问题而导致 Jupyter Notebook 变慢或崩溃的情况? 当我们探索占用大量内存的大数据或繁重的建模计算过程时,这种情况经常发生. 为了控制内存问题,我们可以使用 jupyter-resource-usage 扩展在我们的 Notebook 中显示内存使用情况. 这个扩展工作很

  • python包相关知识点之包的导入、相对路径以及绝对路径

    目录 一.包 二.包的导入 1.import 导入 2.from...import...导入 3.__init__.py文件 4.from 包.模块 import * 三.包的相对和绝对导入 1. 绝对导入 2.相对导入 四.import 导入自定义包的子模块 五.包的单独导入 六.包的安装和发布 总结 一.包 在我们的项目中,可能会有太多的模块 但是我们不能把所有的模块这样放在这里,这样项目会乱七八糟. 我们可以将所有相同类型的模块放在一个文件夹中,这个文件夹就叫做包 包就是文件夹,他用于存放

  • Python栈算法的实现与简单应用示例

    本文实例讲述了Python栈算法的实现与简单应用.分享给大家供大家参考,具体如下: 原理: 栈作为一种数据结构,是一种只能在一端进行插入和删除操作.它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来) 桟的应用场景非常多:1.内存管理中使用的堆栈:2.基于桟实现的二叉树的遍历:3.在语言处理中,符号的平衡问题,在语言中,往往很多符号是成对出现的,比如<>,{},[],()等,如何判断符号是否漏了,一种实现方式就

  • Python安装Flask环境及简单应用示例

    本文实例讲述了Python安装Flask环境及简单应用.分享给大家供大家参考,具体如下: 安装环境 使用虚拟环境安装Flask,可以避免包的混乱和版本的冲突,虚拟环境是Python解释器的副本,在虚拟环境中你可以安装扩展包,为每个程序单独创建的虚拟环境,可以保证程序只能访问虚拟环境中的包.而不会影响系统中安装的全局Python解释器,从而保证全局解释器的整洁. 虚拟环境使用virtualenv创建,可以查看系统是否安装了virtualenv: $ virtualenv --version 安装虚

  • 用Anaconda安装本地python包的方法及路径问题(图文)

    Anaconda确实带来了很多方便,但是之前也过多的依赖了conda自带的一键下载python包的功能.这不,这几天突然要用FastFM这个包,无奈conda里没有,于是只能从github下载下来,实现本地安装. 以下是手动下载和安装步骤: 一.从GitHub上下载: 选择releases,里面会看到一系列版本的包,选择自己电脑对应的型号,点击下载.我选择的是fastFM-0.2.11-cp36-cp36m-macosx_10_7_x86_64.whl,由于电脑是mac,python版本为3.6

  • 将python包发布到PyPI和制作whl文件方式

    wheel文件 Wheel和Egg都是python的打包格式,目的是支持不需要编译或制作的安装过程,实际上也是一种压缩文件,将.whl的后缀改为.zip即可可看到压缩包里面的内容.按照官网说法,wheels是发行版Python的新标准并且要取代.egg. Egg格式是由setuptools在2004年引入,而Wheel格式是由PEP427在2012年定义. Wheel现在被认为是Python的二进制包的标准格式. 以下是Wheel和Egg的主要的不同点: Wheel有一个官方的PEP427来定义

随机推荐