На этом шаге мы продолжим изучение этого вопроса.
Каждый раз, когда вы сталкиваетесь с подобным излишне многословным определением класса, вы можете подумать об использовании декоратора класса или метакласса. Одна из возможностей метакласса в том, что он может быть использован для выполнения множества низкоуровневых деталей реализации, снимая это бремя с пользователя. В качестве примера рассмотрите этот метакласс и слегка переработанный класс Structure:
>>> class StructureMeta(type): ''' Метакласс, который автоматически создает дескрипторы StructField ''' def __init__(self, clsname, bases, clsdict): fields = getattr(self, '_fields_', []) byte_order = '' offset = 0 for format, fieldname in fields: if format.startswith(('<', '>', '!', '@')): byte_order = format[0] format = format[1:] format = byte_order + format setattr(self, fieldname, StructField(format, offset)) offset += struct.calcsize(format) setattr(self, 'struct_size', offset) >>> class Structure(metaclass=StructureMeta): def __init__(self, bytedata): self._buffer = bytedata @classmethod def from_file(cls, f): return cls(f.read(cls.struct_size)) >>>
>>> class PolyHeader(Structure): _fields_ = [ ('<i', 'file_code'), ('d', 'min_x'), ('d', 'min_y'), ('d', 'max_x'), ('d', 'max_y'), ('i', 'num_polys') ] >>>
Как вы можете видеть, это определение намного компактнее. Добавленный метод класса from_file() также делает более удобным чтение данных из файла, снимая необходимость знать какие-либо детали о размере или структуре данных. Например:
>>> f = open('polys.bin', 'rb') >>> phead = PolyHeader.from_file(f) >>> phead.file_code == 0x1234 True >>> phead.min_x 0.5 >>> phead.min_y 0.5 >>> phead.max_x 7.0 >>> phead.max_y 9.2 >>> phead.num_polys 3 >>>
Когда вы вводите в программу метакласс, то можете встроить в него больше "интеллекта". Например, предположим, что вы хотите обеспечить поддержку вложенных бинарных структур. Вот переделанный метакласс вместе с новым дескриптором, который это поддерживает:
>>> class NestedStruct: ''' Дескриптор, представляющий вложенную структуру ''' def __init__(self, name, struct_type, offset): self.name = name self.struct_type = struct_type self.offset = offset def __get__(self, instance, cls): if instance is None: return self else: data = instance._buffer[self.offset:self.offset + \ self.struct_type.struct_size] result = self.struct_type(data) # Сохраняем получившуюся структуру обратно в экземпляр, # чтобы избежать последующего вычисления этого шага setattr(instance, self.name, result) return result >>> class StructureMeta(type): ''' Метакласс, который автоматически создает дескрипторы StructField ''' def __init__(self, clsname, bases, clsdict): fields = getattr(self, '_fields_', []) byte_order = '' offset = 0 for format, fieldname in fields: if isinstance(format, StructureMeta): setattr(self, fieldname, \ NestedStruct(fieldname, format, offset)) offset += format.struct_size else: if format.startswith(('<', '>', '!', '@')): byte_order = format[0] format = format[1:] format = byte_order + format setattr(self, fieldname, StructField(format, offset)) offset += struct.calcsize(format) setattr(self, 'struct_size', offset) >>>
В этом примере кода дескриптор NestedStruct используется для наложения другого определения структуры на область памяти. Он делает это путем извлечения среза изначального буфера памяти и использования его для создания экземпляра переданного типа структуры. Поскольку буфер памяти был инициализирован как memoryview, это извлечение среза не приводит к созданию дополнительных копий в памяти. Вместо этого оно накладывается на изначальную память. Более того, чтобы избежать повторения создания экземпляров, дескриптор сохраняет получившуюся внутреннюю структуру объекта в экземпляр.
Используя эту новую формулировку, вы можете начать писать код так:
>>> class Structure(metaclass=StructureMeta): def __init__(self, bytedata): self._buffer = memoryview(bytedata) @classmethod def from_file(cls, f): return cls(f.read(cls.struct_size)) >>> class Point(Structure): _fields_ = [ ('<d', 'x'), ('d', 'y') ] >>> class PolyHeader(Structure): _fields_ = [ ('<i', 'file_code'), (Point, 'min'), (Point, 'max'), ('i', 'num_polys') ]
Удивительно, но код все еще работает так, как вы ожидаете. Например:
>>> f = open('polys.bin', 'rb') >>> phead = PolyHeader.from_file(f) >>> phead.file_code == 0x1234 True >>> # Вложенная структура >>> phead.min <__main__.Point object at 0x000001F2D9A28B50> >>> phead.min.x 0.5 >>> phead.min.y 0.5 >>> phead.max.x 7.0 >>> phead.max.y 9.2 >>> phead.num_polys 3 >>>
На этом этапе мы разработали фреймворк для работы с записями фиксированного размера, но что делать с компонентами различных размеров? Например, оставшаяся часть файла с многоугольниками содержит элементы различных размеров.
На следующем шаге мы закончим изучение этого вопроса.