2012-09-25 78 views
0

我正在從python 2.7中打包一個基於XML的遠程API。該API通過發送一個<statusCode>元素以及一個<statusDescription>元素來引發錯誤。現在,我抓住了這個條件,並提出了一個單一的異常類型。喜歡的東西:如何基於傳遞給__init__的參數實例化特定的子類?

class ApiError(Exception): 
    pass 

def process_response(response): 
    if not response.success: 
     raise ApiError(response.statusDescription) 

這工作得很好,但我現在想在一個更復雜的方式來處理錯誤。由於我有statusCode元素,因此我想根據statusCode提出一個特定的ApiError子類。實際上,我想我的包裝進行擴展這樣的:

class ApiError(Exception): 
    def __init__(self, description, code): 
     # How do I change self to be a different type? 
     if code == 123: 
      return NotFoundError(description, code) 
     elif code == 456: 
      return NotWorkingError(description, code) 

class NotFoundError(ApiError): 
    pass 

class NotWorkingError(ApiError): 
    pass 

def process_response(response): 
    if not response.success: 
     raise ApiError(response.statusDescription, response.statusCode) 

def uses_the_api(): 
    try: 
     response = call_remote_api() 
    except NotFoundError, e: 
     handle_not_found(e) 
    except NotWorkingError, e: 
     handle_not_working(e) 

的機械捆綁特定statusCode的具體子類是直接的。但是我想要的是將它埋在ApiError的某處。具體來說,除了傳入值statusCode之外,我不想更改process_response。

我已經看過元類,但不確定它們是否有助於這種情況,因爲__new__獲得了寫入時間參數,而不是運行時參數。同樣無用的是在__init__周圍進行黑客攻擊,因爲它不打算返回實例。那麼,如何基於傳遞給__init__的參數實例化一個特定的子類呢?

+1

你爲什麼要使用一個類,如果你需要的方法?使用一個函數,根據狀態碼 –

+0

@GabiPurcaru返回不同的實例。好的問題。因爲我不想顯着地改變'process_response'中的'raise ApiError()'行。我感興趣的是如何在不影響現有「提高」聲明的情況下做到這一點。 –

回答

1

創建一個函數,該函數將基於描述產生請求的錯誤類。 事情是這樣的:

def get_valid_exception(description, code): 
    if code == 123: 
     return NotFoundError(description, code) 
    elif code == 456: 
     return NotWorkingError(description, code) 

根據您的需求和未來的變化,你可以創建具有不同參數異常或做別的事,在不影響使用該功能代碼。

然後在你的代碼,你可以使用它像這樣:

def process_response(response): 
    if not response.success: 
     raise get_valid_exception(response.statusDescription, response.statusCode) 
+1

謝謝。代替更優雅的解決方案,這或多或少是我最終實現的。即使'raise get_valid_exception()'不夠完美,它也是有意和簡潔的。 –

1

您可以創建一系列子類,並使用基類「__new__」作爲孩子的工廠。但是,這可能是過度的;你可以創建一個簡單的工廠方法或類。如果你想在另一個方向上看到幻想,你可以爲基類創建一個元類,當它們被創建時會自動將你的子類添加到工廠。例如:

class ApiErrorRegistry(type): 

    code_map = {} 

    def __new__(cls, name, bases, attrs): 

     try: 
      mapped_code = attrs.pop('__code__') 
     except KeyError: 
      if name != 'ApiError': 
       raise TypeError('ApiError subclasses must define a __code__.') 
      mapped_code = None 
     new_class = super(ApiErrorRegistry, cls).__new__(cls, name, bases, attrs) 
     if mapped_code is not None: 
      ApiErrorRegistry.code_map[mapped_code] = new_class 
     return new_class 

def build_api_error(description, code): 

    try: 
     return ApiErrorRegistry.code_map[code](description, code) 
    except KeyError: 
     raise ValueError('No error for code %s registered.' % code) 


class ApiError(Exception): 

    __metaclass__ = ApiErrorRegistry 


class NotFoundError(ApiError): 

    __code__ = 123 


class NotWorkingError(ApiError): 

    __code__ = 456 


def process_response(response): 

    if not response.success: 
     raise build_api_error(response.statusDescription, response.statusCode) 

def uses_the_api(): 
    try: 
     response = call_remote_api() 
    except ApiError as e: 
     handle_error(e) 
+0

你的例子或多或少是我準備建立的解決方案。然而,看起來就像這樣,'raise ApiErrorRegistry.build_error(description,code)'的味道不僅僅是一個簡單的'raise build_error(description,code)'。所以我想如果有更好的東西沒有出現,我可能會選擇工廠職能。 –

+0

另外,在異常子類上放置一個'handle()'方法似乎是可疑的。它不適用於我目前正在編寫的包裝器,並且在設想一個我希望在類本身中使用異常處理邏輯而不是在諸如'uses_the_api()'等函數中的情況時遇到了困難。 –

+0

我只是想讓它更加簡潔,而且將構建器連接到註冊表似乎是合乎邏輯的。當然,沒有要求這樣做。至於「處理」,我不知道你打算如何使用它。如果它在那裏沒有意義,那麼只需將處理程序分開即可。 –

3

出廠功能將是更容易理解。使用字典代碼映射到異常類:

exceptions = { 
    123: NotFoundError, 
    456: NotWorkingError, 
    # ... 
} 

def exceptionFactory(description, code): 
    return exceptions[code](description, code) 
+0

我更喜歡你的代碼示例,但這個答案在功能上等同於@ Abgan's,他的第一個出現,所以我接受了他的回答。謝謝。 –

相關問題