0

I would like to get the auto-completion on a dictionary or some similar object in order to access objects. Here what I currently have:

class Foo:
   def DoFoo(self):
      pass

list = {"x": Foo(), "y": Foo()}

Now I would like to be able to do:

list.x.DoFoo()

How can I populate a list with objects that I can later access with the IPython auto-completion?

Do I need some kind of "class factory" as I saw here?

Community
  • 1
  • 1
nowox
  • 25,978
  • 39
  • 143
  • 293
  • But `list` does not have an attribute `x`. (And why are you calling dict "list"?) – Daniel Roseman Sep 16 '15 at 11:16
  • 1
    `list` is a terrible name for a list, because it shadows the built-in `list` class, which will surprise many people. It is an _atrocious_ name for a dictionary. – Kevin J. Chase Sep 16 '15 at 11:16
  • I don't know what I am actually calling. I am a bit confused with the all the pythonic hash types. – nowox Sep 16 '15 at 11:17
  • 1
    You built a dictionary, which has a built-in way to do exactly what you're asking for: `foo_instances['x'].DoFoo()`. Why doesn't that work for you? – Kevin J. Chase Sep 16 '15 at 11:18
  • By the way, what do you mean by "auto-completion"? I can't think of anything in Python even remotely like that. Names must match exactly, or the Python interpreter will raise a `NameError`. – Kevin J. Chase Sep 16 '15 at 11:21
  • @KevinJ.Chase. Sorry I wasn't clear with that. I am using IPython as an interactive Python terminal. I would like IPython to show me the available keys of my dictionary when I hit the tab key. I guess this is the same as asking to write it like: `tree.key1.objectMethod1()` – nowox Sep 16 '15 at 11:23
  • But that is not valid! Python is not Javascript, you can't access keys of a dicts as if they were attributes. – Daniel Roseman Sep 16 '15 at 11:26
  • @DanielRoseman Precisely, so I am asking how I can modify my code to get this possibility. I am open to use something else than a dict. – nowox Sep 16 '15 at 11:27
  • I wouldn't suggest you to do that... since you are in ipython, why not just type `mydict.keys()` (`mydict` refeering to your `list` dictionary). The `keys()` method will show you all the keys (kind of the *autocomplete* that you want). – Imanol Luengo Sep 16 '15 at 12:44

2 Answers2

1

You need to override __getattr__:

class Foo(object):

    attr_a = 'default_attr_a_value'
    attr_b = 'default_attr_b_value'

    def __init__(self, *args, **kwargs):
        if len(args) >= 2:
            self.attr_a = args[0]
            self.attr_b = args[1]
        if len(args) == 1:
            self.attr_a = args[0]

        # holds the "emulated" attributes
        self._properties = kwargs

    def __getattr__(self, name):
        if name in self._properties:
            return self._properties[name]
        else:
            raise AttributeError

    def __hasattr__(self, name):
        return name in self._properties

bar = Foo('modified_value_of_attr_a', **{'aaa':1,'bbb':2})

print bar.attr_a
print bar.attr_b
print bar.aaa

try:
    print bar.sss
except AttributeError:
    print 'Attribute error raised!'

list_attrs = ['attr_a', 'aaa', 'sss']
for a in list_attrs:
    if hasattr(bar, a):
        print 'bar, instance of Foo, has an attribute named "%s"' % a
    else:
        print 'bar, instance of Foo, doesn\'t have an attribute named "%s"' % a

More to read

Community
  • 1
  • 1
StefanNch
  • 2,569
  • 24
  • 31
  • You really shouldn't make `_properties` a class attribute. Also, `name in self._properties` already is a boolean value so you don't need any if/else, just `return name in self._properties` – bruno desthuilliers Sep 16 '15 at 12:15
  • @brunodesthuilliers you are correct regarding the `if/else`, as for _properties I wanted to separate attributes from "emulated" attributes. Blckknght's answer uses the built-in `__dict__` – StefanNch Sep 16 '15 at 13:04
  • uhu sorry I missed the last `__init__()` line - but then you really don't need the class level attribute, it only makes things more confusing specially (but not only obviously ) for Python newcomers. – bruno desthuilliers Sep 16 '15 at 15:27
  • @brunodesthuilliers I'm used to declare/initiate variables before using them, sometimes I forget that for python is redundant – StefanNch Sep 18 '15 at 06:15
1

An instance of a custom class can work as a container for attributes you set on it. This can start trivially simple:

class MyContainer:
   pass

a = MyContainer()
a.x = Foo()
a.y = Foo()

a.x.DoFoo()

You can make it a little more complicated to get the ability to pass in the attributes you want as part of the constructor (using the same semantics as the dict constructor):

def MyContainer:
    def __init__(self, vals={}, **kwvals):
        self.__dict__.update(vals, **kwvals)

a = MyContainer({"x":Foo()}, y=Foo())   # there are multiple ways to pass the args

a.x.DoFoo()

If you don't need to add or remove values after you create a container of this sort, you can instead use the standard library's collections.namedtuple class factory. You pass it a class name and a sequence of attribute names and it will return you a new class.

from collections import namedtuple

A = namedtuple("A", ["x", "y"])
a = A(Foo(), y=Foo())       # can use positional or keyword arguments

a.x.DoFoo()      # this still works!
a.x = "something else"      # but this will raise an error
Blckknght
  • 100,903
  • 11
  • 120
  • 169