2

Inside a Python class, I have to do multiple checks for several private variables, e. g. self.__a, self.__b and self.__c that are contained inside a list. The procedure would then start like this:

   for var in [self.__a, self.__b, self.__c]:
       ...

My problem now is, that some (and possibly all) of the variables contained inside the list might have not been set up by now, and my program stops due to the occurring AttributeError.

The only way I figured out to set up this list so that it contains only the variables that already exist is to write (in this case three) try-except-clauses of which every single one adds one of the variable to the list. But I think there has to be a better way to solve this task.

How can I do this in a more elegant way?

Peter Wood
  • 23,859
  • 5
  • 60
  • 99
mindm49907
  • 278
  • 2
  • 10
  • How about replacing `self.__a`, `self.__b`, `self.__c` with a "private" dict `self.priv` containing keys `a`, `b`, `c`. Then you could `iteritems` over it. – eumiro Feb 11 '15 at 08:29
  • In addition to answer below maybe change the loop to `for var in [i for i in [self.__a, self.__b, self.__c] if i]` – ziddarth Feb 11 '15 at 08:40
  • 1
    Why have the private variables not been set up? – Peter Wood Feb 11 '15 at 08:41
  • @PeterWood because the check is very general, it's run at several points where variables are entered or changed, and at some point, may not all variables have been set to far. One could have initialized them with `None` on instantiating the object, but the structure of the class now already works this way. – mindm49907 Feb 11 '15 at 11:35

3 Answers3

3

You could modify the __getattr__ method within your class to prevent returning AttributeError in these cases, a la:

class Foo(object):
    def __getattr__(self, name):
        if hasattr(self, name):
            return object.__getattr__(self, name)
        else:
            return None

Obviously you could extend this to cases where name is in ["__a", "__b", "__c"] or startswith("__").

Then use within your method similar to before, but filter out the Nones:

for var in filter(None, [self.__a, self.__b, self.__c]):
    ...

Note: You should really only do this if you know what it's doing, and you understand the risks it entails. In most cases you should try-except and seek forgiveness on AttributeError.

thiruvenkadam
  • 4,170
  • 4
  • 27
  • 26
Ian Clark
  • 9,237
  • 4
  • 32
  • 49
  • Hello Ian, thank you for your answer, but I need this behavior only for a subset of variables, not for all private variables inside my class. But your tip might be useful in other cases! – mindm49907 Feb 11 '15 at 08:41
  • 1
    OK sweet, didn't know if it was really limited to `__a, __b, __c` or not, so thought I'd just let you know that you can override the lookup method if appropriate. I agree that if this isn't the case then using `hasattr()` is more appropriate :) – Ian Clark Feb 11 '15 at 08:44
  • I really didn't know that I could do that, so I now might can use your suggestion another time. – mindm49907 Feb 11 '15 at 08:47
  • This may work, but there are two problems: `None` does not mean non-existing attribute; and overriding `__getattr__` will affect all pieces of code that uses `Foo` class. `AttributeError` will never be thrown, and a typo in the variable's name somewhere will probably stay hidden for a very long time. – Quan To Feb 11 '15 at 09:24
  • 1
    @btquanto yes, which is why I'd advise caution, but it's still useful to know how things like this work under the hood. As per my first comment, I'd stick with your solution for this *particular case*. – Ian Clark Feb 11 '15 at 09:30
  • Note explaining this added as well :) – Ian Clark Feb 11 '15 at 09:35
  • This should be considered bad as you cannot differentiate between variables that has None value as well as variables that were not initialized – thiruvenkadam Feb 11 '15 at 10:03
1

You can do this:

for attr in ['__a', '__b', '__c']:
    try:
        var = getattr(self, attr)
        # Do something
    except AttributeError:
        pass # or do something else

You can also do this:

NON_EXISTING = object() # put this as a global variable somewhere for reusability

for attr in ['__a', '__b', '__c']:
    var = getattr(self, attr, NON_EXISTING)
    if var is not NON_EXISTING:
        # Do something
    else: # If you just want to skip, you will not even need this
        pass # or do something else

Or this:

for attr in ['__a', '__b', '__c']:
    if hasattr(self, attr):
        var = getattr(self, attr)
        # Do something
    else:
        pass # or do something else
Quan To
  • 697
  • 3
  • 10
  • Thank you btquanto, the first part of your answer is exactly what I needed! – mindm49907 Feb 11 '15 at 08:42
  • I prefer the second, and third (just added) part of the answer, which essentially results in the same thing. However, it does not use exception for flow control (which is a bad practice). Consider reading http://stackoverflow.com/questions/7274310/python-using-exceptions-for-control-flow-considered-bad – Quan To Feb 11 '15 at 08:59
  • Isn't it in Python "better to ask for forgiveness than for permission"? – mindm49907 Feb 11 '15 at 11:30
  • In this case, it does not really matter, since `has_attr`, and `getattr(obj, attr, default)` are implemented with exception handling underneath, as I remember. – Quan To Feb 11 '15 at 11:58
0

Something similar than what you did i.e. writing three try/except is to get the value if it exists or None otherwise using getattr.

obj = MyObject()
for var_name in ["__a", "__b", "__c"]:
    var = getattr(obj, var_name, None)
    if var is not None:
        do_stuff()

I prefer this solution over modifying the way your object behaves when asked to retrieve any of its attribute as the answer of @IanClark suggest

El Bert
  • 2,958
  • 1
  • 28
  • 36