Как можно реализовать эквивалент __getattr__
класса в модуле?
пример
При вызове функции, которая не существует в статически определенных атрибутах модуля, я хочу создать экземпляр класса в этом модуле и вызвать в нем метод с тем же именем, что и при поиске атрибутов в модуле.
class A(object):
def salutation(self, accusative):
print "hello", accusative
# note this function is intentionally on the module, and not the class above
def __getattr__(mod, name):
return getattr(A(), name)
if __name__ == "__main__":
# i hope here to have my __getattr__ function above invoked, since
# salutation does not exist in the current namespace
salutation("world")
Который дает:
matt@stanley:~/Desktop$ python getattrmod.py
Traceback (most recent call last):
File "getattrmod.py", line 9, in <module>
salutation("world")
NameError: name 'salutation' is not defined
python
module
python-3.x
getattr
attributeerror
Мэтт Джойнер
источник
источник
salutation
существовать в глобальном или локальном пространстве имен (это то, что пытается сделать приведенный выше код) или вам нужен динамический поиск имен, когда вы делаете доступ через точку к модулю? Это разные вещи.__getattr__
на модулях поддерживается Python 3.7Ответы:
Некоторое время назад Гвидо объявил, что все специальные методы поиска в классах нового стиля обходят
__getattr__
и__getattribute__
. Раньше методы Dunder работали с модулями - вы могли, например, использовать модуль в качестве диспетчера контекста, просто определяя__enter__
и__exit__
, до того, как эти уловки сломаются .Недавно были возвращены некоторые исторические особенности, в том
__getattr__
числе и модуль , поэтому существующий хак (модуль, заменяющий себя классом воsys.modules
время импорта) больше не нужен.В Python 3.7+ вы просто используете один очевидный способ. Чтобы настроить доступ к атрибутам в модуле, определите
__getattr__
функцию на уровне модуля, которая должна принимать один аргумент (имя атрибута) и возвращать вычисленное значение или вызыватьAttributeError
:Это также позволит подключаться к импорту "из", т.е. вы можете возвращать динамически сгенерированные объекты для таких операторов, как
from my_module import whatever
.В связи с этим, вместе с модулем getattr вы также можете определить
__dir__
функцию на уровне модуля, на которую нужно реагироватьdir(my_module)
. Подробности см. В PEP 562 .источник
m = ModuleType("mod")
и устанавливаюm.__getattr__ = lambda attr: return "foo"
; однако, когда я бегуfrom mod import foo
, я получаюTypeError: 'module' object is not iterable
.m.__getattr__ = lambda attr: "foo"
, плюс вам нужно определить запись для модуля с помощьюsys.modules['mod'] = m
. После этого ошибки сfrom mod import foo
.my_module.whatever
для его вызова (послеimport my_module
).Здесь вы столкнетесь с двумя основными проблемами:
__xxx__
методы ищутся только в классеTypeError: can't set attributes of built-in/extension type 'module'
(1) означает, что любое решение должно также отслеживать, какой модуль исследуется, иначе каждый модуль будет иметь поведение подстановки экземпляра; и (2) означает, что (1) даже невозможно ... по крайней мере, не напрямую.
К счастью, sys.modules не разборчивы в том, что там происходит, поэтому оболочка будет работать, но только для доступа к модулю (т. Е. Для доступа
import somemodule; somemodule.salutation('world')
к тому же модулю вам в значительной степени придется выдернуть методы из класса подстановки и добавить их вglobals()
любой другой с помощью пользовательский метод в классе (мне нравится использовать.export()
) или с общей функцией (например, те, которые уже указаны в качестве ответов). Одна вещь, о которой следует помнить: если оболочка создает новый экземпляр каждый раз, а глобальное решение - нет, вы в конечном итоге ведете слегка различающееся поведение О, и вы не можете использовать оба одновременно - это одно или другое.Обновить
От Гвидо ван Россума :
Итак, установленный способ добиться того, что вы хотите, - это создать один класс в вашем модуле и в качестве последнего действия модуля заменить
sys.modules[__name__]
его экземпляром вашего класса - и теперь вы можете играть с__getattr__
/__setattr__
/ по__getattribute__
мере необходимости.Примечание 1. Если вы используете эту функцию, тогда все остальное в модуле, например глобальные объекты, другие функции и т. Д., Будет потеряно при выполнении
sys.modules
назначения - поэтому убедитесь, что все необходимое находится внутри класса замены.Примечание 2 : Для поддержки
from module import *
вы должны__all__
определить в классе; например:В зависимости от вашей версии Python могут быть другие имена, которые следует опустить
__all__
.set()
Может быть опущена , если совместимость Python 2 не требуется.источник
import sys
податливостьNone
дляsys
. Я предполагаю, что этот взлом не санкционирован в Python 2.Это хак, но вы можете обернуть модуль классом:
источник
import *
.Обычно мы так не поступаем.
Вот что мы делаем.
Зачем? Чтобы неявный глобальный экземпляр был виден.
В качестве примеров посмотрите на
random
модуль, который создает неявный глобальный экземпляр, чтобы немного упростить варианты использования, в которых вам нужен «простой» генератор случайных чисел.источник
Подобно тому, что предлагал @Håvard S, в случае, когда мне нужно было реализовать некоторую магию в модуле (например,
__getattr__
), я бы определил новый класс, который наследуется отtypes.ModuleType
и вставил егоsys.modules
(вероятно, заменив модуль, в которомModuleType
был определен мой собственный ).См. Основной
__init__.py
файл Werkzeug для довольно надежной реализации этого.источник
Это хакерство, но ...
Это работает путем перебора всех объектов в глобальном пространстве имен. Если элемент является классом, он выполняет итерацию по атрибутам класса. Если атрибут вызываемый, он добавляет его в глобальное пространство имен как функцию.
Он игнорирует все атрибуты, содержащие «__».
Я бы не стал использовать это в производственном коде, но он должен помочь вам начать.
источник
AddGlobalAttribute()
когда есть уровень модуляAttributeError
- что-то вроде обратной логики @Håvard S. Если у меня будет возможность, я протестирую это и добавлю свой собственный гибридный ответ, хотя OP уже принял этот ответ.NameError
исключения для глобального (модульного) пространства имен - что объясняет, почему этот ответ добавляет вызываемые объекты для каждой возможности, которую он находит в текущем глобальном пространстве имен, чтобы охватить все возможные случаи заранее.Вот мой скромный вклад - небольшое приукрашивание высоко оцененного ответа @Håvard S, но немного более явного (так что он может быть приемлем для @ S.Lott, хотя, вероятно, недостаточно хорош для OP):
источник
Создайте файл модуля с вашими классами. Импортируйте модуль. Запустите
getattr
только что импортированный модуль. Вы можете выполнить динамический импорт, используя__import__
и извлечь модуль из sys.modules.Вот ваш модуль
some_module.py
:И в другом модуле:
Делаем это динамически:
источник