Skip to content

Instantly share code, notes, and snippets.

@gruzovator
Last active December 28, 2016 07:56
Show Gist options
  • Save gruzovator/fb2d722a43225ad4cd15fb82b7d638e0 to your computer and use it in GitHub Desktop.
Save gruzovator/fb2d722a43225ad4cd15fb82b7d638e0 to your computer and use it in GitHub Desktop.
structure metaclass
from types import NoneType
class Field(object):
def __init__(self, expected_types, *default):
self.name = None # set by metaclass
self.internal_name = None # set by metaclass
self.expected_types = expected_types
if default:
self.default = default[0]
def __repr__(self):
return "%s(name='%s', expected_types=%s)" % \
(self.__class__.__name__, self.name, self.expected_types)
class RoField(Field):
def __get__(self, instance, instance_class):
return getattr(instance, self.internal_name)
class RwField(RoField):
def __set__(self, instance, value):
assert isinstance(value, self.expected_types), \
"Value for field '%s' has wrong type %s" % (self, type(value))
return setattr(instance, self.internal_name, value)
class StructMeta(type):
def __new__(cls, clsname, parents, dct):
slots = []
for k, v in dct.items():
if isinstance(v, Field):
field = v
field.name = k
field.internal_name = '_' + k
slots.append(field.internal_name)
# check default value type
try:
assert isinstance(field.default, field.expected_types), \
"default value for field '%s.%s' has wrong type %s" \
% (clsname, field, type(field.default))
except AttributeError: # if no default value
pass
dct['__slots__'] = slots
def init(self, *args, **kwargs):
for k, v in dct.items():
if isinstance(v, Field):
field = v
if field.name in kwargs:
init_value = kwargs[field.name]
else:
try:
init_value = field.default
except AttributeError: # no .default
raise Exception('no init value for read-only field %s.%s' %(clsname, field))
assert isinstance(init_value, field.expected_types), \
"init value for field '%s.%s' has wrong type %s" % (clsname, field, type(init_value))
setattr(self, field.internal_name, init_value)
dct['__init__'] = init
# pickle/unpickle
dct['__getstate__'] = lambda self: {f: getattr(self, f) for f in self.__slots__}
dct['__setstate__'] = lambda self, state: [setattr(self, k, v) for k, v in state.iteritems()]
# representation
def repr(self):
args = []
for f in self.__slots__:
pub_name = f[1:]
args.append('%s=%r' % (pub_name, getattr(self, f)))
return '%s(%s)' % (clsname, ','.join(args))
dct['__repr__'] = repr
# conversion
dct['_asdict'] = lambda self: {f[1:]: getattr(self, f) for f in self.__slots__}
return type.__new__(cls, clsname, parents, dct)
class StructBase(object):
__metaclass__ = StructMeta
##############################################
# DEMO
##############################################
import pickle
class MyStruct(StructBase):
f0 = RoField(int, 0)
f1 = RoField((int, NoneType), None)
f2 = RwField(str)
s = MyStruct(f1=1, f2='2')
s.f2 = '3'
print s
data = pickle.dumps(s, protocol=pickle.HIGHEST_PROTOCOL)
ss = pickle.loads(data)
print ss
ss.f2 = '1'
ss.f1 =1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment