¿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 estáticamente definidos 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
python
module
python-3.x
getattr
attributeerror
Matt Joiner
fuente
fuente
salutation
existir en el espacio de nombres global o local (que es lo que intenta hacer el código anterior) o desea una búsqueda dinámica de nombres cuando realiza un acceso de punto en un módulo? Son dos cosas diferentes.__getattr__
en módulos es compatible con Python 3.7Respuestas:
Hace un tiempo, Guido declaró que todas las búsquedas de métodos especiales en clases de estilo nuevo omiten
__getattr__
y__getattribute__
. Métodos dunder habían trabajado previamente en módulos - que podría, por ejemplo, utilizar un módulo como un gestor de contexto, simplemente mediante la definición__enter__
y__exit__
, antes de que esos trucos rompieron .Recientemente, algunas características históricas han regresado, el módulo
__getattr__
entre ellas, por lo que el truco existente (un módulo que se reemplaza a sí mismo con una clase en elsys.modules
momento de la importación) ya no debería ser necesario.En Python 3.7+, solo usa la forma obvia. Para personalizar el acceso a los atributos en un módulo, defina una
__getattr__
función en el nivel del módulo que debería aceptar un argumento (nombre del atributo) y devolver el valor calculado o aumentar unAttributeError
:Esto también permitirá ganchos en las 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 responderdir(my_module)
. Ver PEP 562 para más detalles.fuente
m = ModuleType("mod")
y establecerm.__getattr__ = lambda attr: return "foo"
; sin embargo, cuando corrofrom mod import foo
, me saleTypeError: 'module' object is not iterable
.m.__getattr__ = lambda attr: "foo"
, además de que es necesario definir una entrada para el módulo consys.modules['mod'] = m
. Después, no hay error confrom mod import foo
.my_module.whatever
para invocarlo (después de unimport my_module
).Hay dos problemas básicos con los que se encuentra aquí:
__xxx__
los métodos solo se buscan en la claseTypeError: 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 un contenedor funcionará, pero solo para el acceso al módulo (es decir
import somemodule; somemodule.salutation('world')
; para el acceso al mismo módulo, prácticamente tiene que extraer los métodos de la clase de sustitución y agregarlos a cada unoglobals()
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 está creando una nueva instancia cada vez, y la solución global no lo es, terminas con un comportamiento sutilmente diferente Oh, y no puedes usar ambos al mismo tiempo, es uno o el otro.Actualizar
De Guido van Rossum :
Entonces, la forma establecida de lograr lo que desea es crear una sola clase en su módulo, y como el ú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.Nota 1 : Si usa esta funcionalidad, cualquier otra cosa en el módulo, como globales, otras funciones, etc., se perderá cuando
sys.modules
se realice la asignación, así que asegúrese de que todo lo necesario esté dentro de la clase de reemplazo.Nota 2 : Para apoyar
from module import *
debe haber__all__
definido en la clase; por ejemplo:Dependiendo de su versión de Python, puede haber otros nombres para omitir
__all__
. Seset()
puede omitir si no se necesita compatibilidad con Python 2.fuente
import sys
darNone
por hechosys
. Supongo que este truco no está sancionado en Python 2.Este es un truco, pero puedes envolver el módulo con una clase:
fuente
import *
.No solemos hacerlo así.
Lo que hacemos es esto.
¿Por qué? Para que la instancia global implícita sea visible.
Para ver ejemplos, mire el
random
mó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".fuente
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 heredatypes.ModuleType
y la pondríasys.modules
(probablemente reemplazando el módulo dondeModuleType
se definió mi costumbre ).Vea el
__init__.py
archivo principal de Werkzeug para una implementación bastante robusta de esto.fuente
Esto es hack, pero ...
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 invocable, 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 ayudarlo a comenzar.
fuente
AddGlobalAttribute()
cuando hay un nivel de móduloAttributeError
, algo al revés de la lógica de @ Håvard S. Si tengo la oportunidad, probaré esto y agregaré mi propia respuesta híbrida a pesar de que el OP ya ha aceptado esta respuesta.NameError
excepciones para el espacio de nombres global (módulo), lo que explica por qué esta respuesta agrega callables para cada posibilidad que encuentra al espacio de nombres global actual para cubrir todos los casos posibles antes de tiempo.Aquí está 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):
fuente
Cree su archivo de módulo que tenga sus clases. Importar el módulo. Ejecute
getattr
en el módulo que acaba de importar. Puedes hacer una importación dinámica usando__import__
y extraer el módulo de sys.modules.Aquí está tu módulo
some_module.py
:Y en otro módulo:
Haciendo esto dinámicamente:
fuente