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[obj.name] = obj
print(my_instances["Bob"])
# change an object's name
obj.name = "Tony"
print(my_instances["Tony"]) # works fine