How can I get the attribute name when working with the handle protocol in Python?

The descriptor protocol works fine, but I still have one problem that I would like to solve.

I have a handle:

class Field(object):
    def __init__(self, type_, name, value=None, required=False):
        self.type = type_
        self.name = "_" + name
        self.required = required
        self._value = value

    def __get__(self, instance, owner):
        return getattr(instance, self.name, self.value)

    def __set__(self, instance, value):
        if value:
            self._check(value)
            setattr(instance, self.name, value)
        else:
            setattr(instance, self.name, None)

    def __delete__(self, instance):
        raise AttributeError("Can't delete attribute")

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value if value else self.type()

    @property
    def _types(self):
        raise NotImplementedError

    def _check(self, value):
        if not isinstance(value, tuple(self._types)):
            raise TypeError("This is bad")

These are subclasses:

class CharField(Field):
    def __init__(self, name, value=None, min_length=0, max_length=0, strip=False):
        super(CharField, self).__init__(unicode, name, value=value)
        self.min_length = min_length
        self.max_length = max_length
        self.strip = strip

    @property
    def _types(self):
        return [unicode, str]

    def __set__(self, instance, value):
        if self.strip:
            value = value.strip()

        super(CharField, self).__set__(instance, value)

And then the model class is used:

class Country(BaseModel):
    name = CharField("name")
    country_code_2 = CharField("country_code_2", min_length=2, max_length=2)
    country_code_3 = CharField("country_code_3", min_length=3, max_length=3)

    def __init__(self, name, country_code_2, country_code_3):
        self.name = name
        self.country_code_2 = country_code_2
        self.country_code_3 = country_code_3

So far, so good, it works as expected.

The only problem I have here is that we must provide the field name with every field declaration. for example "country_code_2"for a field country_code_2.

How can I get the attribute name of a model class and use it in a field class?

+6
source share
2 answers

There is a simple way, and there is a difficult way.

Python 3.6 ( ) object.__set_name__():

def __set_name__(self, owner, name):
    self.name = '_' + name

, Python , , .

Python metaclass; ( ). , :

class BaseModelMeta(type):
    def __new__(mcls, name, bases, attrs):
        cls = super(BaseModelMeta, mcls).__new__(mcls, name, bases, attrs)
        for attr, obj in attrs.items():
            if isinstance(obj, Field):
                obj.__set_name__(cls, attr)
        return cls

__set_name__() , Python 3.6 . BaseModel:

class BaseModel(object, metaclass=BaseModelMeta):
    # Python 3

class BaseModel(object):
    __metaclass__ = BaseModelMeta
    # Python 2

, __set_name__ , , , . .

+9

Python Descriptors, , 3,6. , , 60 .

, - :

def name_of(descriptor, instance):
    attributes = set()
    for cls in type(instance).__mro__:
        # add all attributes from the class into `attributes`
        # you can remove the if statement in the comprehension if you don't want to filter out attributes whose names start with '__'
        attributes |= {attr for attr in dir(cls) if not attr.startswith('__')}
    for attr in attributes:
        if type(instance).__dict__[attr] is descriptor:
            return attr

, , , , . , .

0

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


All Articles