55 votes

Compréhension des listes dans Python et LINQ dans .NET

Le code LINQ simple suivant

string[] words = { "hello", "wonderful", "linq", "beautiful", "world" };

// Get only short words
var shortWords =
  from word in words
  where word.Length <= 5
  select word;

// Print each word out
shortWords.Dump();

peut être traduit en python en utilisant la compréhension de liste comme suit.

words = ["hello", "wonderful", "linq", "beautiful", "world"]
shortWords = [x for x in words if len(x) <=5]
print shortWords
  • LINQ est-il simplement une autre idée pour mettre en œuvre la compréhension des listes ?
  • Quels sont les exemples que LINQ peut faire mais que la compréhension de liste ne peut pas faire ?

58voto

delnan Points 52260

(Avertissement : Réponse de mammouth en perspective. La partie jusqu'à la première ligne horizontale fait une bonne section tl;dr, je suppose)

Je ne suis pas sûr d'être un gourou de Python... mais j'ai une bonne connaissance de l'itération en Python, alors essayons :)

Tout d'abord : A priori, les requêtes LINQ sont exécutées paresseusement - si c'est le cas, les expressions génératrices sont un concept Python plus proche (de toute façon, les compréhensions de liste, de dictée et d'ensemble sont conceptuellement juste des expressions génératrices alimentées au constructeur de liste/dict/ensemble !)

Il existe également une différence conceptuelle : LINQ sert, comme son nom l'indique, à interroger des structures de données. Les compréhensions de liste/dict/ensemble en sont une application possible (par exemple, filtrer et projeter les éléments d'une liste). Elles sont donc en fait moins générales (comme nous le verrons, de nombreuses choses intégrées à LINQ ne le sont pas). De même, les expressions de générateur sont un moyen de formuler un itérateur unique forward in-place (j'aime penser que c'est un lambda pour les fonctions de générateur, mais sans le mot-clé long et laid ;) ) et non un moyen de décrire une requête complexe. Ils se chevauchent, oui, mais ils ne sont pas identiques. Si vous voulez toute la puissance de LINQ en Python, vous devrez écrire un générateur à part entière. Ou combiner les nombreux générateurs puissants intégrés et dans itertools .


Maintenant, les contreparties Python pour les capacités LINQ que Jon Skeet a nommées :

Projections : (x.foo for ...)

Filtrage : (... if x.bar > 5)

  • Joins (x joint à y sur x.foo égale y.bar)

La chose la plus proche serait ((x_item, next(y_item for y_item in y if x_item.foo == y_item.bar)) for x_item in x) Je suppose.

Notez que cette méthode n'itère pas sur l'ensemble des y pour chaque x_item, elle ne récupère que la première correspondance.

  • Jointures de groupes (x jointures y sur x.foo égale y.bar dans g)

C'est plus difficile. Python n'a pas de types anonymes, bien qu'il soit trivial de les créer soi-même si cela ne vous dérange pas de vous embrouiller avec __dict__ :

class Anonymous(object):
    def __init__(self, **kwargs):
        self.__dict__ = kwargs

Ensuite, nous pourrions faire (Anonymous(x=x, y=y) for ...) pour obtenir une liste d'objets qui ont x y y les membres avec les valeurs respectives. La bonne chose à faire est d'envoyer les résultats au constructeur d'une classe appropriée, disons XY.

  • Regroupement (grouper x.foo par x.bar)

Maintenant, ça devient compliqué... il n'y a pas de moyen intégré, afaik. Mais nous pouvons la définir nous-mêmes si nous en avons besoin :

from collections import defaultdict

def group_by(iterable, group_func):
    groups = defaultdict(list)
    for item in iterable:
        groups[group_func(item)].append(item)
    return groups

Ejemplo:

>>> from operator import attrgetter
>>> group_by((x.foo for x in ...), attrgetter('bar'))
defaultdict(<class 'list'>, {some_value_of_bar: [x.foo of all x where x.bar == some_value_of_bar], some_other_value_of_bar: [...], ...})

Cela nécessite que ce que nous regroupons soit hachable, cependant. Il est possible d'éviter cela, et je m'y emploierai si le public le demande. Mais pour l'instant, je suis paresseux :)

Nous pouvons également retourner un itérable de groupes sans les valeurs par lesquelles nous avons groupé, en appelant .values() sur le résultat (bien sûr, nous pouvons alimenter que a list pour obtenir quelque chose que nous pouvons indexer et itérer plusieurs fois). Mais qui sait si nous n'aurons pas besoin des valeurs de groupe...

  • Ordonnancement (orderby x.foo ascendant, y.bar descendant)

Le tri nécessite une syntaxe spéciale ? La fonction intégrée sorted fonctionne aussi pour les itérables : sorted(x % 2 for x in range(10)) o sorted(x for x in xs, key=attrgetter('foo')) . Trié par ordre croissant par défaut, l'argument mot-clé reverse donne l'ordre décroissant.

