Уведомления

Группа в Telegram: @pythonsu

#1 Май 1, 2009 19:06:07

ZZZ
От: Москва
Зарегистрирован: 2008-04-03
Сообщения: 2161
Репутация: +  26  -
Профиль   Адрес электронной почты  

Структура данных...

Задача нарисовать следующее:

# Тут всё просто
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
Я понимаю, что нужно использовать __getattribute__, но как?
Просто не могу найти правильный подход… Мета-классы я так и не осилил… Ничего, прийдёт ещё время…

Есть вариант “через жопу”. В __init__ найти все новые методы, отдельно их сложить и использовать __getattr__… Но мне совсем не нравится.

P.S. Решил, что такими странными вопросами не стоит забивать умы новичков…



Офлайн

#2 Май 1, 2009 19:26:22

crchemist
От:
Зарегистрирован: 2008-07-09
Сообщения: 379
Репутация: +  0  -
Профиль   Отправить e-mail  

Структура данных...

property http://docs.python.org/library/functions.html#property ?
descriptors http://users.rcn.com/python/download/Descriptor.htm ?



Отредактировано (Май 1, 2009 19:27:57)

Офлайн

#3 Май 1, 2009 19:47:41

ZZZ
От: Москва
Зарегистрирован: 2008-04-03
Сообщения: 2161
Репутация: +  26  -
Профиль   Адрес электронной почты  

Структура данных...

Я думал о дескрипторах (проперти тут уж совсем не в тему). Но разве на них можно реализовать ту функциональность, которая мне нужна? В том-то и дело, что функции приведения должны быть функциями, а набор ключей-атрибутов ограничен только возможностями питона.
Чувствую, что решение где-то рядом, но вот только где?



Офлайн

#4 Май 1, 2009 20:15:50

crchemist
От:
Зарегистрирован: 2008-07-09
Сообщения: 379
Репутация: +  0  -
Профиль   Отправить e-mail  

Структура данных...

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)

Офлайн

#5 Май 1, 2009 20:33:02

poltergeist
От:
Зарегистрирован: 2007-02-28
Сообщения: 522
Репутация: +  0  -
Профиль   Отправить e-mail  

Структура данных...

Можешь взять готовое - casting traits: http://code.enthought.com/projects/traits/docs/html/traits_user_manual/defining.html#id6
… но возможно это лишние зависимости и избыточная функциональность, хотя мне кажется что это то что нужно.

Если самому делать, то нужно делать сетап класса в __new__ + дескрипторы для полного доступа к аттрибутам, или более универсально - метакласс + дескрипторы.



Офлайн

#6 Май 1, 2009 23:27:26

ZZZ
От: Москва
Зарегистрирован: 2008-04-03
Сообщения: 2161
Репутация: +  26  -
Профиль   Адрес электронной почты  

Структура данных...

crchemist, вот это уже интересней. Спасибо, поиграюсь.
На данный момент система построена по принципу второго примера, но хочется больше прозрачности.
Просто действительно уже чёрт знает сколько времени не могу въехать в метаклассы. Раз десять пытался! Чувствую себя каким-то дебилом… Может в этот раз получится? Всё-таки конкретная надобность…

poltergeist, casting traits это, конечно, интересно, но слишком уж много лишнего.



Офлайн

#7 Май 2, 2009 01:00:34

ZZZ
От: Москва
Зарегистрирован: 2008-04-03
Сообщения: 2161
Репутация: +  26  -
Профиль   Адрес электронной почты  

Структура данных...

Поигрался. Ещё раз спасибо. Кажется я начинаю понимать, что там имелось ввиду… :-)
Тема закрыта.

Если вдруг интересно, вот конечный (вроде) вариант:

# 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__)



Офлайн

#8 Май 2, 2009 02:19:58

ZAN
От:
Зарегистрирован: 2007-06-10
Сообщения: 403
Репутация: +  10  -
Профиль   Отправить e-mail  

Структура данных...

Экскурс по метаклассам, специально для ZZZ :)

1. Для создания своего собственного метакласса, нужно наследовать новосозданный класс от метакласса type:

class MyMeta(type):
'''A new custom metaclass'''
2. Каждый раз, когда у инициализируемого класса встречается магический атрибут __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!
3. Если у метакласса не переопределен метод __call__, то происходит обычная инициализация экземпляра пользовательского класса:
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
4. Но если он переопределен, то вместо создания нового объекта возвращается то, что вернет __call__ метакласса:
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
5. А теперь создадим такой метакласс, который будет создавать экземпляры built-in типов, но расширенные пользовательским классом.
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!!!'
print



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!!!



Офлайн

#9 Май 2, 2009 03:58:45

ZZZ
От: Москва
Зарегистрирован: 2008-04-03
Сообщения: 2161
Репутация: +  26  -
Профиль   Адрес электронной почты  

Структура данных...

ZAN
Экскурс по метаклассам, специально для ZZZ :-)
Я польщён… Спасибо. Большое.
Кажется переход количества в качество, наконец, состоялся! Область применения надо искать и использовать. Вот сейчас я с этой мыслью пересплю и займусь… :-)



Офлайн

#10 Май 3, 2009 17:41:58

ZZZ
От: Москва
Зарегистрирован: 2008-04-03
Сообщения: 2161
Репутация: +  26  -
Профиль   Адрес электронной почты  

Структура данных...

Ну что, ваши усилия не пропали даром. Начал встраивать в уже работающий проект и довёл всё это до такого вида.
Класс называется 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



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version