5

For some context to the question - I am using lazy loading to defer full initialization of certain properties in a class until the point at which they are needed (if at all) as they can be computationally expensive to calculate.

My question is - in python if when calculating the value of the property an error occurs, or the value can't be calculated, is raising an exception acceptable or is it an objectively bad idea?

I am aware of this question: Best practices: throwing exceptions from properties - and actually reworded this question after going over the points in it.

However I am really looking for a definitive answer with regards to python. For example should a getter always return a value even if that value is None? In other languages, such as c#, there are clear recommendations on how to design properties.

AVOID throwing exceptions from property getters.

Property getters should be simple operations and should not have any preconditions. If a getter can throw an exception, it should probably be redesigned to be a method. Notice that this rule does not apply to indexers, where we do expect exceptions as a result of validating the arguments

http://msdn.microsoft.com/en-us/library/ms229006.aspx

Would the same be true in python? Should this property really be a method? I can see how in a strongly typed language such as c# this could be an issue but am not really sure if the holds true here. Testing in the debugger it works as expected.

To be clear I am testing something like the following.

class A(object):
    def __init__(self):
        self.__x = None

    @property
    def x(self):
        if not self.__x:
            self.__x = calculate_x()
            if not some_test(self.__x):
                # Is this a bad idea?
                raise ValueError('Get x error {}'.format(self.__x))
        return self.__x

    @x.setter
    def x(self, value):
        if not some_test(value):
            raise ValueError('Set x error {}'.format(value))
        self.__x = value

I have been RTFM and a lot about properties but can't seem to see anything that either uses it or else warns against it. Is this all perfectly acceptable or have I created some kind of monstrous antipattern from hell?

The reason I am trying this is because I was think of something like a "lazy" descriptor that allows me to quickly markup properties. e.g.

from functools import wraps

    class Descriptor(object):
        def __init__(self, func):
            self.func = func
        def __get__(self, obj, type=None):
            value = self.func(obj)
            setattr(obj, self.func.__name__, value)
            return value

    def lazy(func):
        return wraps(func)(Descriptor(func))

then

class A(object):
    def __init__(self):
        self.__x = None

    @lazy
    def x(self):
        # where calculate_x might raise an exception
        return calculate_x()
Fraser
  • 15,275
  • 8
  • 53
  • 104

2 Answers2

4

After more reading and coming back to this I am going to answer my own question and say no - this is not recommended based on the PEP 8 Style Guide.

https://www.python.org/dev/peps/pep-0008/

Note 3: Avoid using properties for computationally expensive operations;
the attribute notation makes the caller believe that access is (relatively) 
cheap.

I realise this is paraphrasing Christian's answer somewhat - but I was looking for some kind of official documentation or guidance on this kind of pattern.

Fraser
  • 15,275
  • 8
  • 53
  • 104
1

I think this question is more appropriate for the Software Engineering Stack. But anyway: I would try to avoid throwing exceptions in getters, it's somewhat surprising when a field blows suddenly in your face. But C# also has Lazy fields which can do exactly the same - so from real world experience I would say: try to avoid that problem, unless there are strong reasons to make something lazy loading, in which case it can be ok.

Alternatives are providing an Initialize method which would try or (my favorite) add a method which returns the value, but uses a backing field internally to cache the value after the first usage.

like this:

class bla(object):
   expensive = None
   def get_expensive():
      if not expensive:
          expensive =compute_expensive()
      return expensive
Fraser
  • 15,275
  • 8
  • 53
  • 104
Christian Sauer
  • 10,351
  • 10
  • 53
  • 85
  • Yes it is exactly a lazy loading that caches the value on first access - see my edit for my intended usage...hmm perhaps I will post on SES, I did worry it was non objective question - but tried to be specific as possible. My issue is I don't know python well enough - and actually like this feature in some other languages (hence my trying to replicate). However most have some special implementation of it - again like C# (didn't know it had Lazy fields - cool!) – Fraser Feb 15 '18 at 06:22