2013-08-20 5 views
2

Je crée une application backend avec SQLAlchemy en utilisant la base déclarative. L'ORM nécessite environ 15 tables dont chacune est mappée à un objet de classe dans SQLAlchemy. Parce que ces objets de classe sont tous définis à l'identique, je pensais qu'un modèle d'usine pouvait produire les classes de façon plus concise. Cependant, ces classes doivent non seulement être définies, mais elles doivent également être affectées à des noms de variables uniques afin de pouvoir être importées et utilisées dans le projet.Définition de classe Python dynamique dans SQLAlchemy

(Désolé si cette question est un peu long, je mis à jour comme j'ai mieux compris le problème.)

Parce que nous avons tant de colonnes (~ 1000), nous définissons leurs noms et types dans des fichiers texte externes Gardez les choses lisibles. Après avoir fait cela d'une façon de s'y prendre pour déclarer nos modèles est comme ceci:

class Foo1(Base): 
    __tablename___ = 'foo1' 

class Foo2(Base): 
    __tablename___ = 'foo2' 

... etc 

puis je peux ajouter les colonnes en boucle sur le contenu du fichier texte externe et en utilisant la setattr() sur chaque définition de classe.

Ceci est OK mais cela semble trop répétitif car nous avons environ 15 tables. Donc à la place, j'ai pris la peine d'écrire une fonction d'usine qui pourrait définir les classes dynamiquement.

def orm_factory(class_name): 
    class NewClass(Base): 
     __tablename__ = class_name.lower() 
    NewClass.__name__ = class_name.upper() 
    return NewClass 

Encore une fois je peux boucler sur les colonnes et utiliser setattr(). Quand je l'ai mis ensemble, il ressemble à ceci:

for class_name in class_name_list: 
    ORMClass = orm_factory(class_name) 
    header_keyword_list = get_header_keyword_list(class_name) 
    define_columns(ORMClass, header_keyword_list) 

get_header_keyword_list obtient les informations de colonne et define_columns effectue l'affectation setattr(). Lorsque je l'utilise et exécute Base.metadata.create_all(), le schéma SQL est généré correctement.

Mais, quand j'essaie alors d'importer ces définitions de classe dans un autre modèle que je reçois une erreur comme ceci:

SAWarning: The classname 'NewClass' is already in the registry of this declarative base, mapped to <class 'ql_database_interface.IR_FLT_0'> 

Ce, je me rends compte maintenant est logique totale basée sur ce que j'ai appris hier: Python class variable name vs __name__.

Vous pouvez résoudre ce problème en utilisant type comme générateur de classe dans votre fonction d'usine (comme le font deux des réponses ci-dessous). Cependant, cela ne résout pas le problème de pouvoir importer la classe car pendant que les classes sont construites dynamiquement dans la fonction d'usine, la variable à laquelle la sortie de cette fonction est assignée est statique. Même s'il était dynamique, comme une clé de dictionnaire, il doit être dans l'espace de nom du module pour pouvoir être importé d'un autre module. Voir ma réponse pour plus de détails.

Répondre

1

[Note, ceci est l'affiche originale]

Ainsi, après une réflexion et de parler aux gens que j'ai décidé que cette capacité de façon dynamique créer et assigner des variables aux objets de classe dans l'espace de nom global de cette façon ce n'est pas quelque chose supports Python (et probablement avec raison). Même si je pense que mon cas d'utilisation est pas trop fou (pompage liste prédéfinie de classes construites à l'identique), il est tout simplement pas pris en charge.

Il y a beaucoup de questions qui pointent vers l'utilisation d'un dictionnaire dans un cas comme celui-ci, comme celui-ci: https://stackoverflow.com/a/10963883/1216837. Je pensais à quelque chose comme ça, mais la question est que je besoin de ces classes dans l'espace de nom du module afin que je puisse les importer dans d'autres modules. Cependant, les ajouter avec globals() comme globals()['MyClass'] = class_dict['MyClass'] semble que ça se assez là-bas et mon impression est sur les gens froncer les sourcils SO sur l'utilisation globals() comme celui-ci.

Il existe des hacks tels que celui suggéré par patjenk mais à un certain point, l'obfuscation et la complexité pèsent sur les avantages de la clarté de la déclaration statique de chaque objet de classe. Donc, même si cela semble répétitif, je vais juste écrire toutes les définitions de classe. Vraiment, ce finissent par être assez concis/maintenable:

Class1 = class_factory('class1') 
Class2 = class_factory('class2') 
... 
+0

Pourquoi avez-vous besoin de modifier GLOBALS si vous ajoutez les classes construites à un conteneur champ extérieur (list_of_classes.append ou un dict)? Le conteneur lui-même sera importable entre les modules car le conteneur sera dans la portée du module. – cowbert

1

Cela ressemble à une idée sommaire. Mais c'est amusant à résoudre alors voici comment vous le faites fonctionner. Si je comprends bien, votre problème est que vous voulez ajouter des classes créées dynamiquement à un module. J'ai créé un hack en utilisant un module et le init fichier .py.

dynamicModule/__ init__.py:

import dynamic 

class_names = ["One", "Two", "Three"] 

for new_name in class_names: 
    dynamic.__dict__['Class%s' % new_name] = type("Class%s" % (new_name), (object,), {'attribute_one': 'blah'}) 

dynamicModule/dynamic.py:

"""Empty file""" 

test.py:

import dynamicModule 
from dynamicModule import dynamic 
from dynamicModule.dynamic import ClassOne 

dynamic.ClassOne 
"""This all seems evil but it works for me on python 2.6.5""" 

__init__.py:

"""Empty file""" 
Questions connexes