可以为超类的构造函数返回子类的实例吗?

时间:2012-02-24 20:17:16

标签: python oop

我正在解析二进制日志文件。日志文件的格式如下:每10个字节是一个记录,记录的第一个字节是记录类型,接下来的5个字节是时间戳,最后4个字节是记录类型特定的数据。

目前我正在做以下事情:

# read the input binary stream
with open(filename, mode='rb') as trace_stream:
    # create an empty list of trace records
    trace = []
    # iterate over each record in the binary stream
    for record_type, record_data in yield_record(trace_stream,
                                                 size=RECORD_LENGTH):
        # create a new record instance
        if record_type == SEN_RECORD:
            new_record = sen_record(record_data)
        elif record_type == DSP_RECORD:
            new_record = dsp_record(record_data)
        elif record_type == USO_RECORD:
            new_record = uso_record(record_data)
        elif record_type == SDM_RECORD:
            new_record = sdm_record(record_data)
        elif record_type == DOC_RECORD:
            new_record = doc_record(record_data)
        elif record_type == DAT_RECORD:
            new_record = dat_record(record_data)
        elif record_type == LAT_RECORD:
            new_record = lat_record(record_data)
        elif record_type == SWI_RECORD:
            new_record = swi_record(record_data)
        elif record_type == FTL_RECORD:
            new_record = ftl_record(record_data)

        # append this new record to our trace
        trace.append(new_record)

其中sen_record,dsp_record,uso_record等都是通用记录类的子类

我想做的是以下内容:

# read the input binary stream
with open(filename, mode='rb') as trace_stream:
    # create an empty list of trace records
    trace = []
    # iterate over each record in the binary stream
    for record_type, record_data in yield_record(trace_stream,
                                                 size=RECORD_LENGTH):
            new_record = record(record_data)

    trace.append(new_record)

然后让记录类构造函数执行确定它是什么类型的记录并创建适当的类实例的工作。理想情况下,我的“主要”例程不需要知道记录类型吗?

有没有办法做到这一点?

4 个答案:

答案 0 :(得分:6)

存储映射

会更简单
record_types = {SEN_RECORD: sen_record,
                DSP_RECORD: dsp_record,
                USO_RECORD: uso_record,
                SDM_RECORD: sdm_record,
                DOC_RECORD: doc_record,
                DAT_RECORD: dat_record,
                LAT_RECORD: lat_record,
                SWI_RECORD: swi_record,
                FTL_RECORD: ftl_record}

某处,并使用它来查找正确的记录类型。 (请注意,您可以这样做,因为类只是对象,因此您可以将它们放在字典中。)

具体来说,你做

new_record = record_types[record_type](record_data)

有更复杂的方法可以执行此操作(例如,如果您希望动态创建子类并在创建时自动注册其超类),但在您的情况下无需使用它们。

答案 1 :(得分:3)

有一种方法可以做到这一点,但我不建议您使用它。我建议只使用工厂函数根据记录构造并返回正确类型的对象。

def create_record(record_type):
  if record_type == SEN_RECORD:
    return sen_record(record_data)
  ...

要覆盖对象创建的行为,可以在类中提供__new__方法。有关详细信息,请参阅official docs。但同样,我不推荐这个;对于除了最专业的应用之外的所有应用程序,使用它就是玩火。

答案 2 :(得分:2)

因为很高兴了解Python的动态特性,所以这就是你如何“神奇地”做到这一点。虽然在实际代码中做这种事情并不是一个好主意 - 它很脆弱,可能导致意外行为。它也是putting data in your variable names,这是一个坏习惯。

此外,您无法完全按照自己的意愿行事,因为操作顺序存在问题。具体来说,当您定义Record时,您无法(显然)定义其子类。所以你不能在那时将调度逻辑放入类中。但是没有其他的时间你可以说“现在我们已经完成了定义子类,设置了调度”,因此你必须在所有子类定义之后将其硬编码到你的源代码中。然后你也可以像我的其他答案一样对dict进行硬编码。

无论如何,有了这个免责声明,这就是魔术。 (它仅适用于新式类。)

@classmethod
def update_record_types(cls):
    cls.records = {c.__name__.upper(): c for c in cls.__subclasses__()}

然后Record.__init__只引用了类属性records,您可以随时通过调用Record.update_record_types()来更新。


编辑:我想我应该指出如何使用它!

>>> class Record(object):
...     @classmethod
...     def update_record_types(cls):
...         cls.records = {c.__name__.upper(): c for c in cls.__subclasses__()}
... 
>>> # define some record types, each with their own __init__
>>> class sen_record(Record): pass
>>> class dsp_record(Record): pass
>>> class uso_record(Record): pass
>>>
>>> # update the listing of record types
>>> Record.update_record_types()
>>>
>>> # look up the one you want
>>> Record.records["SEN_RECORD"]
<class '__main__.sen_record'>

答案 3 :(得分:1)

以下是使用eval执行此操作的方法。我必须对你的数据做出假设,你的record_type字段是值“SEN”,“DSP”等之一。我也会假设你的解析器验证< / strong>的数据,否则这段代码将是一个巨大的安全漏洞。 (事实上​​,这样做会对性能造成影响(与字典或工厂函数相比),但它有点像你想要的那样“神奇”。)

class SEN_record(record):
  ...

class DSP_record(record):
  ...

... # other record subclasses similarly defined here

# read the input binary stream
with open(filename, mode='rb') as trace_stream:
    # create an empty list of trace records
    trace = []
    # iterate over each record in the binary stream
    for record_type, record_data in yield_record(trace_stream,
                                                 size=RECORD_LENGTH):
        trace.append(eval("%s_record(record_data)" % (record_type,)))