構建Python包的五個簡單準則簡介

來源:互聯網
上載者:User
建立一個軟體包(package)似乎已經足夠簡單了,也就是在檔案目錄下搜集一些模組,再加上一個__init__.py檔案,對吧?我們很容易看出來,隨著時間的推移,通過對軟體包的越來越多的修改,一個設計很差的軟體包可能會出現循環相依性問題,或是可能變得不可移植和不可靠。
1. __init__.py 僅為匯入服務

對於一個簡單的軟體包,你可能會忍不住把工具方法,Factory 方法和異常處理都丟進__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, internalimport constants # const relies on: constants, exceptions, internalfrom const import const, set_const # has tests # path relies on: exceptions, constants, internalimport path # has tests # lists relies on: pathfrom 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 fsqclass 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中包含越來越大的,互相不干涉的程式碼片段。

# thistry:  item = fsq.senqueue('queue', 'str', 'arg', 'arg')  scanner = fsq.scan('queue')except FSQScanError:  '''do something'''except FSQEnqueueError:  '''do something else''' # not thistry:  item = fsq.senqueue('queue', 'str', 'arg', 'arg')except FSQEnqueueError:  '''do something else'''try:  scanner = fsq.scan('queue')except FSQScanError:  '''do something''' # and definitely nottry:  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-modulefrom 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, FSQWorkItemfrom .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 檔案來作為一個組織工具,來暴露一個完整的介面。

好的做法是一個模組只定義一個類,伴隨一些協助方法和Factory 方法來協助建立這個模組。

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)。儘管這個例子足夠直觀,讓你的模組保持較小規模需要一些判斷,但是一個好的原則是:

當你有疑問的時候,就去建立一個新的子模組吧。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.