Каждый модуль в Python имеет атрибут ˍˍnameˍˍ. В него записывается строка, содержащая полное имя модуля. Например:
>>> from my_package import my_module
>>> print(my_module.__name__)
'my_package.my_module'
Очень удобно для логгинга когда в имя логгера требуется записать имя модуля, в котором происходят события лога.
Но однажды мне потребовалось создать логгер не для модуля в целом, а для отдельной функции. Что в этом случае можно сделать?
Такой объект как функция тоже имеет атрибут ˍˍnameˍˍ
>>> class MyClass:
>>> def func(self):
>>> pass
>>> print(MyClass.func.__name__)
'func'
Итого нам следует собрать полное имя таким образом:
>>> full_name = '.'.join([__name__, MyClass.__name__, MyClass.func.__name__])
>>> print(full_name)
'my_package.my_module.MyClass.func'
А что если класс вложен в другой класс?
>>> class MyClass:
>>> class SubClass:
>>> def func(self):
>>> pass
>>> full_name = '.'.join([__name__, MyClass.__name__, MyClass.SubClass.__name__, MyClass.SubClass.func.__name__])
>>> print(full_name)
'my_package.my_module.MyClass.SubClass.func'
А что если вложений несколько?
>>> class MyClass:
>>> class SubClass1:
>>> class SubClass2:
>>> def func(self):
>>> pass
Даже не буду писать пример получения имени 😭.
А что если функция не из этого класса а унаследована и вы работаете с внешней библиотекой?
>>> from somewhere.something import OtherClass
>>> class MyClass2(OtherClass):
>>> pass
Cтрашно подумать как нам достать настоящее "полное имя" функции! А если там динамическое определение наследования или метаклассы? Вообще можно забить на решение и придумать другой способ 😵
На помощь приходит PEP3155 и его имплементация "Qualified name".
Всё очень просто, начиная с Python 3.3 классы и функции имеют атрибут ˍˍqualnameˍˍ, который и содержит полное или "честное" или "квалифицированное" имя объекта. Именно в него уже записан готовый полный адрес объекта. И теперь даже такая конструкция сработает правильно:
>>> # some_module.py
>>>
>>> class OtherClass:
>>> class SubClass1:
>>> class SubClass2:
>>> def func(self):
>>> pass
>>>
>>> # my_module.py
>>> from some_module import OtherClass
>>> class FinalClass(OtherClass.SubClass1.SubClass2):
>>> pass
>>>
>>> full_name = '.'.join([__name__, FinalClass.func.__qualname__])
'my_package.my_module.OtherClass.SubClass1.SubClass2.func'
Тут стоит заметить, что если функция находится не в текущем модуле а откуда-то импортирована (как в моём примере) то собирать имя с локальным атрибутом ˍˍnameˍˍ уже не имеет особого смысла. Тогда лучше использовать имя модуля исходного класса:
>>> full_name = '.'.join([FinalClass.__module__, FinalClass.func.__qualname__])
'some_module.OtherClass.SubClass1.SubClass2.func'
#pep