__getattr__ на модуле
Как реализовать эквивалент __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")
Что дает:
[email protected]:~/Desktop$ python getattrmod.py
Traceback (most recent call last):
File "getattrmod.py", line 9, in <module>
salutation("world")
NameError: name 'salutation' is not defined
Ответы
Ответ 1
Некоторое время назад Гвидо объявил, что все специальные методы поиска на
классы нового стиля обходят __getattr__
и __getattribute__
. Более сложные методы ранее работали с модулями - например, вы можете использовать модуль в качестве диспетчера контекста, просто определив __enter__
и __exit__
до того, как эти трюки сломались.
В последнее время возвращаются некоторые исторические особенности, в том числе модуль __getattr__
, поэтому существующий хак (модуль, заменяющий себя классом в sys.modules
во время импорта) больше не нужен.
В Python 3. 7+ вы просто используете один очевидный способ. Чтобы настроить доступ к атрибутам в модуле, определите функцию __getattr__
на уровне модуля, которая должна принимать один аргумент (имя атрибута), и вернуть вычисленное значение или вызвать AttributeError
:
# my_module.py
def __getattr__(name: str) -> Any:
...
Это также позволит подключаться к импорту "из", то есть вы можете возвращать динамически сгенерированные объекты для операторов, таких как from my_module import whatever
.
В соответствующей заметке вместе с модулем getattr вы также можете определить функцию __dir__
на уровне модуля для ответа на dir(my_module)
. Подробнее см. PEP 562.
Ответ 2
Здесь есть две основные проблемы:
__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 экземпляром этого класса (или с классом, если вы настаиваете, но это, как правило, менее полезно). Например.:
# module foo.py
import sys
class Foo:
def funct1(self, <args>): <code>
def funct2(self, <args>): <code>
sys.modules[__name__] = Foo()
Это работает, потому что импортная техника активно взломать, и в качестве последнего шага вытаскивает фактический модуль из sys.modules, после загрузки. (Это не случайно. Взлом был предложил давно, и мы решили, что нам достаточно, чтобы поддержать его в импортная техника.)
Таким образом, установленный способ выполнить то, что вы хотите, это создать отдельный класс в вашем модуле, и в качестве последнего действия модуля заменить sys.modules[__name__]
на экземпляр вашего класса - и теперь вы можете играть с __getattr__
/__setattr__
/__getattribute__
по мере необходимости.
Обратите внимание, что если вы используете эту функцию, все остальное в модуле, например, глобальные переменные, другие функции и т.д., Будет потеряно при назначении sys.modules
- поэтому убедитесь, что все необходимое находится внутри класса замены.
Ответ 3
Это взломать, но вы можете обернуть модуль классом:
class Wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
# Perform custom logic here
try:
return getattr(self.wrapped, name)
except AttributeError:
return 'default' # Some sensible default
sys.modules[__name__] = Wrapper(sys.modules[__name__])
Ответ 4
Обычно мы этого не делаем.
Мы делаем это.
class A(object):
....
# The implicit global instance
a= A()
def salutation( *arg, **kw ):
a.salutation( *arg, **kw )
Почему? Таким образом, видится неявный глобальный экземпляр.
В качестве примеров рассмотрим модуль random
, который создает неявный глобальный экземпляр, чтобы немного упростить варианты использования, в которых вы хотите создать "простой" генератор случайных чисел.
Ответ 5
Подобно тому, что предложил Х. Ховард S в случае, когда мне нужно было реализовать некоторую магию в модуле (например, __getattr__
), я бы определил новый класс, наследующий от types.ModuleType
, и поместил его в sys.modules
(возможно, заменив модуль, где был определен мой пользовательский ModuleType
).
См. основной файл __init__.py
Werkzeug для довольно надежной реализации этого.
Ответ 6
Это хаки, но...
import types
class A(object):
def salutation(self, accusative):
print "hello", accusative
def farewell(self, greeting, accusative):
print greeting, accusative
def AddGlobalAttribute(classname, methodname):
print "Adding " + classname + "." + methodname + "()"
def genericFunction(*args):
return globals()[classname]().__getattribute__(methodname)(*args)
globals()[methodname] = genericFunction
# set up the global namespace
x = 0 # X and Y are here to add them implicitly to globals, so
y = 0 # globals does not change as we iterate over it.
toAdd = []
def isCallableMethod(classname, methodname):
someclass = globals()[classname]()
something = someclass.__getattribute__(methodname)
return callable(something)
for x in globals():
print "Looking at", x
if isinstance(globals()[x], (types.ClassType, type)):
print "Found Class:", x
for y in dir(globals()[x]):
if y.find("__") == -1: # hack to ignore default methods
if isCallableMethod(x,y):
if y not in globals(): # don't override existing global names
toAdd.append((x,y))
for x in toAdd:
AddGlobalAttribute(*x)
if __name__ == "__main__":
salutation("world")
farewell("goodbye", "world")
Это работает путем итерации по всем объектам глобального пространства имен. Если элемент является классом, он выполняет итерацию над атрибутами класса. Если атрибут является вызываемым, он добавляет его в глобальное пространство имен как функцию.
Он игнорирует все атрибуты, содержащие "__".
Я бы не использовал это в производственном коде, но он должен вас запустить.
Ответ 7
Вот мой собственный скромный вклад - небольшое приукрашивание высоко оцененного ответа @Håvard S, но немного более явное (поэтому это может быть приемлемо для @S.Lott, хотя, вероятно, и не достаточно для OP):
import sys
class A(object):
def salutation(self, accusative):
print "hello", accusative
class Wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
try:
return getattr(self.wrapped, name)
except AttributeError:
return getattr(A(), name)
_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__])
if __name__ == "__main__":
_globals.salutation("world")
Ответ 8
Создайте свой файл модуля с вашими классами. Импортируйте модуль. Запустите getattr
в модуле, который вы только что импортировали. Динамический импорт можно выполнять с помощью __import__
и вытаскивать модуль из sys.modules.
Здесь ваш модуль some_module.py
:
class Foo(object):
pass
class Bar(object):
pass
И в другом модуле:
import some_module
Foo = getattr(some_module, 'Foo')
Выполнение этого динамически:
import sys
__import__('some_module')
mod = sys.modules['some_module']
Foo = getattr(mod, 'Foo')
Ответ 9
В некоторых случаях словарь globals()
может быть достаточным, например, вы можете создать экземпляр класса по имени из глобальной области:
from somemodule import * # imports SomeClass
someclass_instance = globals()['SomeClass']()