Hélas, afaik trier par plusieurs attributs n'est pas si facile, surtout quand on mélange ascendant et descendant. Hmm... un sujet pour une recette ?

  • Variables intermédiaires (let tmp = x.foo)

Non, ce n'est pas possible dans les compréhensions ou les expressions génératrices - elles sont, comme leur nom l'indique, censées être des expressions (et ne couvrent généralement qu'une ou deux lignes). C'est parfaitement possible dans les générateurs de fonctions, par contre :

(x * 2 for x in iterable)

réécrit comme un générateur avec une variable intermédiaire :

def doubles(iterable):
    for x in iterable:
        times2 = x * 2
        yield times2

Aplatissement : (c for s in ("aa","bb") for c in s )


Notez que bien que LINQ to Objects traite des délégués, d'autres fournisseurs de requêtes (par exemple LINQ to SQL) peuvent traiter des arbres d'expression qui décrivent la requête au lieu de simplement présenter des délégués exécutables. Cela permet de traduire la requête en SQL (ou d'autres langages d'interrogation) - encore une fois, je ne sais pas si Python supporte ce genre de chose ou non. Mais c'est une partie importante de LINQ.

Python ne fait absolument rien de tel. Les expressions de liste correspondent à l'accumulation d'une liste simple dans une boucle for (éventuellement imbriquée), les expressions de générateur correspondent à un générateur. Étant donné le parser y ast module, il serait possible en théorie d'écrire une bibliothèque pour convertir une compréhension en, par exemple, une requête SQL. Mais personne ne s'en soucie.

25voto

Jon Skeet Points 692016

Eh bien, vous devez faire la distinction entre différentes choses :

  • Opérateurs d'interrogation standard LINQ
  • Expressions de requêtes LINQ en C#
  • Expressions de requêtes LINQ en VB

Le C# ne prend pas en charge autant d'expressions de requêtes que le VB, mais voici ce qu'il propose hace soutien :

  • Projections ( select x.foo )
  • Filtrage ( where x.bar > 5 )
  • Joints ( x join y on x.foo equals y.bar )
  • Joints de groupe ( x join y on x.foo equals y.bar into g )
  • Regroupement ( group x.foo by x.bar )
  • Commande ( orderby x.foo ascending, y.bar descending )
  • Les variables intermédiaires ( let tmp = x.foo )
  • Aplatissement ( from x in y from z in x )

Je ne sais pas combien d'entre elles sont supportées directement dans les compréhensions de listes de Python.

Notez que bien que LINQ to Objects traite des délégués, d'autres fournisseurs de requêtes (par exemple LINQ to SQL) peuvent traiter en arbres d'expression qui décrivent la requête au lieu de présenter uniquement des délégués exécutables. Cela permet de traduire la requête en SQL (ou en d'autres langages de requête) - encore une fois, je ne sais pas si Python supporte ce genre de choses ou non. Mais c'est une partie importante de LINQ.

17voto

Rob Smallshire Points 472

En utilisant le asq Le paquet Python vous permet de faire facilement la plupart des choses en Python que vous pouvez faire en C# en utilisant LINQ-for-objects. En utilisant asq, votre exemple Python devient :

from asq.initiators import query
words = ["hello", "wonderful", "linq", "beautiful", "world"]
shortWords = query(words).where(lambda x: len(x) <= 5)

4voto

tsimbalar Points 1582

Je ne suis pas un gourou de Python, mais je dirais que Python les supporte tous, car vous pouvez imbriquer les compréhensions de listes et inclure toutes les expressions lambda que vous voulez. (les compréhensions de listes ont tendance à être difficiles à lire si elles deviennent trop complexes, cependant...), mais il n'inclut pas de "syntaxe spécifique" pour accomplir tout cela.

La plupart des fonctionnalités peuvent être reproduites en utilisant : - compréhensions de listes o générateurs - des lambda-fonctions ou des fonctions intégrées (telles que filter() o map() ) ou des fonctions de la itertools module

Par exemple, si vous voulez copier le comportement de :

  • Projections : ce serait la partie gauche d'une compréhension de liste ... qui peut être des valeurs simples mais aussi des tuples. ex : [ (k,v) for k,v in my_dict.items() if k.startswith("abc"] . Vous pouvez également utiliser map()
  • Filtrage : ce serait l'expression sur la droite, après la if . Vous pouvez également utiliser filter()
  • Commander : utilisez simplement la fonction intégrée sorted()
  • Regroupement o agrégats : utiliser la fonction intégrée min() , max() o itertools.groupby()

Concernant rejoint o aplatissement je pense qu'il faudrait "le faire à la main"...

(Il est toujours bon d'avoir le Référence rapide à Python à portée de main )

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X