1

So I'm trying to make a class that extends list, with the extra ability that certain special attributes are mapped to refer to certain parts of the list. Using this Py3k doc page, I created the following code. The idea is that (say I have a sequence instance of this class) sequence.seq should act exactly like sequence[0], and sequence.index should act exactly like sequence[2], etc.

It seems to work great, except that I can't seem to access the class variable mapping attributes to the list.

I found this SO question, but either the answer there is wrong, or something is different within methods. I could also use self.__class__.__map__, but since I need the class variable inside __getattribute__, that sends me into an infinite recursion loop.

>>> class Sequence(list):
...      __map__ = {'seq': 0,
...                 'size': 1,
...                 'index': 2,
...                 'fdbid': 3,
...                 'guide': 4,
...                 'factors': 5,
...                 'clas': 6,
...                 'sorttime': 7,
...                 'time': 8,
...                 'res': 9,
...                 'driver': 10 }
...      
...      def __setattr__(self, name, value): # "Black magic" meta programming to make certain attributes access the list
...           print('Setting atr', name, 'with val', value)
...           try:
...                self[__map__[name]] = value
...           except KeyError:
...                object.__setattr__(self, name, value)
...      
...      def __getattribute__(self, name):
...           print('Getting atr', name)
...           try:
...                return self[__map__[name]]
...           except KeyError:
...                return object.__getattribute__(self, name)
...      
...      def __init__(self, seq=0, size=0, index=0, fdbid=0, guide=None, factors=None, 
...           sorttime=None, time=None):
...                super().__init__([None for i in range(11)]) # Be sure the list has the necessary length
...                self.seq = seq
...                self.index = index
...                self.size = size
...                self.fdbid = fdbid
...                self.guide = ''
...                self.time = time
...                self.sorttime = sorttime
...                self.factors = factors
...                self.res = ''
...                self.driver = ''
... 
>>> a = Sequence()
Setting atr seq with val 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 31, in __init__
  File "<stdin>", line 17, in __setattr__
NameError: global name '__map__' is not defined
Community
  • 1
  • 1
Dubslow
  • 553
  • 2
  • 6
  • 15

2 Answers2

1

You access attributes with a dot (.), not with []. Python doesn't allow you to omit the self reference, so you need to access the class variable with self.__map__. So if you want to access the element at that position, you need self[self.__map__[name]].

Note that it's not a good idea to use double-underscore-sandwiched names for your own purposes. Even two leading underscores (which does name-mangling) is usually more than you need. If you just want to indicate to users that the __map__ attribute isn't part of the public API, call it _map.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • Since `Sequence` extends the `list` type, `self` is also an instance of `list`, and can be indexed as such. I believe there is no difference between `self.__map__` and `Sequence.__map__`, the style I used in my answer. And good point about preferring `_map` to `__map__`. – chepner Jul 27 '12 at 02:35
  • `self.__map__` is not the same as `Sequence.__map__`. Instances do not have the class variables, see [here](http://stackoverflow.com/questions/68645/static-class-variables-in-python). Thanks for the tip about hidden variables though, I'll be sure to change that. (And chepner's right, since Sequence subclasses list, as long as I run super().__init__(), self is in fact a list.) – Dubslow Jul 27 '12 at 02:39
  • @Dubslow: `self.__map__` and `Sequence.__map__` refer to the same object until you assign to `self.__map__`. Then `self.__map__` is created as an instance attribute separate from the class attribute. – chepner Jul 27 '12 at 02:50
  • @chepner Then accessing `self.__map__` (without assigning it) won't cause it to be added to the instance attribute dict? So I really could use `self.__map__` instead of `Sequence.__map__` without fear of adding an attribute to the instance? Neat! Sorry about being wrong, I think perhaps that link I posted needs some editing... – Dubslow Jul 27 '12 at 02:56
1

Since none of the methods are called until after Sequence is fully defined, you can refer to Sequence.__map__ without any trouble. For example:

def __setattr(self, name, value):
    print('Setting atr', name, 'with val', value)
    try:
        self[Sequence.__map__[name]] = value
    except KeyError:
        object.__setattr__(self, name, value)

As an aside, here's a demonstration that class attributes may be accessed via objects as long as an instance attribute with the same name does not also exist:

class Foo:
    i = 3
    def __init__(self, overwrite):
        if overwrite:
            self.i = 4

f = Foo(False)
id(f.i) == id(Foo.i)     # Should be True
f = Foo(True)
id(f.i) == id(Foo.i)     # Should be False
chepner
  • 497,756
  • 71
  • 530
  • 681
  • I was trying to do it without using the literal class name in the code... but at least it's a solution. I forgot about the methods-not-called-until-after-definition, thanks for the reminder ;) – Dubslow Jul 27 '12 at 02:40
  • So the summary is use `var` outside method definitions, but use `__class__.var` inside method definitions. Cool. – Dubslow Jul 27 '12 at 02:41
  • Hmm... I just tried using `self._map` "in the field" and it didn't work. It sent me into infinite recursion in `__getattribute__` trying to get `self._map`. I reverted to `Sequence._map` and that worked fine. – Dubslow Jul 27 '12 at 06:23
  • Oh, right. Sorry, I forgot that you were redefining __getattribute__, which gets called when you access an attribute of your object (but not the class). – chepner Jul 27 '12 at 12:24