1
import random

class Foo(object):
    def __init__(self):
        self._name = ''.join([chr(65 + random.randrange(0, 26)) for _ in range(3)])
        self._data = None

    def __getattr__(self, item):
        dashitem = '_' + item
        # if dhasattr(self, dashitem):
        # is a bad idea because hasattr calls getattr
        # is in self.__dict__.keys() also a bad idea?
        if dashitem in self.__dict__.keys():
            return self.__dict__[dashitem]

obj = Foo()
obj._data = [random.randrange(10, 100) for _ in range(random.randrange(1, 11))]

So far, so good. I can call obj.name and get backobj._name`

In [2]: obj.name
Out[2]: 'QZB'

In [3]: obj.data
Out[3]: [54]

Then I try pickling the object:

import pickle
pickle.dumps(obj)

is a no-go though.

  File "<ipython-input-7-a7748eba906b>", line 2, in <module>
    pickle.dumps(obj)
  File "/usr/local/Cellar/python/2.7.14_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1380, in dumps
    Pickler(file, protocol).dump(obj)
  File "/usr/local/Cellar/python/2.7.14_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 224, in dump
    self.save(obj)
  File "/usr/local/Cellar/python/2.7.14_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 306, in save
    rv = reduce(self.proto)
  File "/Users/vishal/virtenvs/fm/bin/../lib/python2.7/copy_reg.py", line 84, in _reduce_ex
    dict = getstate()

How do I do what I want with __getattr__ above (return _<attr> if <attr> is not found without breaking other normal behaviour??

Vishal
  • 2,097
  • 6
  • 27
  • 45
  • If the point is to have read-only attributes, using `properties` (computed attributes) would be a better choice - it's explicit, the properties are thru inspection, and you only expose the one you choose to expose. – bruno desthuilliers Jun 28 '18 at 08:59
  • The properties are not read-only, and are not easily explicitly enumerable. I was using properties and it got a bit out of hand. – Vishal Jun 28 '18 at 09:16
  • A `property` without a setter IS read-only, and yes you can easily find out a class properties thru inspection - but I fail to imagine why you'd want to do so here. – bruno desthuilliers Jun 28 '18 at 09:23
  • Oh and yes, while we're at it : `dashitem in self.__dict__.keys()` is counter-productive - you're generating a list and do a O(N) lookup, when you could avoid the list generation and have a O(1) lookup with `dashitem in self.__dict__:`. – bruno desthuilliers Jun 28 '18 at 09:25
  • Thank you. I was trying to solve the problems I thought I had because I didn't understand the `__getattr__` and `getattr()` worked clearly enough. I have better clarity now. – Vishal Jun 28 '18 at 09:26

1 Answers1

1

The easiest approach would be to simply protect against infinite recursion and let the default logic handle the rest. It is important to not implicitly return None (which you do in the omitted else-case) and break the default behaviour (which is to raise an AttributeError when an attribute is not found):

# ...
def __getattr__(self, item):
    # all double-underscore attrs should be found before calling the fallback
    if item.startswith('__'):  
        raise AttributeError
    return getattr(self, '_'+item)

See also this question and the docs.

user2390182
  • 72,016
  • 6
  • 67
  • 89
  • Thank you for the answer and the link. Both are helpful. I didn't realize that I needed to return something or raise an exception. – Vishal Jun 28 '18 at 09:23