简体   繁体   中英

Set dictionary key to attribute of class instance

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.

Do it the other way round:

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

foo1 = Foo('foo1')
foo_dict = {
    foo1.name: foo1
}

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM