Reputation: 268
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.
Upvotes: 1
Views: 2702
Reputation: 23783
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
Upvotes: 2
Reputation: 18438
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:
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'),
}
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.
Use @Daniel's answer to this question. Clean and understandable for someone else reading your code.
Upvotes: 2
Reputation: 42788
Do it the other way round:
class Foo:
def __init__(self, name=None):
self.name = name
foo1 = Foo('foo1')
foo_dict = {
foo1.name: foo1
}
Upvotes: 1