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
¿Fue útil?

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í:

  1. métodos __xxx__ solamente se buscan en la clase
  2. 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

Guido van Rossum :

  

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']()
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top