python - español - __getattr__ en un módulo
getattr python español (9)
¿Cómo se puede implementar el equivalente de __getattr__
en una clase, en un módulo?
Ejemplo
Cuando llamo 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
Aquí está mi propia contribución humilde: un ligero embellecimiento 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 bueno 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 hacer una importación dinámica usando __import__
y extraer el módulo de sys.modules.
Aquí está su 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 global globals()
puede ser suficiente, por ejemplo, puede instanciar una clase por nombre del alcance global:
from somemodule import * # imports SomeClass
someclass_instance = globals()[''SomeClass'']()
Esto es 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 nombre global. Si el elemento es una clase, itera sobre los atributos de la clase. Si el atributo es invocable, lo agrega al espacio de nombre 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 comenzar.
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__])
Hay dos problemas básicos con los que se está metiendo aquí:
-
__xxx__
métodos__xxx__
solo se__xxx__
en la clase -
TypeError: can''t set attributes of built-in/extension type ''module''
(1) significa que cualquier solución debería también 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 quisquilloso con lo que va allí para que funcione un wrapper, pero solo para el acceso a módulos (es decir, import somemodule; somemodule.salutation(''world'')
; para el mismo módulo de acceso tienes que tirar de los métodos de la clase de sustitución y agréguelos a globals()
con un método personalizado en la clase (me gusta usar .export()
) o con una función genérica (como las ya enumeradas 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. Ah, y no puedes usar ambos al mismo tiempo, es uno o el otro.
Actualizar
De Guido van Rossum :
En realidad, hay 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 su 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 soportarlo en la maquinaria de importación).
Entonces, la forma establecida de lograr lo que quieres es crear una sola clase en tu módulo, y como último acto del módulo reemplazar sys.modules[__name__]
con una instancia de tu clase, y ahora puedes jugar con __getattr__
/ __setattr__
/ __getattribute__
según sea necesario.
Similar a lo que @ Håvard S propuso, 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 lo pondría en sys.modules
(probablemente reemplazando el módulo donde mi ModuleType
personalizado fue definido).
Consulte el archivo __init__.py
principal de Werkzeug para una implementación bastante robusta de esto.
Usualmente no lo hacemos de esa manera.
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.
Por ejemplo, mire el módulo random
, que crea una instancia global implícita para simplificar levemente los casos de uso en los que desea un generador de números aleatorios "simple".
Hace un tiempo, Guido declaró que todas las búsquedas de métodos especiales en las clases de estilo nuevo omiten __getattr__
y __getattribute__
. Dunder métodos utilizados para trabajar en los módulos: podría, por ejemplo, utilizar un módulo como administrador de contexto simplemente definiendo __enter__
y __exit__
, antes de que esos trucos se broke .
Recientemente, algunas características históricas han __getattr__
, el módulo __getattr__
entre ellas, por lo que el hack existente (un módulo que se reemplaza con una clase en sys.modules
en el momento de la importación) ya no debería ser necesario.
2018 respuesta:
En Python 3.7+, simplemente hazlo de la manera más obvia.
# my_module.py
def __getattr__(name: str) -> Any:
...
Para personalizar el acceso de atributos en un módulo, defina una función __getattr__
a nivel de módulo que debería aceptar un argumento (el nombre de un atributo) y devolver el valor calculado o generar un AttributeError
. De forma similar, puede definir una función __dir__
a nivel de módulo para responder a dir(my_module)
.
Ver PEP 562 para más detalles.