2

I'm implementing a class in Python which has many properties. Here's an example of a property:

class Test:
    def __init__(self):
        self._value = None

    def get_value(self):
        if self._value is None:
            self._value = datetime.datetime.now()
        return self._value

    def set_value(self, new_value):
        self._value = new_value

    value = property(get_value, set_value)

This seems like a lot of code.

Is there a more concise way to implement this pattern? I'm using PyCharm if that makes any difference.

5
  • 1
    What's the pattern? There's a lot of different things you could abstract away. Commented Feb 9, 2022 at 15:56
  • One example does not establish a pattern. (Unless you want datetime.datetime.now to provide the initial value for all the properties.) Commented Feb 9, 2022 at 16:12
  • @chepner The real case actually has nothing to do with datetime, I was trying to provide a concise example. Commented Feb 9, 2022 at 16:25
  • At the very least, you need to provide two examples, which show how two properties might differ from each other. Commented Feb 9, 2022 at 16:27
  • Hi Ian, per your recently deleted Meta question about history of programming, see this similar request for a site on MSE: meta.stackexchange.com/questions/97786/… Commented Feb 11, 2022 at 20:48

2 Answers 2

2

If you have same logic/validation for some of your properties you can write your own descriptor(Properties are descriptors).

Here for example I accept only string with length higher than 3. Instead of two properties, one for first_name and one for last_name, I created a descriptor ValidName and use it for both first_name and last_name:

class ValidName:
    def __set_name__(self, owner, name):
        self.name = f"_{name}"

    def __set__(self, instance, value):
        if len(value) < 3:
            raise ValueError("The lenght must be larger than 3.")
        setattr(instance, self.name, value)

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


class Person:
    first_name = ValidName()
    last_name = ValidName()

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
Sign up to request clarification or add additional context in comments.

5 Comments

Always wanted to use descriptors. Nice example of validation. I assume the __get__ could be tweaked to achieve calculated results like the datetime in the OPs question.
@JLPeyret Yes. They are handy where we have repetitive getters and setters in properties that doing exactly same thing. Getting default values as you mentioned, or validation in setter on integers or strings and etc.
Thanks, my use case isn't validation it's initializing resource intensive properties (expensive database queries, loading images etc). I suppose I can use this pattern but pass in a function or lambda for the initializer?
@IanNewson you can do anything you want in __get__ and __set__. treat them as if they are properties(properties implement descriptor protocol), but you abstract away the logic instead of writing them over and over again in separate properties. Of course the logic needs to be same...
@IanNewson docs have a good tutorial at docs.python.org/3/howto/descriptor.html just reading through it myself. Powerful stuff. I think you’d just pass in a calc function to the descriptor constructor. See their example.
0

You can use the property decorator:

class Test:

    def __init__(self):
        self._value = None

    @property
    def value(self):
        if self._value is None:
            self._value = datetime.datetime.now()
        return self._value

Of course, you could also get almost the same behavior in this case by writing:

    def __init__(self):
        self.value = datetime.datetime.now()

The difference being that the solution with @property will be read-only by default (see the docs on adding setter and deleter methods), while the latter solution creats a read/write attribute.

1 Comment

Thanks, I do actually have setters in most cases. The important part of my code is that I need the property to be lazily initialised, but also able to be overridden by the caller.

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.