web-dev-qa-db-fra.com

SQL Alchemy ORM renvoyant une seule colonne, comment éviter le post-traitement commun

J'utilise l'ORM de SQL Alchemy et je trouve que lorsque je retourne une seule colonne, j'obtiens les résultats comme suit:

[(result,), (result_2,)] # etc...

Avec un ensemble comme celui-ci, je trouve que je dois le faire souvent:

results = [r[0] for r in results] # So that I just have a list of result values

Ce n'est pas si "mauvais" parce que mes jeux de résultats sont généralement petits, mais s'ils ne l'étaient pas, cela pourrait ajouter des frais généraux importants. La plus grande chose est que je sens que cela encombre la source, et manquer cette étape est une erreur assez courante que je rencontre.

Existe-t-il un moyen d'éviter cette étape supplémentaire?

Un côté lié: Ce comportement de l'orm semble incommode dans ce cas, mais un autre cas où mon jeu de résultats était, [(id, valeur)] il se termine comme ceci:

[(result_1_id, result_1_val), (result_2_id, result_2_val)]

Je peux alors simplement faire:

results = dict(results) # so I have a map of id to value

Celui-ci a l'avantage de faire sens comme une étape utile après avoir renvoyé les résultats.

Est-ce vraiment un problème ou est-ce que je suis juste un nitpick et le post-traitement après avoir obtenu le jeu de résultats est logique dans les deux cas? Je suis sûr que nous pouvons penser à d'autres opérations de post-traitement courantes pour rendre l'ensemble de résultats plus utilisable dans le code d'application. Existe-t-il des solutions hautes performances et pratiques à tous les niveaux ou le post-traitement est-il inévitable et simplement requis pour différentes utilisations des applications?

Lorsque mon application peut réellement tirer parti des objets renvoyés par l'ORM de SQL Alchemy, cela semble extrêmement utile, mais dans les cas où je ne peux pas ou pas, pas tellement. Est-ce juste un problème courant des ORM en général? Suis-je mieux de ne pas utiliser la couche ORM dans des cas comme celui-ci?

Je suppose que je devrais montrer un exemple des requêtes orm réelles dont je parle:

session.query(OrmObj.column_name).all()

ou

session.query(OrmObj.id_column_name, OrmObj.value_column_name).all()

Bien sûr, dans une vraie requête, il y aurait normalement des filtres, etc.

67
Derek Litz

Le Zip de Python combiné avec l'opérateur d'expansion en ligne * est une solution assez pratique pour cela:

>>> results = [('result',), ('result_2',), ('result_3',)]
>>> Zip(*results)
[('result', 'result_2', 'result_3')]

Vous n'avez alors qu'à [0] indexer une seule fois. Pour une liste aussi courte, votre compréhension est plus rapide:

>>> timeit('result = Zip(*[("result",), ("result_2",), ("result_3",)])', number=10000)
0.010490894317626953
>>> timeit('result = [ result[0] for result in [("result",), ("result_2",), ("result_3",)] ]', number=10000)
0.0028390884399414062

Cependant, pour les listes plus longues, Zip devrait être plus rapide:

>>> timeit('result = Zip(*[(1,)]*100)', number=10000)
0.049577951431274414
>>> timeit('result = [ result[0] for result in [(1,)]*100 ]', number=10000)
0.11178708076477051

C'est donc à vous de déterminer ce qui est le mieux adapté à votre situation.

22
Beright

Une façon de réduire l'encombrement dans la source est d'itérer comme ceci:

results = [r for (r, ) in results]

Bien que cette solution soit un caractère plus longue que l'utilisation de [] opérateur, je pense que c'est plus agréable pour les yeux.

Pour encore moins d'encombrement, supprimez les parenthèses. Cela rend plus difficile la lecture du code, pour remarquer que vous manipulez en fait des tuples, cependant:

results = [r for r, in results]
18
Dag Høidahl

J'ai aussi eu du mal avec cela jusqu'à ce que je réalise que c'est comme n'importe quelle autre requête:

for result in results:
     print result.column_name
11
Pakman

J'ai trouvé ce qui suit plus lisible, inclut également la réponse pour le dict (dans Python 2.7):

d = {id_: name for id_, name in session.query(Customer.id, Customer.name).all()}
l = [r.id for r in session.query(Customer).all()]

Pour la valeur unique, emprunt d'une autre réponse:

l = [name for (name, ) in session.query(Customer.name).all()]

Comparez avec la solution Zip intégrée, adaptée à la liste:

l = list(Zip(*session.query(Customer.id).all())[0])

qui, à mon époque, ne fournit que des améliorations de vitesse d'environ 4%.

2
Roman Susi

Ma solution ressemble à ceci;)

def column(self):
    for column, *_ in Model.query.with_entities(Model.column).all():
        yield column

REMARQUE: py3 uniquement.

0
brunsgaard

Wow, les gars, pourquoi tendre? Il existe une méthode plus raide, plus rapide et plus élégante)

>>> results = [('result',), ('result_2',), ('result_3',)]
>>> sum(results, Tuple())
('result', 'result_2', 'result_3')

La vitesse:

>>> timeit('result = Zip(*[("result",), ("result_2",), ("result_3",)])', number=10000)
0.004222994000883773
>>> timeit('result = sum([("result",), ("result_2",), ("result_3",)], ())', number=10000)
0.0038205889868550003

Mais si plus d'éléments dans la liste - utilisez uniquement Zip. Zip plus de vitesse.

0
user8814926