__getattr__ в модуле - PullRequest
       77

__getattr__ в модуле

110 голосов
/ 15 марта 2010

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

Ответы [ 9 ]

107 голосов
/ 06 октября 2011

Здесь есть две основные проблемы:

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


Обновление

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

На самом деле есть хак, который иногда используется и рекомендуется: Модуль может определить класс с желаемой функциональностью, а затем в конец, замените себя в sys.modules экземпляром этого класса (или с классом, если вы настаиваете, но это, как правило, менее полезно). E.g.:

# 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, поэтому убедитесь, что все необходимое находится внутри класса замены.

45 голосов
/ 15 марта 2010

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

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__])
22 голосов
/ 22 февраля 2018

Некоторое время назад Гвидо объявил, что все специальные методы поиска на классы нового стиля обходят __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). Подробнее см. PEP 562 .

19 голосов
/ 15 марта 2010

Обычно мы так не делаем.

Что мы делаем, так это.

class A(object):
....

# The implicit global instance
a= A()

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

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

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

13 голосов
/ 15 марта 2010

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

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

7 голосов
/ 06 мая 2010

Это хак, но ...

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

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

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

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

4 голосов
/ 13 июля 2013

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

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")
0 голосов
/ 31 мая 2011

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

from somemodule import * # imports SomeClass

someclass_instance = globals()['SomeClass']()
0 голосов
/ 15 марта 2010

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