2008-10-21 11 views
6

Estoy trabajando en una aplicación web que devolverá un conjunto variable de módulos dependiendo de la entrada del usuario. Cada módulo es una clase de Python con un constructor que acepta un solo parámetro y tiene una propiedad '.html' que contiene el resultado.Mejor enfoque con clases dinámicas usando Python globals()

Tirando de la clase dinámicamente desde el espacio de nombres global funciona:

result = globals()[classname](param).html 

y es sin duda más sucinto que:

if classname == 'Foo': 
    result = Foo(param).html 
elif classname == 'Bar': 
    ... 

Lo que se considera la mejor manera de escribir esto, estilísticamente? ¿Hay riesgos o razones para no usar el espacio de nombres global?

Respuesta

6

Un error con este enfoque es que puede dar al usuario la posibilidad de hacer más de lo que usted desea. Pueden llamar al cualquier función de un solo parámetro en ese espacio de nombres simplemente proporcionando el nombre. Puede ayudar a protegerse contra esto con unos pocos controles (por ejemplo, isinstance (SomeBaseClass, theClass), pero es probable que sea mejor evitar este enfoque. Otra desventaja es que restringe la ubicación de su clase. Si termina con docenas de tales clases y decide agruparlos en módulos, su código de búsqueda dejará de funcionar

usted tiene varias opciones alternativas:.

  1. crear una asignación explícita:

    class_lookup = {'Class1' : Class1, ... } 
    ... 
    result = class_lookup[className](param).html 
    

    aunque esto tiene el inconveniente de que tiene para volver a listar a todos l las clases.

  2. Anote las clases en un ámbito adjunto. P.ej. definirlos dentro de su propio módulo, o dentro de una clase externa:

    class Namespace(object): 
        class Class1(object): 
         ... 
        class Class2(object): 
         ... 
    ... 
    result = getattr(Namespace, className)(param).html 
    

    Usted expone inadvertidamente un par de variables de clase adicionales aquí, sin embargo (y__bases__, __getattribute__ etc) - probablemente no explotable, pero no es perfecto.

  3. Construye un dict de búsqueda del árbol de subclase. Haga que todas sus clases hereden de una única clase base. Cuando se hayan creado todas las clases, examine todas las clases base y rellene un dict de ellas. Esto tiene la ventaja de que puede definir sus clases en cualquier lugar (por ejemplo, en módulos separados), y siempre que cree el registro una vez que se hayan creado, las encontrará.

    def register_subclasses(base): 
        d={} 
        for cls in base.__subclasses__(): 
         d[cls.__name__] = cls 
         d.update(register_subclasses(cls)) 
        return d 
    
    class_lookup = register_subclasses(MyBaseClass) 
    

    Una variación más avanzada de lo anterior es el uso de las clases de registro automático - crear una metaclase que registra automáticamente las clases creadas en un diccionario. Esto es probablemente excesivo para este caso, aunque es útil en algunos escenarios de "plugins de usuario".

4

En primer lugar, parece que puede estar reinventando la rueda un poco ... la mayoría de los frameworks web de Python (CherryPy/TurboGears es lo que sé) ya incluyen una forma de enviar solicitudes a clases específicas basadas en los contenidos de la URL, o la entrada del usuario.

No hay nada incorrecto con la forma en que lo haces, realmente, pero en mi experiencia, tiende a indicar algún tipo de "abstracción faltante" en tu programa. Básicamente, confía en el intérprete de Python para almacenar una lista de los objetos que pueda necesitar, en lugar de almacenarla usted mismo.

Por lo tanto, como primer paso, es posible que desee simplemente hacer un diccionario de todas las clases que es posible que desee llamar:

dispatch = {'Foo': Foo, 'Bar': Bar, 'Bizbaz': Bizbaz} 

Inicialmente, esto no hará mucha diferencia. Pero a medida que crece su aplicación web, puede encontrar varias ventajas: (a) no se encontrará con conflictos de nombres, (b) usando globals() puede haber problemas de seguridad donde un atacante puede, en esencia, acceder a cualquier símbolo global en su programa si pueden encontrar una forma de inyectar un classname arbitrario en su programa, (c) si alguna vez desea que classname sea algo diferente del nombre de clase exacto real, usar su propio diccionario será más flexible, (d) puede reemplazar el dispatch diccionario con una clase definida por el usuario más flexible que hace acceso a la base de datos o algo así si encuentra la necesidad.

Los problemas de seguridad son particularmente importantes para una aplicación web. Haciendo globals()[variable] donde variable se ingresa desde un formulario web es solo preguntando por el problema.

0

Otra manera de construir el mapa entre los nombres de las clases y las clases:

Al definir clases, añadir un atributo a cualquier clase que desea poner en la tabla de búsqueda, por ejemplo:

class Foo: 
    lookup = True 
    def __init__(self, params): 
     # and so on 

Una vez hecho esto, la construcción del mapa de búsqueda es:

class_lookup = zip([(c, globals()[c]) for c in dir() if hasattr(globals()[c], "lookup")])