Last active
December 28, 2016 07:56
-
-
Save gruzovator/fb2d722a43225ad4cd15fb82b7d638e0 to your computer and use it in GitHub Desktop.
structure metaclass
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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