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

   这篇文章主要介绍了构建Python包的五个简单准则简介,在Github开源合作日趋主流的今天,健壮的Python包的构建成为开发者必须要考虑到的问题,本文提出了五项建议,需要的朋友可以参考下

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

  1. __init__.py 仅为导入服务

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

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

  ?

  1

  2

  3

  4

  5

  6

  7

  8

  9

  10

  11

  12

  13

  14

  15

  16

  17

  18

  19

  20

  21

  22

  23

  24

  25

  26# 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中唯一的与导入模块不相关的代码:

  ?

  1

  2

  3

  4

  5

  6

  7

  8

  9

  10__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子模块中导入了全部的异常。从这里出发,你将看到,在大多数的软件包中,异常被定义在引起它们的代码附近。尽管这样可以为一个模块提供高度的完整性,一个足够复杂的软件包会通过如下两种方式,使得这一模式出现问题。

  通常一个模块/程序需要从一个子模块导入一个函数, 利用它导入代码并抛出异常。为了捕获异常并保持一定的粒度,你需要导入你需要的模块,以及定义了异常的模块(或者更糟,你要导入一系列的异常)。这一系列衍生出来的导入需求,是在你的软件包中编织一张错综复杂的导入之网的始作俑者。你使用这种方式的次数越多,你的软件包内部就变的越相互依赖,也更加容易出错。

  随着异常数量的不断增长,找到一个软件包可能引发的全部异常变的越来越难。把所有的异常定义在一个单独的模块中,提供了一个方便的地方,在这里,程序员可以审查并确定你的软件包所能引发全部潜在错误状态。

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

  ?

  1

  2

  3

  4class APackageException(Exception):

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

  pass

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

  ?

  1

  2

  3

  4

  5

  6try:

  '''bunch of code from your package'''

  except APackageException:

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

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

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

  ?

  1

  2

  3

  4

  5

  6

  7

  8

  9

  10

  11

  12

  13# 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中包含越来越大的,互相不干涉的代码段。

  ?

  1

  2

  3

  4

  5

  6

  7

  8

  9

  10

  11

  12

  13

  14

  15

  16

  17

  18

  19

  20

  21

  22

  23

  24

  25

  26

  27

  28

  29

  30

  31

  32

  33

  34# 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. 在软件包内部只进行相对导入

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

  ?

  1

  2# within a sub-module

  from a_package import APackageError

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

  子模块只有当软件包被安装在 PYTHONPATH 内才能正确运行。

  子模块只有当这个软件包的名字是 a_package 时才能正确运行。

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

  ?

  1

  2

  3

  4

  5

  6

  7# 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 文件来作为一个组织工具,来暴露一个完整的接口。

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

  ?

  1

  2

  3

  4

  5

  6

  7class APackageClass(object):

  '''One class'''

  def apackage_builder(how_many):

  for i in range(how_many):

  yield APackageClass()

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

  ?

  1

  2

  3

  4

  5

  6

  7

  8

  9

  10

  11

  12

  13

  14

  15

  16

  17

  18

  19

  20

  21

  22

  23

  24

  25

  26

  27

  28

  29

  30

  31

  32

  33

  34

  35

  36

  37

  38

  39

  40

  41

  42

  43####### 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)。尽管这个例子足够直观,让你的模块保持较小规模需要一些判断,但是一个好的原则是:

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

时间: 2025-01-31 05:42:36

构建Python包的五个简单准则简介的相关文章

Python 包管理工具解惑

本文转载自http://zengrong.net/post/2169.htm,感谢原作者. 一.困惑 作为一个 Python 初学者,我在包管理上感到相当疑惑(嗯,是困惑).主要表现在下面几个方面: 这几个包管理工具有什么不同? distutils setuptools distribute disutils2 distlib pip 什么时候该用pip,什么时候该用 setup.py ,它们有关系么? easy_install.ez_setup.py.setup.py.setup.cfg 分别

使用Docker镜像构建RPM包

本文讲的是使用Docker镜像构建RPM包,[编者的话]RPM(Red Hat Package Manager)是用于 Linux 分发版的最常见的软件包管理器.因为它允许分发已编译的软件,所以用户只用一个命令就可以安装软件.而RPM包的构建相当繁琐,并且对环境的要求比较高,本文作者介绍了如何借助Docker来构建可以适用多个平台的RPM包. 在一个内部项目中,我一直在思考如何通过非CI工具/流程生成RPM包,我想手动生成RPM包,这样我可以测试它们是否能正常安装,并用于正常的冒烟测试(译者注:

python包管理-distutils,setuptools,pip,virtualenv等介绍

python包管理-distutils,setuptools,pip,virtualenv等介绍 对于每个编程语言来说打包和发布开发包往往非常重要,而作为一个编程者能够快速容易的获得并应用这些由第三方提供的包同样非常重要.类似于java为了便于管理有人开发了maven等管理工作,而python自然而然也需要便捷的打包和发布工具,以下就介绍python的几个包管理方式.   一  distutils - Python自带的基本安装工具, 适用于非常简单的应用场景使用 通过distutils来打包,

docker(5):使用alpinelinux 构建python http 项目

本文的原文连接是: http://blog.csdn.net/freewebsys/article/details/53509676 未经博主允许不得转载. 博主地址是:http://blog.csdn.net/freewebsys 1,开始学习构建docker镜像 http://study.163.com/course/courseMain.htm?courseId=1273002 课程讲的是使用centos构建.自己也一直使用centos,但是有个问题. 就是centos镜像比较大,安装的东

在Jupyter中安装Python包

彩蛋:作者著作:<Python Data Science Handbook> 上述图书是电子书链接,供爱学习的同学学习. 对于使用Jupyter notebook的户来说,你会经常遇到下面的问题: 我安装了软件包X,现在我无法将其导入到notebook中.帮帮我! 这个问题几乎是所有初学者第一个拦路虎,任何语言都是如此.今天我们就来说说Jupyter notebook如何解决这类问题. 从根本上来说,这个问题的根是Jupyter内核与Jupyter的shell分离的事实,换句话说,安装程序与笔

Muduo 网络编程示例(一) 五个简单TCP协议

本文将介绍第一个示例:五个简单 TCP 网络服务协议,包括 echo (RFC 862).discard (RFC 863) .chargen (RFC 864).daytime (RFC 867).time (RFC 868),以及 time 协议的客户端.各协议的功 能简介如下: * discard - 丢弃所有收到的数据: * daytime - 服务端 accept 连接之 后,以字符串形式发送当前时间,然后主动断开连接: * time - 服务端 accept 连接之后,以 二进制形式

Python使用shelve模块实现简单数据存储的方法

  本文实例讲述了Python使用shelve模块实现简单数据存储的方法.分享给大家供大家参考.具体分析如下: Python的shelve模块提供了一种简单的数据存储方案,以dict(字典)的形式来操作数据. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

Python加pyGame实现的简单拼图游戏实例

 本文实例讲述了Python加pyGame实现的简单拼图游戏.分享给大家供大家参考.具体实现方法如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 6

python处理图片之PIL模块简单使用方法

  这篇文章主要介绍了python处理图片之PIL模块简单使用方法,涉及Python使用PIL模块实现针对图片的锐化.绘制直线.绘制椭圆等相关技巧,需要的朋友可以参考下 本文实例讲述了python处理图片之PIL模块简单使用方法.分享给大家供大家参考.具体实现方法如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 4