2

I am new to python. I'm trying to create a configuration class with required validators. In below code snippet, accessing variable 'a' using python class and instance of class is returning a different value. Whether this is a proper design or should i initialise var 'a' only in the class constructor and do validation in the setter method.

class IntField:
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('expecting integer')
        instance.__dict__[self.name] = value

    def __set_name__(self, owner, name):
        self.name = name

class DefaultConfig:
    a = IntField()

    def __init__(self):
        self.a = 2

print(DefaultConfig.a)
print(DefaultConfig().a)

output:

<__main__.IntField object at 0x10c34b550>
2
5
  • @Netwave Then how to set var a to 2? Commented Nov 8, 2018 at 13:45
  • 4
    As a descriptor, an instance of IntField is intended to be assigned to a class attribute, not an instance attribute. Your code is behaving as it should; you don't have any particular reason to access the class attribute directly, despite it being syntactically legal to do so. Commented Nov 8, 2018 at 13:52
  • 1
    @Netwave As chepner points out, it has to be a class attribute to function. Commented Nov 8, 2018 at 14:04
  • @chepner I agree it's working as expected. I just want to know whether it's right way to do it. Commented Nov 8, 2018 at 14:14
  • IMO, this is fine. Commented Nov 8, 2018 at 14:50

1 Answer 1

1

I just want to know whether it's right way to do it

Rather asking for opinion only answer, by I will try to be as objective as I can.

Your code behaves as expected as long as instances attributes are processed:

>>> c = DefaultConfig()
>>> c.a = 'foo'
Traceback (most recent call last):
  File "<pyshell#88>", line 1, in <module>
    c.a = 'foo'
  File "<pyshell#83>", line 10, in __set__
    raise ValueError('expecting integer')
ValueError: expecting integer
>>> c.a = 4
>>> c.a
4

When inspecting DefaultConfig.a, the __get__ function is still used with instance=None. So you can choose one of 2 possible ways:

  • be transparent and show what the attribute actually is (what you currently do)
  • insist on the descriptor magic and return the default value (here 2).

For that latter way, code could become:

class IntField:
    def __get__(self, instance, owner):
        if instance is None:
            return getattr(owner, '_default_' + self.name, self)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('expecting integer')
        instance.__dict__[self.name] = value

    def __set_name__(self, owner, name):
        self.name = name

class DefaultConfig:
    a = IntField()
    _default_a = 2
    def __init__(self):
        self.a = self._default_a

The trick here is that by convention, the default value for an attribute x is expected to be _default_x.

In that case, you will get:

print(DefaultConfig.a)
2
Sign up to request clarification or add additional context in comments.

1 Comment

Good answer. You could probably omit setting the defaults in __init__ if you adjusted __get__ to always look for the default value as a fallback.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.