__getattr__ en un módulo
-
20-09-2019 - |
Pregunta
¿Cómo se puede aplicar el equivalente de un __getattr__
en una clase, en un módulo?
Ejemplo
Cuando se llama a una función que no existe en los atributos definidas estáticamente de un módulo, deseo de crear una instancia de una clase en ese módulo, e invocar el método en él con el mismo nombre que fracasó 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
Solución
Hace un tiempo, Guido declaró que todos método especial inversas de
clases de nuevo estilo eludir __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 definiendo __enter__
y __exit__
, antes de que esos trucos rompió .
Recientemente, algunos rasgos históricos han hecho una reaparición, el módulo de __getattr__
entre ellos, por lo que el corte existente (un módulo sustituyéndose con una clase en sys.modules
en el momento de las importaciones) deberían ser ya no es necesario.
En Python 3.7 +, sólo tiene que utilizar la forma obvia. Para personalizar el acceso atributo en un módulo, definir una función __getattr__
a nivel de módulo que debe aceptar un argumento (nombre del atributo), y devolver el valor calculado o lanzar una AttributeError
:
# my_module.py
def __getattr__(name: str) -> Any:
...
Esto también permitirá ganchos en "de" las importaciones, es decir, que pueda volver dinámicamente objetos de declaraciones como from my_module import whatever
generado.
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.
Otros consejos
Hay dos problemas básicos que se están ejecutando en aquí:
- métodos
__xxx__
solamente se buscan en la clase -
TypeError: can't set attributes of built-in/extension type 'module'
(1) significa cualquier solución tendría que mantener también un registro de que se estaba examinando módulo, de lo contrario cada módulo tendría entonces el comportamiento ejemplo de sustitución; y (2) significa que (1) no es ni siquiera posible ... al menos no directamente.
Afortunadamente, sys.modules no es exigente con lo que va allí por lo que un envoltorio va a funcionar, pero sólo para el acceso del módulo (es decir import somemodule; somemodule.salutation('world')
; para el acceso del mismo módulo que bastante tienen que tirar de los métodos de la clase de sustitución y añadirlos a globals()
eiher con un método personalizado a 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 la envoltura es la creación de una nueva instancia cada vez, y la solución globales no es, usted termina con un comportamiento ligeramente diferente Ah, y no llegar a utilizar ambos al mismo tiempo -.. es uno o el otro
Actualizar
En realidad, hay un truco que se utiliza ocasionalmente y recomendó: una módulo puede definir una clase con la funcionalidad deseada, y luego a Al final, sustituirse en sys.modules con una instancia de esa clase (O con la clase, si insiste, pero eso es por lo general menos útiles). Por ejemplo:.
# 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 está permitiendo a esta activamente corte, y como su último paso tira del módulo real de sys.modules, después de cargarlo. (Esto no es casual. El truco era propusimos hace mucho tiempo y decidimos que nos gustó lo suficiente como para apoyarlo en el maquinaria de importación.)
Así que la forma establecida para lograr lo que desea es crear una única clase en el módulo, y como el último acto del módulo sustituyen sys.modules[__name__]
con una instancia de su clase - y ahora se puede jugar con __getattr__
/ __setattr__
/ __getattribute__
según sea necesario.
Tenga en cuenta que si se utiliza esta funcionalidad cualquier otra cosa en el módulo, tales como globales, otras funciones, etc., se perderán cuando la tarea se hace sys.modules
-. Por lo que asegurarse de que todo es necesaria dentro de la clase de reemplazo
Este es un truco, pero se puede 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__])
Por lo general no hacemos así.
Lo que hacemos es la siguiente.
class A(object):
....
# The implicit global instance
a= A()
def salutation( *arg, **kw ):
a.salutation( *arg, **kw )
¿Por qué? Por lo que la instancia global implícito es visible.
Para ejemplos, ver el módulo de random
, que crea una instancia global implícito para simplificar un poco los casos de uso en los que desea un generador de números "simple" al azar.
Al igual que en lo que @ Håvard S propuesto, en un caso en que tenía que aplicar un poco de magia en un módulo (como __getattr__
), me gustaría definir una nueva clase que hereda de types.ModuleType
y poner esto en sys.modules
(probablemente reemplazar el módulo donde se definió mi ModuleType
personalizado).
Vea el principal __init__.py
archivo de Werkzeug para un bastante robusta implementación de este.
Este es hacker, 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 mediante la iteración sobre los todos los objetos en el espacio de nombres global. Si el artículo es una clase, se itera sobre los atributos de clase. Si el atributo es exigible que se añade al espacio de nombres global como una función.
Se ignoran todos los atributos que contienen "__".
Yo no usaría esto en el código de producción, pero debería empezar.
Esta es mi humilde contribución - un ligero @ embellecimiento de respuesta de alta calificación Håvard S, pero un poco más explícita (lo que podría ser aceptable para @ S. Lott, aunque probablemente no 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")
Cree el archivo de módulo que tiene sus clases. Importar el módulo. getattr
ejecutar en el módulo que acaba de importar. Se puede realizar una importación dinámico utilizando __import__
y tire del módulo de sys.modules.
Aquí tiene su some_module.py
módulo:
class Foo(object):
pass
class Bar(object):
pass
Y en otro módulo:
import some_module
Foo = getattr(some_module, 'Foo')
Hacer esto de forma dinámica:
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, se puede crear una instancia de una clase por su nombre desde un ámbito global:
from somemodule import * # imports SomeClass
someclass_instance = globals()['SomeClass']()