2014-02-04 2 views
2

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

В питона Документами struct, есть пример с namedtuple здесь:

from collections import namedtuple 
Student = namedtuple('Student', 'name serialnum school gradelevel') 
Student._make(unpack('<10sHHb', record)) 

Мой вопрос: можно ли создать метакласса, что я мог бы использовать, чтобы сделать часть этого для меня? Как и мое предполагаемое решение:

class Student(object): 
    __metaclass__ = ??? 
    fields = 'name serialnum school gradelevel' 
    struct = '<10sHHb' 

s = Student(record) # and give same output as the _make() call above 

Как это сделать?

+0

Я не уверен, что вы здесь просите, но если хотите, вы можете использовать 'self .__ name__', чтобы получить имя класса, с которым работаете, поэтому вы можете построить' namedtuple' в ваш конструктор, агностик фактического имени, которое вы дали классу. – 2rs2ts

+0

@ 2rs2ts Я хочу определить класс, просто указав его поля и структуру структуры, чтобы он мог быть построен из его данных напрямую. Так что результат 2-го блока совпадает с результатом 1-го блока. – Barry

+0

Ох. Для этого я бы использовал 'setattr'. Я не знаком с модулем 'struct', но я бы сказал, передайте строку структуры и распакуйте его, а затем используйте' setattr' на основе результатов распаковки. – 2rs2ts

ответ

5

Определенно интересная проблема, вот мое решение:

import struct 
import collections 

class MetaStruct(type): 
    def __new__(cls, clsname, bases, dct): 
     nt = collections.namedtuple(clsname, dct['fields']) 
     def new(cls, record): 
      return super(cls, cls).__new__(cls, *struct.unpack(dct['struct'], record)) 
     dct.update(__new__=new) 
     return super(MetaStruct, cls).__new__(cls, clsname, (nt,), dct) 

class Student(object): 
    __metaclass__ = MetaStruct 
    fields = 'name serialnum school gradelevel' 
    struct = '<10sHHb' 

record = 'raymond \x32\x12\x08\x01\x08' 
s = Student(record) 

Существенная разница между моим ответом и других является то, что в конце этого Student еще класс и s является экземпляром Student (isinstance(s, Student) возвращает True). Я выполнил это с помощью метакласса, добавив namedtuple в качестве базового класса вновь созданного класса, с созданием объекта нового класса (Student.__new__), делегированного базовому классу (namedtuple).

2

Да, можно:

from collections import namedtuple 
from struct import pack, unpack 

class MetaStruct(type): 
    def __new__(meta, name, bases, attrs): 
     nt = namedtuple(name, attrs.pop('fields')) 
     struct = attrs.pop('struct') 
     def factory(record): 
      return nt._make(unpack(struct, record)) 
     return factory 

class Student(object): 
    __metaclass__ = MetaStruct 
    fields = 'name serialnum school gradelevel' 
    struct = '<10sHHb' 

record = pack('<10sHHb', 'student123', 123, 456, 12) 
s = Student(record) 
# => Student(name='student123', serialnum=123, school=456, gradelevel=12) 

MetaStruct метакласса чита немного: он определяет указанный namedtuple, но вместо фактического класса (как метаклассы ожидается), она возвращает фабричную функцию, которая производит экземпляры namedtuple; предполагается, что «класс», определенный с использованием этого метакласса, должен вернуть namedtuple после создания экземпляра, а не полноценного экземпляра класса, поэтому это неизбежно. Он может определять фактический подкласс с определенным namedtuple как родительский, но он кажется излишним в вашем сценарии.

+0

Очень круто! Не думал об этом «обмане». – Barry

+1

Это довольно умно, но у него есть предостережение, что 'Student' не будет классом, поэтому что-то вроде' isinstance (s, Student) 'вызовет исключение.Это было бы довольно запутанным для диагностики, вам, вероятно, лучше просто создать фабричную функцию вместо использования магии класса/метакласса. –

+0

Было интересно узнать, могу ли я обойти проблему с моего последнего комментария, просто добавил аналогичный ответ, который, кажется, делает трюк. –

0

Ответ lanzz довольно аккуратный; здесь альтернативный метод, использующий декораторы:

class Dec(object): 
    def __init__(self, cls, fields=None, struct=None): 
     print cls,fields,struct 
     self.cls = cls 
     self.nt = namedtuple(cls.__name__, fields or cls.fields) 
     self.struct = struct 

    def __call__(self, record): 
     return self.nt._make(unpack(self.struct or self.cls.struct, record)) 


@Dec 
class Student(object): 
    fields = 'name serialnum school gradelevel' 
    struct = '<10sHHb' 


s = Student('abcdefghla00000') 

Он делает то же самое, что и метакласса но используя класс декоратора. Вы также можете использовать метод декоратора, и вы можете изменить либо, чтобы принимать поля/struct как аргументы декоратора. Так много возможностей ...

Один метод лучше, чем другой? Я не уверен, это, вероятно, сводится к предпочтению. Для этой задачи я, вероятно, предпочел бы декоратор, так как вы на самом деле не создаете классы, а namedtuple заводы, но это только я.

Смежные вопросы