【问题标题】:factory class, wrong number of arguments being passed to subclass constructor工厂类,传递给子类构造函数的参数数量错误
【发布时间】:2012-06-24 02:31:38
【问题描述】:

我正在查看Python: Exception in the separated module works wrong,它使用多用途 GnuLibError 类来“替代”各种不同的错误。每个子错误都有自己的 ID 号和错误格式字符串。

我认为最好将其写成 Exception 类的层次结构,并着手这样做:

class GNULibError(Exception):
    sub_exceptions = 0  # patched with dict of subclasses once subclasses are created
    err_num = 0
    err_format = None

    def __new__(cls, *args):
        print("new {}".format(cls)) # DEBUG
        if len(args) and args[0] in GNULibError.sub_exceptions:
            print("  factory -> {} {}".format(GNULibError.sub_exceptions[args[0]], args[1:])) # DEBUG
            return super(GNULibError, cls).__new__(GNULibError.sub_exceptions[args[0]], *(args[1:]))
        else:
            print("  plain {} {}".format(cls, args)) # DEBUG
            return super(GNULibError, cls).__new__(cls, *args)

    def __init__(self, *args):
        cls = type(self)
        print("init {} {}".format(cls, args)) # DEBUG
        self.args = args
        if cls.err_format is None:
            self.message = str(args)
        else:
            self.message = "[GNU Error {}] ".format(cls.err_num) + cls.err_format.format(*args)

    def __str__(self):
        return self.message

    def __repr__(self):
        return '{}{}'.format(type(self).__name__, self.args)

class GNULibError_Directory(GNULibError):
    err_num = 1
    err_format = "destination directory does not exist: {}"

class GNULibError_Config(GNULibError):
    err_num = 2
    err_format = "configure file does not exist: {}"

class GNULibError_Module(GNULibError):
    err_num = 3
    err_format = "selected module does not exist: {}"

class GNULibError_Cache(GNULibError):
    err_num = 4
    err_format = "{} is expected to contain gl_M4_BASE({})"

class GNULibError_Sourcebase(GNULibError):
    err_num = 5
    err_format = "missing sourcebase argument: {}"

class GNULibError_Docbase(GNULibError):
    err_num = 6
    err_format = "missing docbase argument: {}"

class GNULibError_Testbase(GNULibError):
    err_num = 7
    err_format = "missing testsbase argument: {}"

class GNULibError_Libname(GNULibError):
    err_num = 8
    err_format = "missing libname argument: {}"

# patch master class with subclass reference
# (TO DO: auto-detect all available subclasses instead of hardcoding them)
GNULibError.sub_exceptions = {
    1: GNULibError_Directory,
    2: GNULibError_Config,
    3: GNULibError_Module,
    4: GNULibError_Cache,
    5: GNULibError_Sourcebase,
    6: GNULibError_Docbase,
    7: GNULibError_Testbase,
    8: GNULibError_Libname
}

这以 GNULibError 作为工厂类开始 - 如果您使用属于可识别子类的错误号调用它,它会返回一个属于该子类的对象,否则它会将自身作为默认错误类型返回。

根据这段代码,下面的代码应该是完全等价的(但不是):

e = GNULibError(3, 'missing.lib')
f = GNULibError_Module('missing.lib')

print e  # -> '[GNU Error 3] selected module does not exist: 3'
print f  # -> '[GNU Error 3] selected module does not exist: missing.lib'

我添加了一些策略性的打印语句,错误似乎在GNULibError.__new__

>>> e = GNULibError(3, 'missing.lib')

new <class '__main__.GNULibError'>
  factory -> <class '__main__.GNULibError_Module'> ('missing.lib',)  # good...
init <class '__main__.GNULibError_Module'> (3, 'missing.lib')        # NO!
                                            ^
                                           why?

我将子类构造函数称为subclass.__new__(*args[1:])——这应该去掉3,子类类型ID——但它的__init__仍然是3!如何修剪传递给 subclass.__init__ 的参数列表?

【问题讨论】:

    标签: python subclassing


    【解决方案1】:

    您不能影响传递给__init__ 的内容,只要您使用像现在这样返回其子类的“工厂类”来执行此操作。仍然传递“3”参数的原因是因为您仍然从__new__ 返回一个 GNULibError 实例。当__new__ 被调用时,现在决定将什么传递给__init__ 为时已晚。如the documentation 中所述(强调添加):

    如果__new__()返回一个cls的实例,那么新实例的__init__()方法将像__init__(self[, ...])一样被调用,其中self是新实例其余参数与已传递给__new__()

    换句话说,当您调用 GNULibError(3, 'missing.lib') 时,为时已晚 --- 通过使用这些参数调用类,您已确保这些参数是将传递给 __init__ 的参数。 __new__ 可以返回与您可能获得的不同的实例,但它不能阻止正常初始化的发生。

    正如@Ned Batchelder 所建议的,你最好使用工厂函数而不是“工厂类”,因为函数没有这个__new__/__init__ 机制,你可以只返回一个实例你想要的课程。

    【讨论】:

      【解决方案2】:

      这比它需要的要复杂得多。不要试图让一个类创建另一个类的对象。写一个工厂函数来创建你的异常,不要乱用__new__。正如您所发现的那样,这太棘手了。

      【讨论】:

        【解决方案3】:

        在您的用例中 - 我同意 Ned - 它需要更复杂。

        您可以尝试诸如(基于您的派生类似乎没有做任何事情但与错误消息不同的事实)。

        class GNULibError(Exception):
            pass # put logic code here
        
        GNULibErrors = {
            1: type('GNULibError_Directory', (GNULibError,), {'message': 'suitable whatever here'})
        }
        

        然后从那里调整...

        【讨论】:

          猜你喜欢
          • 2015-04-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-04-04
          • 2015-10-11
          • 1970-01-01
          • 2021-02-07
          相关资源
          最近更新 更多