As explained in Greg's answer, optimization-wise, there is nothing to worry about.
Now, regarding the maintainability problem (i.e. having to keep two copies of the same data in sync), a solution would be to use references to the object's ID/name field as keys to the associative container, if the language allows it.
Note that since strings are usually immutable objects, one may need to wrap them in a mutable object and store a reference to that instead. The Python example shown in the question is wrong; this would be a possible correct version:
# class of objects that need ID or name
class MyClass:
# mutable class to wrap the immutable string value
class Name:
def __init__(self, value: str):
self.value = value
# enable direct string comparisons
def __eq__(self, other): return self.value == other
def __le__(self, other): return self.value < other
def __hash__(self): return hash(self.value)
def __init__(self, name: str):
self._name = MyClass.Name(name)
@property
def name(self) -> str:
return self._name.value
@name.setter
def name(self, value: str):
self._name.value = value
# associative container from names to objects
my_instances = {}[]
# add an object
obj = MyClass("Bob")
my_instances[objmy_instances.name] =append((obj._name, obj)) # use wrapper object as key
# retrieve an object
print(my_instances["Bob"]next(o for n, o in my_instances if n == "Bob"))
# change an object's name
obj.name = "Tony"
print(my_instances["Tony"]next(o for n, o in my_instances if n == "Tony")) # works fine