Может ли конструктор суперкласса вместо этого возвращать экземпляры подкласса? - PullRequest
2 голосов
/ 25 февраля 2012

Я анализирую двоичный файл журнала. Файл журнала форматируется следующим образом: каждые 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 ]

6 голосов
/ 25 февраля 2012

Было бы проще просто сохранить отображение

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)

Существуют более сложные способы сделать это (например, если вы хотите, чтобы подклассы создавались динамически и автоматически регистрировались в их суперклассе при создании), но в вашей ситуации их не нужно использовать.

3 голосов
/ 25 февраля 2012

Есть способ сделать это, но я не рекомендую вам его использовать.Я рекомендую просто использовать фабричную функцию для создания и возврата правильного типа объекта на основе записи.

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

Чтобы переопределить поведение создания объекта, в классе можно предоставить метод __new__.См. официальные документы для деталей.Но опять же, я не рекомендую это;для всех, кроме самых специализированных приложений, использование этого - игра с огнем.

2 голосов
/ 25 февраля 2012

Поскольку приятно узнать о динамических функциях Python, вот как вы можете сделать это «волшебным образом».Хотя в реальном коде делать подобные вещи не очень хорошая идея - он хрупок и может привести к неожиданному поведению.Также помещает данные в имена переменных , что является плохой привычкой.

Кроме того, вы не можете делать то, что хотите, потому что есть проблема с последовательностью операций.В частности, когда вы определяете Record, вы еще не можете определить его подклассы (очевидно).Таким образом, вы не можете в этот момент применить диспетчерскую логику в классе.Но нет другого особенного времени, когда вы можете сказать: «теперь мы закончили определение подклассов, настроили диспетчеризацию», поэтому вы должны жестко закодировать его в свой исходный код после всех определений подкласса.Тогда вы можете так же жестко закодировать диктовку, как и в моем другом ответе.

В любом случае, с этим отказом от ответственности, вот магия.(Это работает только для классов нового стиля.)

@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'>
1 голос
/ 27 февраля 2012

Вот способ сделать это, используя eval.Я должен сделать предположение о ваших данных, хотя, что ваше поле record_type является одним из значений "SEN", "DSP" и т. Д. Я также собираюсь предположить, что ваш анализатор делает проверка данных, в противном случае этот код будет огромной дырой в безопасности.(На самом деле, за это стоит снижение производительности (по сравнению со словарем или фабричной функцией), но это своего рода «волшебство», как вы хотите.)

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,)))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...