创建元类以将结构解压缩为namedtuple

时间:2014-02-04 21:00:26

标签: python

我最近阅读了一些有关元类的SO问题,虽然看起来大部分时间都没有必要使用(包括我的问题),但我觉得这个案例很有意思。

struct的python文档中,这里有一个带有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

我该怎么做?

3 个答案:

答案 0 :(得分: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的结尾仍然是一个类,sStudent的实例(isinstance(s, Student)返回真正)。我通过让元类将namedtuple添加为新创建的类的基类来完成此操作,并将对象创建新类(Student.__new__)委托给基类(namedtuple)。

答案 1 :(得分: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,但它不是实际的类(如预期的元类),而是返回一个生成{{的实例的工厂函数。 1}};仍然,使用此元类定义的“类”应该在实例化时返回namedtuple而不是完全成熟的类实例,因此这是不可避免的。它可以定义一个实际的子类,并将定义的namedtuple作为父类,但在您的场景中似乎有点过分。

答案 2 :(得分: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')

它与元类相同但使用装饰器类。您还可以使用装饰器方法,并且可以修改以将fields / struct作为装饰器参数。这么多的可能性......

一种方法比另一种更好吗?我不确定,它可能归结为偏好。对于这个任务,我可能更喜欢装饰器,因为你不是真的创建类而是命名工厂,但那只是我。