您现在的位置: 万盛学电脑网 >> 程序编程 >> 网络编程 >> 编程语言综合 >> 正文

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

作者:佚名    责任编辑:admin    更新时间:2022-06-22

   这篇文章主要介绍了构建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