1

I have a dictionary which stores objects of a class foo. Class foo has an attribute Name. For every instance, I want the Name attribute to be the key to the instance in the dictionary. All instances of the class will be defined inside the dictionary.

class foo:
    def __init__(self):
        self.Name = None  #self.Name should equal "self"

foo_dict = {
'foo1' = foo()
}

#(foo.Name should equal 'foo1')

How can I set the Name attribute to be the key to the instance in the dictionary?

Comment if specifications are needed.

Jacob
  • 268
  • 1
  • 7
  • 20
  • How do you access the ```Name``` attribute? – wwii Dec 06 '14 at 15:57
  • In the initialization – Jacob Dec 06 '14 at 15:57
  • Can you access an instance variable before it is instantiated? Have you made any attempts to solve this? – wwii Dec 06 '14 at 15:59
  • Well I do not believe so. As far as attempts, I know that I need to use `self.Name = foo_dict[???]`. But since the instantiation is happening after the dictionary key/ dictionary entry is created, can't I somehow assign the key to `self.Name`? – Jacob Dec 06 '14 at 16:03
  • What would the `.Name` be if you do: `f = foo(); foo_dict = {'foo1': f, 'foo2': f,}` ? – Savir Dec 06 '14 at 16:07
  • I'm not certain, but that case will not appear in my program. All instances of the class will be defined inside the dictionary. – Jacob Dec 06 '14 at 16:08
  • `foo.Name` is a class attribute (which is shared by all instances of `class foo` -- so your question makes little sense. – martineau Dec 06 '14 at 17:00

3 Answers3

2

Seems like you need a reference to the instance to do what you want. If you build the dictionary with a comprehension, you can create instance references and use them.

class Foo(object):
    def __init__(self, n = None):
        self.name = n

d = {f.name:f for f in (Foo(n) for n in 'abcd')}

>>> d
{'a': <__main__.Foo object at 0x03DF9710>, 'c': <__main__.Foo object at 0x03E01250>, 'b': <__main__.Foo object at 0x03DF9A50>, 'd': <__main__.Foo object at 0x03E01290>}
>>> 
>>> d = {f.name:f for f in (Foo(n) for n in [1])}
>>> d
{1: <__main__.Foo object at 0x03E01B50>}
>>> foo_dict = {}
>>> foo_dict.update(d)
>>> foo_dict
{1: <__main__.Foo object at 0x03E01B50>}
>>> 

I stumbled upon this SO answer the other day. Using that class decorator/descriptor, you could create a class factory that produces Foo objects and keeps track of the current object and a counter for the next object.

class InnerClassDescriptor(object):
    '''allows access to the outer class and its attributes

    decorator/descriptor
    an instance of a nested inner class can access the outer class and its attributes
    '''
    def __init__(self, cls):
        self.cls = cls
    def __get__(self, instance, outerclass):
        class Wrapper(self.cls):
              outer = instance
        Wrapper.__name__ = self.cls.__name__
        return Wrapper

class FooFactory(object):
    next_foo = 0
    this_foo = None
    @InnerClassDescriptor
    class Foo(object):
        def __init__(self):
            # print 'Foo,__init__, next_foo = ', self.outer.next_foo
            self.name = 'Foo' + str(self.outer.next_foo)
            self.outer.next_foo += 1
            self.outer.this_foo = self 

Usage:

ff = FooFactory()
d = {ff.this_foo.name:ff.Foo()}
for k, v in d.items():
    print k, v.name

>>> 
Foo0 Foo0
>>>

This relies on the dictionary item value being evaluated before the key - which seems to be the case for Python 2.7

Community
  • 1
  • 1
wwii
  • 23,232
  • 7
  • 37
  • 77
2

I can't possibly stress enough how BAD this is... Please, please, use this only for educational purposes. It's crumbly, unreliable... BAD If you change anything in your code, it'll stop working. It is dirty. It is possibly non portable... OMG... I think a few kittens were killed when I hit Post Your Answer

import inspect
import re

class Foo(object):
        def __init__(self):
            r = re.compile(
                r"\W+['\"](?P<name>\w+)['\"]\W+%s\W+"
                % self.__class__.__name__
            )
            caller_frame = inspect.currentframe().f_back
            code_context = inspect.getframeinfo(caller_frame).code_context
            match = r.match(''.join(code_context))
            if match:
                self.name = match.groupdict()['name']
                print "Assigned name: %s" % self.name
            else:
                raise Exception("This wasn't called as it was supposed to")

if __name__ == "__main__":
    foo_dict = {
        'foo1': Foo(),
        'foo2': Foo(),
    }

But it does what you seem to be asking:

borrajax@borrajax:/tmp$ python ./test.py 
Assigned name: foo1
Assigned name: foo2

Now, what I would do is:

Option 1:

Pass the name in the initialization.

Possibly the simplest, most maintainable and that leaves the code in a much clearer state (important if someone else reads your code)

class Foo(object):
        def __init__(self, name):
            self.name = name
            print "Assigned name: %s" % self.name

if __name__ == "__main__":
    foo_dict = {
        'foo1': Foo('foo1'),
        'foo2': Foo('foo2'),
    }

Option 2:

Create your own dict class and overwrite the __setitem__ method (see also Subclassing Python dictionary to override __setitem__ and How to "perfectly" override a dict?):

class Foo(object):
    pass

class MyDict(dict):
    def __setitem__(self, key, val):
        if not isinstance(val, Foo):
            raise TypeError("My dict only accepts %s" % Foo)
        val.name = key
        print "Assigned name: %s" % val.name
        return super(MyDict, self).__setitem__(key, val)

if __name__ == "__main__":
    foo_dict = MyDict()
    foo_dict['foo1'] = Foo()
    foo_dict['foo2'] = Foo()
    foo_dict['foo3'] = 1

Prints:

borrajax@borrajax:/tmp$ python ./test.py 
Assigned name: foo1
Assigned name: foo2
Traceback (most recent call last):
  File "./stack64.py", line 17, in <module>
    foo_dict['foo3'] = 1
  File "./stack64.py", line 8, in __setitem__
    raise TypeError("My dict only accepts %s" % Foo)
TypeError: My dict only accepts <class '__main__.Foo'>

This has the disadvantage of magically adding attributes (the .name) to the instances of Foo when assigned to the dictionary, which can cause name conflicts (if your Foo class already had a .name, this method would change its value). In general, I'd stay away of methods that magically add attributes to instances in the middle of the execution.

Option 3:

Use @Daniel's answer to this question. Clean and understandable for someone else reading your code.

Community
  • 1
  • 1
Savir
  • 17,568
  • 15
  • 82
  • 136
1

Do it the other way round:

class Foo:
    def __init__(self, name=None):
        self.name = name

foo1 = Foo('foo1')
foo_dict = {
    foo1.name: foo1
}
Daniel
  • 42,087
  • 4
  • 55
  • 81
  • 1
    I said, "All instances of the class will be defined inside the dictionary". This means the instance will be instantiated inside the dictionary and be assigned a dictionary key. `foo_dict = {'foo1': Foo()}` – Jacob Dec 06 '14 at 16:14