dunder_lookup.py 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. # Copyright (c) 2016-2018 Claudiu Popa <pcmanticore@gmail.com>
  2. # Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
  3. # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
  4. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  5. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  6. """Contains logic for retrieving special methods.
  7. This implementation does not rely on the dot attribute access
  8. logic, found in ``.getattr()``. The difference between these two
  9. is that the dunder methods are looked with the type slots
  10. (you can find more about these here
  11. http://lucumr.pocoo.org/2014/8/16/the-python-i-would-like-to-see/)
  12. As such, the lookup for the special methods is actually simpler than
  13. the dot attribute access.
  14. """
  15. import itertools
  16. import astroid
  17. from astroid.exceptions import AttributeInferenceError
  18. def _lookup_in_mro(node, name):
  19. attrs = node.locals.get(name, [])
  20. nodes = itertools.chain.from_iterable(
  21. ancestor.locals.get(name, []) for ancestor in node.ancestors(recurs=True)
  22. )
  23. values = list(itertools.chain(attrs, nodes))
  24. if not values:
  25. raise AttributeInferenceError(attribute=name, target=node)
  26. return values
  27. def lookup(node, name):
  28. """Lookup the given special method name in the given *node*
  29. If the special method was found, then a list of attributes
  30. will be returned. Otherwise, `astroid.AttributeInferenceError`
  31. is going to be raised.
  32. """
  33. if isinstance(
  34. node, (astroid.List, astroid.Tuple, astroid.Const, astroid.Dict, astroid.Set)
  35. ):
  36. return _builtin_lookup(node, name)
  37. if isinstance(node, astroid.Instance):
  38. return _lookup_in_mro(node, name)
  39. if isinstance(node, astroid.ClassDef):
  40. return _class_lookup(node, name)
  41. raise AttributeInferenceError(attribute=name, target=node)
  42. def _class_lookup(node, name):
  43. metaclass = node.metaclass()
  44. if metaclass is None:
  45. raise AttributeInferenceError(attribute=name, target=node)
  46. return _lookup_in_mro(metaclass, name)
  47. def _builtin_lookup(node, name):
  48. values = node.locals.get(name, [])
  49. if not values:
  50. raise AttributeInferenceError(attribute=name, target=node)
  51. return values