Всем привет.
Это продолжение ответов на вопросы и задания по Python с сайта pyobject.ru.
Disclaimer:
1. Написать базовый класс Observable, который бы позволял наследникам:
a. при передаче **kwargs заносить соответствующие значения как атрибуты
b. сделать так, чтобы при print отображались все публичные атрибуты
При обращении к атрибуту, происходит следующее (тут мог и соврать):
1. проверяется сам объект на наличие атрибута в нем. Пользовательские атрибуты хранятся в атрибуте __dict__ объекта.
2. проверяется атрибут __dict__ типа объекта через __class__.__dict__ объекта
3. проверяются родители типа
4. выполняется метод __getattribute__ для новых классов
5. выполняется метод __getattr__
Для того, чтобы значения были доступны как атрибуты, достаточно обновить атрибут __dict__ объекта, что и происходит в методе __init__.
Метод __str__ используется для отображения «информативного представления» объекта. В данном случае мы выводим все публичные атрибуты объекта и их значения.
2. Написать класс, который бы по всем внешним признакам был бы словарем, но позволял обращаться к ключам как к атрибутам.
Для того, чтобы класс по всем внешним признакам был словарем, мы его от этого словаря и унаследуем. А чтобы к ключам можно было обращаться как к атрибутам, в классе определим метод __getattr__, который вызывается в тех случаях, когда атрибут не найден в словаре объекта, его типа и родителях.
3. Пункт 2 с усложнением: написать родительский класс XDictAttr так, чтобы у наследника динамически определялся ключ по наличию метода get_KEY.
Над этим заданием я просидел достаточно много времени, потому что у меня никак не получалось сделать его модно, стильно, молодежно и не костыльно. Что получилось — решать вам.
В кратце о том, как оно работает:
1. метод __getitem__ позволяет перехватывать обращения через []
Когда мы попадаем в метод то, в начале, пробуем получить значение с помощью простого обращения к словарю через вызов родительского __getitem__. Если же элемент в словаре не был найден, то пробуем получить значение с помощью метода __getattr__ в качестве аргумента которого используем get_KEY
2. метод get() переопределяет стандартное поведение get() типа dict. Мы вначале пробуем получить атрибут с помощью __getattr__ и аргумента get_KEY. И только в случае неудачи вызываем родительский метод get, который обратится к непереопределенному __getitem__ и проверит наличие аргумента в словаре.
3. метод __getattr__ позволяет обрабатывать все остальные ситуации. Для начала мы пробуем получить значение через вызов родительского __getitem__. И вот тут, в случае неудачи, в дело вступает грязный хак. Я так и не смог придумать, как исключить возможность рекурсии, потому что необходимо проверить наличие атрибута get_KEY, которое происходит через вызов __getattr__ объекта. Ну вы поняли. В итоге я имел строку вида get_get_get_get_get_foo.
Недостаток данной реализации — атрибуты, начинающиеся с get_, вызовут AttributeError. Это отображено в doctest.
4. Написать класс, который регистрирует свои экземпляры и предоставляет интерфейс итератора по ним
Я понимаю, что я не знаю многого в Python, но как по мне данное задание стоило отнести к метаклассам по следующей причине:
Данный пример подразумевает, что итератор мы берем у типа, а не объекта класса. Да и интерпретатор питона ругался похожей ошибкой, когда я пробовал обернуть __iter__ в classmethod или staticmethod.
Поэтому моя реализация следующая:
P.S. да, и еще в примере Reg instance, у меня же Reg object. Может тут мой косяк?
Наверное самый противоречивый раздел, ведь с метаклассами я практически не имел дела по причине: «Если вы не знаете нужен ли вам метакласс, то он вам не нужен». Но все же попробуем.
Вопросы:
Я не буду цитировать документацию (Data model). В дополнение напишу: на хабре есть хорошие статьи об объектной структуре в общем и метаклассах в частности. Тут и тут. Спасибо их авторам.
Задания:
1. Реализовать дескрипторы, которые бы фиксировали тип атрибута.
Двумя словами — дескриптор это атрибут класса нового вида с определенным поведением.
Статья о дескрипторах тут.
2. Реализовать базовый класс (используя метакласс), который бы фиксировал тип атрибута.
Вот тут у меня была проблема — не совсем понял задание. По примеру автора атрибуты height, path являются классовыми. Подразумевалось ли, что метакласс должен фиксировать и переносить их в объект или только фиксировать — не понятно. Я реализовал второе.
Класс Property берем такой же, как и в ответе к заданию 1.
Все, что нам надо сделать это обернуть публичные атрибуты создаваемого класса в Property. Так как метакласс получает словарь с атрибутами класса, то это не проблема.
Дополнительно сделал проверки на то, является ли атрибут публичным или методом.
3. Реализовать базовый класс (используя метакласс) и дескрипторы, которые бы на основе класса создавали SQL-схему (ANSI SQL) для модели.
Просьба прокомментировать данный способ, потому что мне кажется, что можно было сделать красивей.
Для реализации были сделаны:
1. базовый дескриптор Property с классовым счетчиком, который позволяет отсортировать атрибуты в той последовательности, в который они были созданы.
2. дескрипторы Integer и Str с собственными __str__, которые используются при создании SQL представления модели.
3. метакласс TableMeta, который получает список полей модели и создает SQL представление модели.
4. базовый класс Table, который предоставляет классовый метод, который возвращает SQL представление модели.
На этом все. За комментарии и критику — спасибо.
Это продолжение ответов на вопросы и задания по Python с сайта pyobject.ru.
Disclaimer:
Предложенные ответы не стоит рассматривать как самые правильные. Более того, я надеюсь что более опытные люди укажут на ошибки, неточности и плохие места. За что им заранее спасибо.
Классы
1. Написать базовый класс Observable, который бы позволял наследникам:
a. при передаче **kwargs заносить соответствующие значения как атрибуты
b. сделать так, чтобы при print отображались все публичные атрибуты
При обращении к атрибуту, происходит следующее (тут мог и соврать):
1. проверяется сам объект на наличие атрибута в нем. Пользовательские атрибуты хранятся в атрибуте __dict__ объекта.
2. проверяется атрибут __dict__ типа объекта через __class__.__dict__ объекта
3. проверяются родители типа
4. выполняется метод __getattribute__ для новых классов
5. выполняется метод __getattr__
Для того, чтобы значения были доступны как атрибуты, достаточно обновить атрибут __dict__ объекта, что и происходит в методе __init__.
Метод __str__ используется для отображения «информативного представления» объекта. В данном случае мы выводим все публичные атрибуты объекта и их значения.
class Observable(object): """ Base class for attributes from dict. >>> class X(Observable): ... pass >>> x = X(foo=1, bar="Test", _barr='hidden', baz=(5, 6)) >>> print x X(bar=Test, foo=1, baz=(5, 6)) >>> print x.foo 1 >>> print x.bar Test >>> print x._barr hidden >>> print x.baz (5, 6) """ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __str__(self): return '%s(%s)' % (self.__class__.__name__,\ (', '.join('%s=%s' % (key, val) for (key, val)\ in self.__dict__.iteritems() if not key.startswith('_'))))
2. Написать класс, который бы по всем внешним признакам был бы словарем, но позволял обращаться к ключам как к атрибутам.
Для того, чтобы класс по всем внешним признакам был словарем, мы его от этого словаря и унаследуем. А чтобы к ключам можно было обращаться как к атрибутам, в классе определим метод __getattr__, который вызывается в тех случаях, когда атрибут не найден в словаре объекта, его типа и родителях.
class DictAttr(dict): """ Base class for JS-style dict. >>> x = DictAttr([('one', 1), ('two', 2), ('three', 3)]) >>> print x {'three': 3, 'two': 2, 'one': 1} >>> print x['three'] 3 >>> print x.get('two') 2 >>> print x.one 1 >>> print x.test Traceback (most recent call last): ... AttributeError """ def __getattr__(self, name): try: return self[name] except KeyError: raise AttributeError
3. Пункт 2 с усложнением: написать родительский класс XDictAttr так, чтобы у наследника динамически определялся ключ по наличию метода get_KEY.
Над этим заданием я просидел достаточно много времени, потому что у меня никак не получалось сделать его модно, стильно, молодежно и не костыльно. Что получилось — решать вам.
В кратце о том, как оно работает:
1. метод __getitem__ позволяет перехватывать обращения через []
Когда мы попадаем в метод то, в начале, пробуем получить значение с помощью простого обращения к словарю через вызов родительского __getitem__. Если же элемент в словаре не был найден, то пробуем получить значение с помощью метода __getattr__ в качестве аргумента которого используем get_KEY
2. метод get() переопределяет стандартное поведение get() типа dict. Мы вначале пробуем получить атрибут с помощью __getattr__ и аргумента get_KEY. И только в случае неудачи вызываем родительский метод get, который обратится к непереопределенному __getitem__ и проверит наличие аргумента в словаре.
3. метод __getattr__ позволяет обрабатывать все остальные ситуации. Для начала мы пробуем получить значение через вызов родительского __getitem__. И вот тут, в случае неудачи, в дело вступает грязный хак. Я так и не смог придумать, как исключить возможность рекурсии, потому что необходимо проверить наличие атрибута get_KEY, которое происходит через вызов __getattr__ объекта. Ну вы поняли. В итоге я имел строку вида get_get_get_get_get_foo.
Недостаток данной реализации — атрибуты, начинающиеся с get_, вызовут AttributeError. Это отображено в doctest.
class XDictAttr(dict): """ >>> class X(XDictAttr): ... def get_foo(self): ... return 5 ... def get_bar(self): ... return 12 ... def get_get_z(self): ... return 42 >>> x = X({'one': 1, 'two': 2, 'three': 3}) >>> x {'one': 1, 'three': 3, 'two': 2} >>> x['one'] 1 >>> x.three 3 >>> x.bar 12 >>> x['foo'] 5 >>> x.get('foo', 'missing') 5 >>> x.get('bzz', 'missing') 'missing' >>> x.get_bar() 12 >>> x.get_foz() Traceback (most recent call last): ... AttributeError >>> x.get_get_z() 42 >>> x.get('get_z') 42 >>> x.get_z Traceback (most recent call last): ... AttributeError """ def __getattr__(self, name): try: return super(XDictAttr, self).__getitem__(name) except KeyError: if not name.startswith('get_'): return getattr(self, 'get_%s' % name)() else: raise AttributeError def __getitem__(self, key): try: return super(XDictAttr, self).__getitem__(key) except KeyError: return getattr(self, 'get_%s' % key)() def get(self, key, default=None): try: return getattr(self, 'get_%s' % key)() except AttributeError: return super(XDictAttr, self).get(key, default)
4. Написать класс, который регистрирует свои экземпляры и предоставляет интерфейс итератора по ним
Я понимаю, что я не знаю многого в Python, но как по мне данное задание стоило отнести к метаклассам по следующей причине:
>>> for i in Reg: ... print i <Reg instance at 0x98b6ecc> <Reg instance at 0x98b6fec> <Reg instance at 0x98ba02c>
Данный пример подразумевает, что итератор мы берем у типа, а не объекта класса. Да и интерпретатор питона ругался похожей ошибкой, когда я пробовал обернуть __iter__ в classmethod или staticmethod.
Поэтому моя реализация следующая:
class RegBase(type): def __iter__(cls): return iter(cls._instances) class Reg(object): """ >>> x = Reg() >>> x # doctest: +ELLIPSIS <__main__.Reg object at 0x...> >>> y = Reg() >>> y # doctest: +ELLIPSIS <__main__.Reg object at 0x...> >>> z = Reg() >>> z # doctest: +ELLIPSIS <__main__.Reg object at 0x...> >>> for i in Reg: # doctest: +ELLIPSIS ... print i <__main__.Reg object at 0x...> <__main__.Reg object at 0x...> <__main__.Reg object at 0x...> """ __metaclass__ = RegBase _instances = [] def __init__(self): self._instances.append(self)
P.S. да, и еще в примере Reg instance, у меня же Reg object. Может тут мой косяк?
Метаклассы и дескрипторы
Наверное самый противоречивый раздел, ведь с метаклассами я практически не имел дела по причине: «Если вы не знаете нужен ли вам метакласс, то он вам не нужен». Но все же попробуем.
Вопросы:
Я не буду цитировать документацию (Data model). В дополнение напишу: на хабре есть хорошие статьи об объектной структуре в общем и метаклассах в частности. Тут и тут. Спасибо их авторам.
Задания:
1. Реализовать дескрипторы, которые бы фиксировали тип атрибута.
Двумя словами — дескриптор это атрибут класса нового вида с определенным поведением.
Статья о дескрипторах тут.
class Property(object): """ >>> class Image(object): ... height = Property(0) ... width = Property(0) ... path = Property('/tmp/') ... size = Property(0) >>> img = Image() >>> img.height = 340 >>> img.height 340 >>> img.path = '/tmp/x00.jpeg' >>> img.path '/tmp/x00.jpeg' >>> img.path = 320 Traceback (most recent call last): ... TypeError """ def __init__(self, value): self.__value = value self.__value_type = type(value) def __get__(self, obj, objtype=None): return self.__value def __set__(self, obj, value): if type(value) == self.__value_type: self.__value = value else: raise TypeError
2. Реализовать базовый класс (используя метакласс), который бы фиксировал тип атрибута.
Вот тут у меня была проблема — не совсем понял задание. По примеру автора атрибуты height, path являются классовыми. Подразумевалось ли, что метакласс должен фиксировать и переносить их в объект или только фиксировать — не понятно. Я реализовал второе.
Класс Property берем такой же, как и в ответе к заданию 1.
Все, что нам надо сделать это обернуть публичные атрибуты создаваемого класса в Property. Так как метакласс получает словарь с атрибутами класса, то это не проблема.
Дополнительно сделал проверки на то, является ли атрибут публичным или методом.
class ImageMeta(type): def __new__(mcs, name, bases, dct): for key, val in dct.iteritems(): if not key.startswith('_') and not hasattr(val, '__call__'): dct[key] = Property(val) return type.__new__(mcs, name, bases, dct) class ImageBase(object): """ >>> class Image(ImageBase): ... height = 0 ... path = 'tmp' ... ... def foo(self): ... return 'bar' >>> img = Image() >>> img.height = 340 >>> img.height 340 >>> img.path = '/tmp/x00.jpeg' >>> img.path '/tmp/x00.jpeg' >>> img.path = 320 Traceback (most recent call last): ... TypeError >>> hasattr(img.foo '__call__') True """ __metaclass__ = ImageMeta
3. Реализовать базовый класс (используя метакласс) и дескрипторы, которые бы на основе класса создавали SQL-схему (ANSI SQL) для модели.
Просьба прокомментировать данный способ, потому что мне кажется, что можно было сделать красивей.
Для реализации были сделаны:
1. базовый дескриптор Property с классовым счетчиком, который позволяет отсортировать атрибуты в той последовательности, в который они были созданы.
2. дескрипторы Integer и Str с собственными __str__, которые используются при создании SQL представления модели.
3. метакласс TableMeta, который получает список полей модели и создает SQL представление модели.
4. базовый класс Table, который предоставляет классовый метод, который возвращает SQL представление модели.
class Property(object): """ >>> class Image(object): ... size = Property(int) ... name = Property(basestring) >>> img = Image() >>> img.size = 0 >>> img.size 0 >>> img.name = '/tmp/img' >>> img.name '/tmp/img' >>> img.size = '~' Traceback (most recent call last): ... TypeError >>> img.name = ['/tmp/', 'img'] Traceback (most recent call last): ... TypeError >>> img.__class__.__dict__['size'].counter 0 >>> img.__class__.__dict__['name'].counter 1 """ counter = 0 def __init__(self, value_type): self.__value = None self.__value_type = value_type self.counter = Property.counter Property.counter += 1 def __get__(self, obj, objtype=None): return self.__value def __set__(self, obj, value): if isinstance(value, self.__value_type): self.__value = value else: raise TypeError class Integer(Property): def __init__(self): super(Integer, self).__init__(int) def __str__(self): return self.__class__.__name__.upper() class Str(Property): def __init__(self, size): super(Str, self).__init__(basestring) self.__size = size def __str__(self): return '{0}({1})'.format('varchar', self.__size).upper() class TableMeta(type): def __new__(mcs, name, bases, dct): fields = [(attr_name, val) for (attr_name, val) in dct.items()\ if isinstance(val, Property)] fields.sort(key=lambda x: x[1].counter) sql = ',\n'.join('\t{0} {1}'.format(attr_name, val)\ for (attr_name, val) in fields) dct['__sql'] = u'CREATE TABLE {0} (\n{1}\n)'.format(name, sql) return type.__new__(mcs, name, bases, dct) class Table(object): """ >>> class Image(Table): ... height = Integer() ... width = Integer() ... path = Str(128) >>> print Image.sql() # doctest: +NORMALIZE_WHITESPACE CREATE TABLE Image ( height INTEGER, width INTEGER, path VARCHAR(128) ) """ __metaclass__ = TableMeta @classmethod def sql(cls): return cls.__dict__['__sql']
На этом все. За комментарии и критику — спасибо.
