__getattr__ en un módulo

Resuelto Matt Joiner asked hace 14 años • 9 respuestas

¿Cómo se puede implementar el equivalente de a __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 <module>
    salutation("world")
NameError: name 'salutation' is not defined
Matt Joiner avatar Mar 15 '10 20:03 Matt Joiner
Aceptado

Hay dos problemas básicos con los que se encuentra aquí:

  1. __xxx__Los métodos sólo se buscan 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 realizar 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 un contenedor funcionará, pero solo para el acceso al módulo (es decir import somemodule; somemodule.salutation('world'), para acceder al mismo módulo, prácticamente tienes que extraer los métodos de la clase de sustitución y agregarlos a cualquiera de ellos globals()con un método personalizado en la clase (me gusta usar .export()) o con una función genérica (como las que ya figuran como respuestas). Una cosa a tener en cuenta: si el contenedor crea una nueva instancia cada vez y la solución global no, terminas con un comportamiento sutilmente diferente. Ah, y no puedes usar ambos al mismo tiempo: es uno o el otro.


Actualizar

De Guido van Rossum :

De hecho, existe un truco que se utiliza 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 insistes). , pero eso generalmente es menos útil). P.ej:

# module foo.py

import sys

class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>

sys.modules[__name__] = Foo()

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

Entonces, la forma establecida de lograr lo que desea es crear una sola clase en su módulo y, como último acto del módulo, reemplazarla sys.modules[__name__]con una instancia de su clase, y ahora puede jugar con // __getattr__según sea necesario.__setattr____getattribute__


Nota 1 : Si usa esta funcionalidad, cualquier otra cosa en el módulo, como funciones globales, otras funciones, etc., se perderá cuando se sys.modulesrealice la asignación, así que asegúrese de que todo lo necesario esté dentro de la clase de reemplazo.

Nota 2 : Para soportar from module import *debes tener __all__definido en la clase; Por ejemplo:

class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>
    __all__ = list(set(vars().keys()) - {'__module__', '__qualname__'})

Dependiendo de su versión de Python, puede haber otros nombres que omitir __all__. Se set()puede omitir si no se necesita compatibilidad con Python 2.

Ethan Furman avatar Oct 05 '2011 21:10 Ethan Furman

Hace un tiempo, Guido declaró que todas las búsquedas de métodos especiales en clases de nuevo estilo omiten __getattr__y__getattribute__ . Los métodos de Dunder habían funcionado anteriormente en módulos; por ejemplo, se podía usar un módulo como administrador de contexto simplemente definiendo __enter__y __exit__, antes de que esos trucos fallaran .

Recientemente, algunas características históricas han regresado, __getattr__entre ellas el módulo, por lo que el truco existente (un módulo que se reemplaza con una clase en el sys.modulesmomento de la importación) ya no debería ser necesario.

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

# my_module.py

def __getattr__(name: str) -> Any:
    ...

Esto también permitirá enlaces a importaciones "desde", es decir, puede devolver objetos generados dinámicamente para declaraciones como from my_module import whatever.

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

wim avatar Feb 21 '2018 21:02 wim

Este 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__])
Håvard S avatar Mar 15 '2010 13:03 Håvard S

No solemos hacerlo 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, mire el randommódulo, 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".

S.Lott avatar Mar 15 '2010 13:03 S.Lott