Question: Implement MultiKeyDict class in Python with alias() method for creating aliases. Existing code fails when original key is deleted. Need fix

Python OOP problem
MultiKeyDict class, which is almost identical to the dict class. Creating an instance of MultiKeyDict class should be similar to creating an instance of dict class:
multikeydict1 = MultiKeyDict(x=1, y=2, z=3)
multikeydict2 = MultiKeyDict([('x', 1), ('y', 2), ('z', 3)])

print(multikeydict1['x']) # 1
print(multikeydict2['z']) # 3

A feature of the MultiKeyDict class should be the alias() method, which should allow aliases to be given to existing keys. The reference to the created alias should not differ from the reference to the original key, that is, the value has two keys (or more if there are several aliases) when the alias is created:
multikeydict = MultiKeyDict(x=100, y=[10, 20])

multikeydict.alias('x', 'z') # add key 'x' alias 'z'
multikeydict.alias('x', 't') # add alias 't' to key 'x'
print(multikeydict['z']) # 100
multikeydict['t'] += 1
print(multikeydict['x']) # 101

multikeydict.alias('y', 'z') # now 'z' becomes an alias of the key 'y'
multikeydict['z'] += [30]
print(multikeydict['y']) # [10, 20, 30]

The value must remain available by alias even if the original key was removed:
multikeydict = MultiKeyDict(x=100)

multikeydict.alias('x', 'z')
del multikeydict['x']
print(multikeydict['z']) # 100

Keys must take precedence over aliases. If some key and alias are the same, then all operations when accessing them must be performed with the key:
multikeydict = MultiKeyDict(x=100, y=[10, 20])

multikeydict.alias('x', 'y')
print(multikeydict['y']) # [10, 20]

I can't implement such a feature, please give me ideas how it can be done!!!
multikeydict = MultiKeyDict(x=100)

multikeydict.alias('x', 'z')
del multikeydict['x']
print(multikeydict['z']) # 100

my code does not work with this test
multikeydict = MultiKeyDict(x=100)

multikeydict.alias('x', 'z')
del multikeydict['x']
print(multikeydict['z']) #100 

class MultiKeyDict(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.aliases = {}

    def alias(self, key, alias):
        self.aliases[alias] = key

    def __getitem__(self, key):
        if key in self.aliases:
            key = self.aliases[key]
        return super().__getitem__(key)

    def __setitem__(self, key, value):
        if key in self.aliases:
            key = self.aliases[key]
        super().__setitem__(key, value)
    
    def __delitem__(self, key):
        if key in self.aliases:
            del self.aliases[key]
        super().__delitem__(key)


multikeydict = MultiKeyDict(x=100, y=[10, 20])

multikeydict.alias('x', 'z')
multikeydict.alias('x', 't')
print(multikeydict['z'])
multikeydict['t'] += 1
print(multikeydict['x'])

multikeydict.alias('y', 'z')
multikeydict['z'] += [30]
print(multikeydict['y'])



Answer:
Some remarks:

As the specification says that keys should have precedence over aliases (when both exist), you should first test key membership on self before looking in aliases. Your methods first check for membership in aliases...

As a value must continue to exist when a key is deleted for which there are still alias(es), I would suggest storing the values wrapped in a list (that just has that value as only member). This way you can reference that list in an alias entry. When the key is deleted, the alias will still have the reference to the list and can still act on it.


Here is how that could look:
class MultiKeyDict(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.aliases = {}
        # wrap each value in a list of size 1:
        for key, value in self.items():
            super().__setitem__(key, [value])
            
    def alias(self, key, alias):
        self.aliases[alias] = super().__getitem__(key)

    def __getitem__(self, key):
        if key in self:
            return super().__getitem__(key)[0]
        return self.aliases[key][0]

    def __setitem__(self, key, value):
        if key in self:
            super().__getitem__(key)[0] = value
        elif key in self.aliases:
            self.aliases[key][0] = value
        else:
            super().__setitem__(key, [value])

    def __delitem__(self, key):
        if key in self:
            return super().__delitem__(key)
        del self.aliases[key]