Форум сайта python.su
Задача нарисовать следующее:
# Тут всё просто
o = ds(dict(a=1, b=2))
assert o.a == 1 and o.b == 2
assert o['a'] == 1 and o['b'] == 2
o.a = 3
o['b'] = 4
assert o.a == 3 and o.b == 4
assert o['a'] == 3 and o['b'] == 4
# А теперь сложности
class DS(ds):
a = str # Функции приведения типов
b = int
o = DS(dict(a=1, b=2))
assert o.a == '1' and o.b == 2
assert o['a'] == '1' and o['b'] == 2
o.a = 3
o['b'] = '4'
assert o.a == '3' and o.b == 4
assert o['a'] == '3' and o['b'] == 4
Офлайн
property http://docs.python.org/library/functions.html#property ?
descriptors http://users.rcn.com/python/download/Descriptor.htm ?
Отредактировано (Май 1, 2009 19:27:57)
Офлайн
Я думал о дескрипторах (проперти тут уж совсем не в тему). Но разве на них можно реализовать ту функциональность, которая мне нужна? В том-то и дело, что функции приведения должны быть функциями, а набор ключей-атрибутов ограничен только возможностями питона.
Чувствую, что решение где-то рядом, но вот только где?
Офлайн
something like this?
class Wrapper(object):
def __init__(self, _type):
self.__type = _type
def __get__(self, obj, objtype):
return self.__type(self.__a)
def __set__(self, obj, val):
self.__a = val
class TypeCast(type):
def __init__(cls, name, base, cls_dict):
cls.a = Wrapper(cls.a)
cls.b = Wrapper(cls.b)
class A(object):
__metaclass__ = TypeCast
def __init__(self, a, b):
self.a = a
self.b = b
a = str
b = int
o = A(1,2)
assert o.a == '1' and o.b == 2
o.a = 3
o.b = '4'
assert o.a == '3' and o.b == 4
class Wrapper(object):
def __init__(self, _type):
self.__type = _type
def __get__(self, obj, objtype):
return self.__type(self.__a)
def __set__(self, obj, val):
self.__a = val
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
a = Wrapper(str)
b = Wrapper(int)
o = A(1,2)
assert o.a == '1' and o.b == 2
o.a = 3
o.b = '4'
assert o.a == '3' and o.b == 4
Отредактировано (Май 1, 2009 20:19:11)
Офлайн
Можешь взять готовое - casting traits: http://code.enthought.com/projects/traits/docs/html/traits_user_manual/defining.html#id6
… но возможно это лишние зависимости и избыточная функциональность, хотя мне кажется что это то что нужно.
Если самому делать, то нужно делать сетап класса в __new__ + дескрипторы для полного доступа к аттрибутам, или более универсально - метакласс + дескрипторы.
Офлайн
crchemist, вот это уже интересней. Спасибо, поиграюсь.
На данный момент система построена по принципу второго примера, но хочется больше прозрачности.
Просто действительно уже чёрт знает сколько времени не могу въехать в метаклассы. Раз десять пытался! Чувствую себя каким-то дебилом… Может в этот раз получится? Всё-таки конкретная надобность…
poltergeist, casting traits это, конечно, интересно, но слишком уж много лишнего.
Офлайн
Поигрался. Ещё раз спасибо. Кажется я начинаю понимать, что там имелось ввиду… :-)
Тема закрыта.
Если вдруг интересно, вот конечный (вроде) вариант:
# coding: utf-8
__all__ = ['ds']
class cast_descriptor(object):
def __init__(self, name, cast_func):
self.name = name
self.cast_func = cast_func
def __get__(self, inst, owner):
try:
return inst.__data__[self.name]
except KeyError, exc:
raise AttributeError, self.name
def __set__(self, inst, value):
inst.__data__[self.name] = self.cast_func(value)
def __delete__(self, inst):
del inst.__data__[self.name]
class meta_cast(type):
def __init__(cls, name, base, cls_dict):
for key, func in cls_dict.items():
if key.startswith('__') and key.endswith('__'):
continue
else:
setattr(cls, key, cast_descriptor(key, cls_dict[key]))
class ds(object):
__metaclass__ = meta_cast
def __init__(self, data=None):
self.__data__ = {}
if data:
for key, value in data.items():
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 __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__)
Офлайн
Экскурс по метаклассам, специально для ZZZ :)
1. Для создания своего собственного метакласса, нужно наследовать новосозданный класс от метакласса type:
class MyMeta(type):
'''A new custom metaclass'''
class MyMeta(type):
'''A new custom metaclass'''
def __init__(mymeta_self, *args, **kwargs):
print 'mymeta_self: ', mymeta_self
mymeta_self.hello = 'Hello, World!'
class A(object):
#этот класс будем называть пользовательским
__metaclass__ = MyMeta
class B(object):
#и этот тоже
__metaclass__ = MyMeta
print A.hello
output:
mymeta_self: <class '__main__.A'>
mymeta_self: <class '__main__.B'>
Hello, World!
class MyMeta(type):
'''A new custom metaclass'''
def __init__(cls, *args, **kwargs):
cls.__repr__ = lambda self: 'object belongs to MyMeta'
class A(object):
__metaclass__ = MyMeta
def __init__(self):
print 'init'
a = A()
print a
output:
init
object belongs to MyMeta
class MyMeta(type):
'''A new custom metaclass'''
def __call__(cls, *args, **kwargs):
return 'hello'
class A(object):
__metaclass__ = MyMeta
a = A()
print a
output:
hello
class MyMeta(type):
def __call__(cls, arg):
builtin_class = arg.__class__
class New(cls, builtin_class):
pass
instance = builtin_class.__new__(New, arg)#<- arg is needed here by immutable objects
builtin_class.__init__(instance, arg)#<- __init__ - by mutable ones
return instance
class Cooler(object):
__metaclass__ = MyMeta
def do_cool(self):
print 'It is not an ordinary method - it is really a cool one!!!'
a = Cooler({'a': 'b'})
print a
a.do_cool()
b = Cooler(123)
print b
b.do_cool()
c = Cooler([1,2,3])
print c
c.do_cool()
d = Cooler('abc')
print d
d.do_cool()
e = Cooler(('a', 'b'))
print e
e.do_cool()
output:
{'a': 'b'}
It is not an ordinary method - it is really a cool one!!!
123
It is not an ordinary method - it is really a cool one!!!
[1, 2, 3]
It is not an ordinary method - it is really a cool one!!!
abc
It is not an ordinary method - it is really a cool one!!!
('a', 'b')
It is not an ordinary method - it is really a cool one!!!
Офлайн
ZANЯ польщён… Спасибо. Большое.
Экскурс по метаклассам, специально для ZZZ :-)
Офлайн
Ну что, ваши усилия не пропали даром. Начал встраивать в уже работающий проект и довёл всё это до такого вида.
Класс называется 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
# 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
Офлайн