Шаг 113.
Python: сборник рецептов. Кодирование и обработка данных. Чтение вложенных и различных по размеру бинарных структур (продолжение)

    На этом шаге мы продолжим изучение этого вопроса.

    Каждый раз, когда вы сталкиваетесь с подобным излишне многословным определением класса, вы можете подумать об использовании декоратора класса или метакласса. Одна из возможностей метакласса в том, что он может быть использован для выполнения множества низкоуровневых деталей реализации, снимая это бремя с пользователя. В качестве примера рассмотрите этот метакласс и слегка переработанный класс 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))

	
>>>
Используя этот новый класс Structure, вы можете записывать определение структуры так:
>>> 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
>>>

    На этом этапе мы разработали фреймворк для работы с записями фиксированного размера, но что делать с компонентами различных размеров? Например, оставшаяся часть файла с многоугольниками содержит элементы различных размеров.

    На следующем шаге мы закончим изучение этого вопроса.




Предыдущий шаг Содержание Следующий шаг