如何根据传递给__init__的参数实例化特定的子类?

时间:2012-09-25 14:27:01

标签: python python-2.7

我正在从python 2.7包装一个基于XML的远程API。 API通过发送<statusCode>元素以及<statusDescription>元素来引发错误。现在,我抓住了这个条件并提出了一个异常类型。类似的东西:

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__

的参数来实例化特定的子类

3 个答案:

答案 0 :(得分:3)

工厂功能将更容易理解。使用字典将代码映射到异常类:

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

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

答案 1 :(得分: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)

答案 2 :(得分: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)