__getattr__ en un módulo

¿Cómo se puede implementar el equivalente de un __getattr__ en una clase, en un módulo?

Ejemplo

Al llamar a una función que no existe en los atributos definidos estáticamente de un módulo, deseo crear una instancia de una clase en ese módulo e invocar el método con el mismo nombre que falló en la búsqueda de atributos en el módulo.

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

Lo que da:

 matt@stanley:~/Desktop$ python getattrmod.py Traceback (most recent call last): File "getattrmod.py", line 9, in  salutation("world") NameError: name 'salutation' is not defined 

Hace un tiempo, Guido declaró que todas las búsquedas de métodos especiales en clases de nuevo estilo omiten a __getattr__ y __getattribute__ . Los métodos de Dunder habían trabajado previamente en módulos; por ejemplo, podría usar un módulo como administrador de contexto simplemente definiendo __enter__ y __exit__ , antes de que se rompieran esos trucos.

Recientemente, algunas características históricas han __getattr__ , el módulo __getattr__ entre ellos, por lo que el hack existente (un módulo que se reemplaza a sí mismo con una clase en sys.modules en el momento de la importación) ya no debería ser necesario.

En Python 3.7+, solo usas una forma obvia. Para personalizar el acceso a los atributos en un módulo, defina una función __getattr__ en el nivel del módulo que debe aceptar un argumento (nombre del atributo) y devuelva el valor calculado o genere un AttributeError :

 # my_module.py def __getattr__(name: str) -> Any: ... 

Esto también permitirá from my_module import whatever “desde”, es decir, puede devolver objetos generados dinámicamente para declaraciones como la from my_module import whatever .

En una nota relacionada, junto con el módulo getattr también puede definir una función __dir__ a nivel de módulo para responder a dir(my_module) . Ver PEP 562 para más detalles.

Hay dos problemas básicos que está encontrando aquí:

  1. __xxx__ métodos __xxx__ solo se __xxx__ en la clase.
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1) significa que cualquier solución también tendría que hacer un seguimiento de qué módulo se estaba examinando; de lo contrario, cada módulo tendría el comportamiento de sustitución de instancia; y (2) significa que (1) ni siquiera es posible … al menos no directamente.

Afortunadamente, sys.modules no es exigente con lo que va allí, por lo que funcionará una envoltura, sino solo para el acceso a módulos (es decir, import somemodule; somemodule.salutation('world') ; para acceder a los mismos módulos, es prácticamente necesario eliminar los métodos desde la clase de sustitución y agréguelos a globals() eiher con un método personalizado en la clase (me gusta usar .export() ) o con una función genérica (como las que ya se enumeran como respuestas). Una cosa a tener en cuenta: si el contenedor crea una nueva instancia cada vez, y la solución global no lo es, terminas con un comportamiento sutilmente diferente. Ah, y no puedes usar ambos al mismo tiempo, es uno u otro.


Actualizar

De Guido van Rossum :

En realidad, hay un hack que se usa y recomienda ocasionalmente: un módulo puede definir una clase con la funcionalidad deseada, y luego al final, reemplazarse en sys.modules con una instancia de esa clase (o con la clase, si insiste) , pero eso es en general menos útil). P.ej:

 # module foo.py import sys class Foo: def funct1(self, ):  def funct2(self, ):  sys.modules[__name__] = Foo() 

Esto funciona porque la maquinaria de importación está activando activamente este truco y, como paso final, saca el módulo real de sys.modules, después de cargarlo. (Esto no es accidental. El truco se propuso hace mucho tiempo y decidimos que nos gustaba lo suficiente como para apoyarlo en la maquinaria de importación).

Entonces, la forma establecida de lograr lo que quiere es crear una clase única en su módulo, y como último acto del módulo, reemplace sys.modules[__name__] con una instancia de su clase, y ahora puede jugar con __getattr__ / __setattr__ / __getattribute__ según sea necesario.

Tenga en cuenta que si utiliza esta funcionalidad, cualquier otra cosa en el módulo, como los globales, otras funciones, etc., se perderá cuando se sys.modules asignación de sys.modules , así que asegúrese de que todo lo necesario esté dentro de la clase de reemplazo.

Esto es un truco, pero puedes envolver el módulo con una clase:

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

Normalmente no lo hacemos así.

Lo que hacemos es esto.

 class A(object): .... # The implicit global instance a= A() def salutation( *arg, **kw ): a.salutation( *arg, **kw ) 

¿Por qué? Para que la instancia global implícita sea visible.

Para ver ejemplos, vea el módulo random , que crea una instancia global implícita para simplificar ligeramente los casos de uso en los que desea un generador de números aleatorios “simple”.

Similar a lo que propuso @ Håvard S, en un caso en el que necesitaba implementar algo de magia en un módulo (como __getattr__ ), definiría una nueva clase que hereda de types.ModuleType y la pondría en sys.modules (probablemente reemplazando el módulo donde se definió mi ModuleType personalizado).

Vea el archivo principal __init__.py de Werkzeug para una implementación bastante sólida de esto.

Esto es un hackish, pero …

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

Esto funciona iterando sobre todos los objetos en el espacio de nombres global. Si el elemento es una clase, itera sobre los atributos de la clase. Si el atributo es llamable, lo agrega al espacio de nombres global como una función.

Ignora todos los atributos que contienen “__”.

No usaría esto en el código de producción, pero debería empezar.

Esta es mi humilde contribución: un ligero adorno de la respuesta altamente calificada de @ Håvard S, pero un poco más explícita (por lo que podría ser aceptable para @ S.Lott, aunque probablemente no sea lo suficientemente buena para el 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") 

Crea tu archivo de módulo que tiene tus clases. Importar el módulo. Ejecute getattr en el módulo que acaba de importar. Puede realizar una importación dinámica utilizando __import__ y extraer el módulo de sys.modules.

Aquí está tu módulo some_module.py :

 class Foo(object): pass class Bar(object): pass 

Y en otro módulo:

 import some_module Foo = getattr(some_module, 'Foo') 

Haciendo esto dinámicamente:

 import sys __import__('some_module') mod = sys.modules['some_module'] Foo = getattr(mod, 'Foo') 

En algunas circunstancias, el diccionario globals() puede ser suficiente, por ejemplo, puede instanciar una clase por nombre desde el ámbito global:

 from somemodule import * # imports SomeClass someclass_instance = globals()['SomeClass']()