Doubt regarding `__qualname__`

I create a CustomDict, which has flip functionality,

class CustomDict(dict):
  def flip(self):
    import collections.abc
    copy = {}
    for key, value in self.items():
      if(isinstance(value, collections.abc.Hashable)):
        copy[value] = key
      else:
        raise TypeError(f'unhashable type {type(value)} for {value}')
    self = copy
    return self
x = CustomDict({'a': 1, 'b': 2})
x.flip()

gives,

{1: 'a', 2: 'b'}

then, I use a modified form of dir on it,

passed = defaultdict(lambda : defaultdict(set))
failed = defaultdict(set)
combined = {'x': x}
for key, value in combined.items():
  for j in dir(value):
    try:
      qualname = eval(f'{key}.{j}.__qualname__')
      qualname = qualname.split('.')
      passed[key][qualname[0]].add(qualname[1])
    except:
      failed[key].add(j)

I get passed as,

defaultdict(<function <lambda> at 0x7f18d0c35170>,
            {'x': defaultdict(<class 'set'>,
                              {'CustomDict': {'__contains__', '__dir__',
                                              '__format__', '__getitem__',
                                              '__init_subclass__', '__reduce__',
                                              '__reduce_ex__', '__sizeof__',
                                              '__subclasshook__', 'clear',
                                              'copy', 'flip', 'fromkeys', 'get',
                                              'items', 'keys', 'pop', 'popitem',
                                              'setdefault', 'update',
                                              'values'},
                               'dict': {'__delitem__', '__eq__', '__ge__',
                                        '__getattribute__', '__gt__',
                                        '__init__', '__iter__', '__le__',
                                        '__len__', '__lt__', '__ne__',
                                        '__new__', '__repr__', '__setitem__'},
                               'object': {'__delattr__', '__setattr__',
                                          '__str__'}})})

when I do a similar check on a dict

d = {'a': 1, 'b': 2}
combined = {'d': d}

passed gives,

defaultdict(<function <lambda> at 0x7f18d0c35d40>,
            {'d': defaultdict(<class 'set'>,
                              {'dict': {'__contains__', '__delitem__',
                                        '__dir__', '__eq__', '__format__',
                                        '__ge__', '__getattribute__',
                                        '__getitem__', '__gt__', '__init__',
                                        '__init_subclass__', '__iter__',
                                        '__le__', '__len__', '__lt__', '__ne__',
                                        '__new__', '__reduce__',
                                        '__reduce_ex__', '__repr__',
                                        '__setitem__', '__sizeof__',
                                        '__subclasshook__', 'clear', 'copy',
                                        'fromkeys', 'get', 'items', 'keys',
                                        'pop', 'popitem', 'setdefault',
                                        'update', 'values'},
                               'object': {'__delattr__', '__setattr__',
                                          '__str__'}})})

but why did methods like pop, update get classified under CustomDict, whereas, __len__, __lt__, __ne__, got classified under dict.

shouldn’t only flip have been classified under CustomDict
or
everything that is classified under dict for the second case + flip should have been classified under CustomDict.

why did it classify some methods under CustomDict and others under dict?

Maybe you should ask the author of the custom version of dir. Who knows what it is doing? It could be full of bugs. I don’t even know what it is supposed to do, let alone whether it does it correctly.

Also, a couple of comments on this piece of code:

combined = {'x': x}
for key, value in combined.items():
    for j in dir(value):
        try:
            qualname = eval(f'{key}.{j}.__qualname__')
            qualname = qualname.split('.')
            passed[key][qualname[0]].add(qualname[1])
        except:
            failed[key].add(j)

You have a for-loop that loops over something that you know will always contain one item. Why use a for loop for something that you know will only execute once?

And there is no need for eval. eval() is slow (it has to parse your string, compile it to byte code, then execute it) and dangerous if somebody can sneakily trick you in eval’ing a malicious string.

for name in dir(x):
    try:
        qualname = getattr(x, name).__qualname__
        qualname = qualname.split('.')
        passed['x'][qualname[0]].add(qualname[1])
    except:
        failed['x'].add(name)

Notice that I use a better name for the loop over dir(x). I assume that, like the builtin dir, your modified dir returns a list of names. Meaningful, self-descriptive names are important.

Obviously I can’t test this, as I don’t know what your custom dir() is or where you got it.

I was testing it for a few more builtins earlier, like, list, set, so, used the for loop.

by modified form of dir, I do not mean that dir(value) is some different version of dir.
it is the same builtin dir.
the implementation inside try, except block, gives us like a modified/enhanced version of dir, which would also categorize the methods with the class they belong to, so, it is more clear from where is the method being used.
but in the above case, it also leads to confusion, that why certain methods belong to CustomDict, while others belong to dict.
the dir here
for j in dir(value).
is the same as the builtin dir, I dont use any other custom dir
in the above post, i should have probably written,

I use the builtin dir to make a kind of modified/enhanced dir

Hello, @vainaixr. This is just a simple observation-probably will not help you much- below :

  • Magic(dunder) methods listed under CustomDict also exist in dir(object).
  • And, “normal” methods of CustomDict are the ones inherited -or stolen- from dict -except flip-.
INHERITED DICT
Magic Methods Left Here, But Gave The Normal Methods To CustomDict
|
|
CUSTOMDICT
Borrowed Normal Methods From Dict And Dunder Methods From Object. Externally uses dict's magic methods.

Maybe:

  • Dict itself needs object methods but those methods do not actually belong to it. So, inherited dict does not include those methods. Just the magic ones -those defined directly inside the dict class- exactly belong to it.(Normal ones were passed to CustomDict)
  • Python uses dict -in this case- as a “dunder store” and uses those dunder methods when needed. Does not directly inherit them.

These stuff was my general thoughts about your code and question. Just thoughts and comments, not information that guaranteed to be correct.

but then shouldn’t

'__contains__', '__dir__', '__format__', '__getitem__',
'__init_subclass__', '__reduce__',
'__reduce_ex__', '__sizeof__', '__subclasshook__'

be classified under dict, as they are also special methods.

But those methods do not belong to dict. If you try dir(object), you will see that these are 'object’s methods.

Dict itself needs object methods but those methods do not actually belong to it. So, inherited dict does not include those methods

If you would ask “then, where do those methods come from to CustomDict”, my answer would be that they come from object automatically. But I have no idea about why some dunder methods of object are not inherited and left in object.