Вопрос

Как можно реализовать эквивалент __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
Это было полезно?

Решение

Некоторое время назад Guido объявил, что все запросы специальных методов в классы нового стиля обходят __getattr__ и __getattribute__.Методы Dunder ранее работали с модулями - вы могли бы, например, использовать модуль в качестве контекстного менеджера, просто определив __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).Видишь БОДРОСТЬ ДУХА 562 для получения подробной информации.

Другие советы

Есть две основные проблемы, с которыми вы сталкиваетесь здесь:

  1. __xxx__ методы просматриваются только в классе
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1) означает, что любое решение должно было бы также отслеживать, какой модуль проверялся, в противном случае каждый тогда модуль будет иметь поведение подстановки экземпляра;и (2) означает, что (1) даже невозможно...по крайней мере, не напрямую.

К счастью, sys.modules не придирчив к тому, что туда входит, поэтому оболочка будет работать, но только для доступа к модулю (т.е. import somemodule; somemodule.salutation('world');для доступа к одному модулю вам в значительной степени придется извлечь методы из класса подстановки и добавить их в globals() либо с помощью пользовательского метода в классе (мне нравится использовать .export()) или с помощью универсальной функции (например, те, которые уже перечислены в качестве ответов).Одна вещь, которую нужно иметь в виду:если оболочка каждый раз создает новый экземпляр, а глобальное решение - нет, в конечном итоге вы получите слегка отличающееся поведение.О, и вы не можете использовать и то, и другое одновременно - это либо одно, либо другое.


Обновить

От Гвидо ван Россум:

На самом деле существует хак, который иногда используется и рекомендуется:a модуль может определить класс с желаемой функциональностью, а затем в конце заменить себя в 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 назначение выполнено - поэтому убедитесь, что все необходимое есть внутри замещающего класса.

Это взлом, но вы можете обернуть модуль классом:

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__])

Обычно мы так не поступаем.

Вот что мы делаем.

class A(object):
....

# The implicit global instance
a= A()

def salutation( *arg, **kw ):
    a.salutation( *arg, **kw )

Почему?Так что неявный глобальный экземпляр виден.

Для примеров посмотрите на random модуль, который создает неявный глобальный экземпляр, чтобы немного упростить варианты использования, когда вам нужен "простой" генератор случайных чисел.

Аналогично тому, что предложил @Håvard S, в случае, когда мне нужно было реализовать некоторую магию в модуле (например __getattr__), я бы определил новый класс, который наследуется от types.ModuleType и вложи это в sys.modules (вероятно, замена модуля, где мой пользовательский ModuleType был определен).

Смотрите основные __init__.py файл из Werkzeug для довольно надежной реализации этого.

Это халтурно, но...

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")

Это работает путем перебора всех объектов в глобальном пространстве имен.Если элемент является классом, он выполняет итерацию по атрибутам класса.Если атрибут можно вызвать, он добавляет его в глобальное пространство имен в виде функции.

Он игнорирует все атрибуты, которые содержат "__".

Я бы не стал использовать это в производственном коде, но это должно помочь вам начать.

Вот мой собственный скромный вклад - небольшое приукрашивание высоко оцененного ответа @Håvard S, но немного более явного (так что это может быть приемлемо для @S.Лотт, хотя, вероятно, недостаточно хорош для ОПЕРАЦИИ):

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")

Создайте свой модульный файл, содержащий ваши классы.Импортируйте модуль.Беги 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')

В некоторых обстоятельствах globals() словаря может быть достаточно, например, вы можете создать экземпляр класса по имени из глобальной области видимости:

from somemodule import * # imports SomeClass

someclass_instance = globals()['SomeClass']()
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top