Ну что, ваши усилия не пропали даром. Начал встраивать в уже работающий проект и довёл всё это до такого вида.
Класс называется
Meta, потому что у меня он представляет метаинформацию к данным.
Если коротко, то работает так:
class M(Meta):
a = int
b = str, 'default'
l = list, []
l2 = list, [], True
n = None # Только None, как пустое, но присутствующее значение!
n2 = lambda x: x, None # Тоже, что и 'n'
m = M()
assert 'a' not in m
assert 'b' in m
assert m.b == 'default'
m.a = '1'
assert 'a' in m
assert m.a == 1
assert m.a == m['a']
assert 'n' in m
assert m.n is None
m2 = M()
assert m.l is m2.l
assert m.l2 not is m2.l2
# можно продолжать и дальше в том же духе...
# coding: utf-8
__all__ = ['Meta', 'meta_to_dict', 'dict_to_meta']
class CastDescriptor(object):
def __init__(self, name, cast_func, *args):
self.name = name
self.cast_func = cast_func
if len(args) == 1:
self.default = args[0]
elif len(args) > 1:
raise TypeError, '{0} takes 2 or 3 arguments ({1} given)'.format(
self.__class__.__name__, len(args))
def __set__(self, inst, value):
inst.__data__[self.name] = self.cast_func(value)
def __get__(self, inst, owner):
try:
return inst.__data__[self.name]
except KeyError, exc:
if hasattr(self, 'default'):
return self.default
else:
raise AttributeError, self.name
def __delete__(self, inst):
del inst.__data__[self.name]
class MetaCast(type):
def __init__(cls, name, base, cls_dict):
cls.__data__ = {}
cls.__need_cast_in_init__ = []
for c in reversed(cls.__mro__):
if type(c) is MetaCast:
cls.__data__.update(c.__data__)
for key, value in cls_dict.items():
if key.startswith('__') and key.endswith('__'):
continue
elif hasattr(value, '__call__'):
setattr(cls, key, CastDescriptor(key, value))
elif value is None:
cls.__data__[key] = None
delattr(cls, key)
elif isinstance(value, tuple) or isinstance(value, list):
if len(value) == 2:
func, default, need_cast_in_init = value + (None,)
elif len(value) == 3:
func, default, need_cast_in_init = value
else:
raise RuntimeError, (key, value)
cls.__data__[key] = default
if func is not None:
setattr(cls, key, CastDescriptor(key, func, default))
else:
delattr(cls, key)
if need_cast_in_init:
cls.__need_cast_in_init__.append(key)
else:
raise RuntimeError, (key, value)
class Meta(object):
__metaclass__ = MetaCast
def __init__(self, data=None):
self.__dict__['__data__'] = type(self).__data__.copy()
for key in type(self).__need_cast_in_init__:
setattr(self, key, self.__data__[key])
if data:
for key, value in data.items():
self.__data__[key] = value
setattr(self, key, value)
def __setitem__(self, key, value):
setattr(self, key, value)
def __getitem__(self, key):
return self.__data__[key]
def __delitem__(self, key):
del self.__data__[key]
def __setattr__(self, attr, value):
t = type(self)
if attr in t.__dict__ and isinstance(t.__dict__[attr], CastDescriptor):
t.__dict__[attr].__set__(self, value)
else:
self.__data__[attr] = value
def __getattr__(self, attr):
try:
return self.__data__[attr]
except KeyError, exc:
raise AttributeError, attr
def __delattr__(self, attr):
if attr in type(self).__dict__:
type(self).__dict__[attr].__del__(self)
else:
del self.__data__[attr]
def __iter__(self):
return iter(self.__data__)
def __dir__(self):
return self.__data__.keys()
def __contains__(self, key):
return key in self.__data__
def __len__(self):
return len(self.__data__)
def __repr__(self):
return '<{0}({1})>'.format(self.__class__.__name__, repr(self.__data__))
def meta_to_dict(meta):
d = {}
for key in meta:
value = meta[key]
if isinstance(value, Meta):
value = meta_to_dict(value)
d[key] = value
return d
def dict_to_meta(dict):
m = Meta(dict)
for key, value in dict.items():
if isinstance(key, __builtins__.dict):
m[key] = dict_to_meta(value)
return m
И тесты, чтобы лучше понять, как оно работает. Я не стал особо вычищать их от зависимости к моему коду, но думаю ясно, что
Meta, это часть
zpk.
# coding: utf-8
import sys
import os
from nose.tools import *
import zpk
def test_1_meta():
meta = zpk.Meta(dict(a=1, b='s'))
eq_(meta.a, 1)
eq_(meta.a, meta['a'])
eq_(meta.b, 's')
eq_(meta.b, meta['b'])
assert_raises(AttributeError, getattr, meta, 'c')
meta.c = True
eq_(meta.c, True)
eq_(meta.c, meta['c'])
class Meta(zpk.Meta):
i = int
s = str
b = bool, False
v = None
meta = Meta()
assert 'i' not in meta
assert_raises(AttributeError, getattr, meta, 'i')
assert 's' not in meta
assert_raises(AttributeError, getattr, meta, 's')
assert 'b' in meta
eq_(meta.b, False)
eq_(meta.b, meta['b'])
assert 'v' in meta
eq_(meta.v, None)
meta.v = 1
eq_(meta['v'], 1)
meta.v = 'str'
eq_(meta.v, 'str')
meta.i = 3.14
assert 'i' in meta
eq_(meta.i, 3)
meta['s'] = 13
assert 's' in meta
eq_(meta.s, '13')
meta['b'] = True
assert 'b' in meta
eq_(meta.b, True)
eq_(meta.i, meta['i'])
eq_(meta.s, meta['s'])
eq_(meta.b, meta['b'])
eq_(meta.v, meta['v'])
class Meta2(Meta):
l = list, [], True
t = tuple, ()
d = dict, {}
meta1 = Meta2()
meta2 = Meta2()
assert 'b' in meta1
eq_(meta1.b, False)
assert 'b' in meta2
eq_(meta2.b, False)
meta1.b = True
eq_(meta1.b, True)
eq_(meta2.b, False)
assert meta1.l is not meta2.l
assert meta1.t is meta2.t
assert meta1.d is meta2.d