📜 ⬆️ ⬇️

Answers to questions with PyObject. Part 2

Hello.
This is a continuation of the answers to questions and tasks for Python from the site pyobject.ru .


Disclaimer :
The proposed answers should not be considered as the most correct. Moreover, I hope that more experienced people will point out mistakes, inaccuracies and bad places. For which they thank in advance.


Classes


1. Write the base class Observable, which would allow successors:
a. when sending ** kwargs, enter the corresponding values ​​as attributes
b. make it so that when print all public attributes are displayed
')
When accessing an attribute, the following happens (it could lie here):
1. The object itself is checked for the presence of an attribute in it. Custom attributes are stored in the __dict__ attribute of an object.
2. The attribute __dict__ of the object type is checked through the __class __.__ dict__ object
3. parents are checked
4. __getattribute__ method is executed for new classes
5. __getattr__ method is executed

In order for the values ​​to be available as attributes, it is enough to update the attribute of the __dict__ object, which happens in the __init__ method.
The __str__ method is used to display an “informative view” of an object. In this case, we display all the public attributes of the object and their values.

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. Write a class that would be a dictionary by all external signs, but allowed to refer to keys as attributes.
In order for the class to be a dictionary by all its external features, we will inherit it from this dictionary. And so that the keys can be accessed as attributes, we define the __getattr__ method in the class, which is called when the attribute is not found in the object dictionary, its type, and its parents.

  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. Point 2 with complication: write the parent class XDictAttr so that the heir dynamically determines the key by the presence of the get_KEY method.
I spent a lot of time on this task, because I could not make it fashionable, stylish, youth and not a crutch. What happened - you decide.

In short, how it works:
1. __getitem__ method allows intercepting calls through []
When we get to the method, at the beginning, we try to get a value using a simple reference to the dictionary through a call to the parent __getitem__. If the item in the dictionary was not found, then we try to get the value using the __getattr__ method for whose argument we use get_KEY
2. The get () method overrides the standard get () behavior of type dict. We first try to get the attribute using __getattr__ and the get_KEY argument. And only in case of failure, we call the parent get method, which will access the unregistered __getitem__ and check for the presence of an argument in the dictionary.
3. The __getattr__ method allows you to handle all other situations. To begin, we try to get the value through a call to the parent __getitem__. And here, in case of failure, a dirty hack comes into play. I could not figure out how to eliminate the possibility of recursion, because it is necessary to check for the presence of the get_KEY attribute, which occurs through a call to the __getattr__ object. Well, you understand. In the end, I had a string of the form get_get_get_get_get_foo.

The disadvantage of this implementation is that attributes beginning with get_ will cause an AttributeError. This is displayed in the 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. Write a class that registers its instances and provides an iterator interface for them
I understand that I do not know much in Python, but as for me this task should have been attributed to metaclasses for the following reason:
  >>> for i in Reg: ... print i <Reg instance at 0x98b6ecc> <Reg instance at 0x98b6fec> <Reg instance at 0x98ba02c> 

This example implies that we take the iterator from the type, not the class object. And the python interpreter swore a similar error when I tried to wrap __iter__ in a classmethod or staticmethod.
Therefore, my implementation is as follows:
  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) 

PS Yes, and in the example of Reg instance, I have the same Reg object. Maybe here my cant?

Metaclasses and descriptors


Probably the most controversial section, because I almost did not deal with metaclasses because: “If you don’t know if you need a metaclass, then you don’t need it”. But still try.

Questions:
I will not quote the documentation (Data model). In addition, I will write: there are good articles on Habré about object structure in general and metaclasses in particular. Here and here . Thanks to their authors.

Tasks:
1. Implement descriptors that would fix the attribute type.
Two words - the descriptor is an attribute of the class of a new type with a specific behavior.
An article about the descriptors here.
  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. Implement the base class (using the metaclass), which would fix the attribute type.
Here I had a problem - I did not quite understand the task. According to the author’s example, the height, path attributes are class attributes. Whether it was meant that the metaclass should fix and transfer them to an object or just fix is ​​not clear. I implemented the second.

Class Property take the same as in the answer to the task 1.
All we need to do is wrap the public attributes of the class being created in Property. Since the metaclass gets a dictionary with class attributes, this is not a problem.
Additionally made a check on whether the attribute is public or method.
  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. Implement the base class (using the metaclass) and descriptors that would create a SQL schema (ANSI SQL) for the model based on the class.
Please comment on this method, because it seems to me that it was possible to make more beautiful.
To implement were made:
1. A basic Property Descriptor with a class counter that allows you to sort the attributes in the order in which they were created.
2. Integer and Str descriptors with their own __str__, which are used when creating the SQL representation of the model.
3. Metaclass TableMeta, which gets a list of model fields and creates a SQL representation of the model.
4. The base class Table, which provides a class method that returns a SQL representation of the model.

  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'] 


That's all. For comments and criticism - thanks.

Source: https://habr.com/ru/post/147783/


All Articles