web-dev-qa-db-fra.com

Compréhension de la liste Python vs .NET LINQ

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 à l'aide de la compréhension de la liste comme suit.

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

(AVERTISSEMENT: Mammoth Réponse à l'avance. La partie jusqu'à la première ligne horizontale fait une bonne tl; DR Section, je suppose)

Je ne sais pas si je me qualifie Python guru ... mais j'ai une solide compréhension sur l'itération à Python, essayons donc :)

Tout d'abord: AFAIK, les requêtes LINQ sont exécutées paresseusement - si c'est le cas, les expressions génératrices sont de plus près Python Concept (de la manière des compréhensions de liste, dict-dict et définies étant conceptuellement, des expressions de générateur conceptuel au constructeur de la liste/dict/set!).

En outre, il existe une différence conceptuelle: Linq est pour, comme le dit le nom, interrogeant les structures de données. Les compréhensions de liste/dict-/définies sont une application possible de ceci (par exemple filtrage et en projetant les éléments d'une liste). Ils sont donc en fait moins généraux (comme nous le verrons, de nombreuses choses intégrées à Linq ne leur sont pas intégrées). De même, les expressions de générateur sont un moyen de formuler un itérateur à l'avance unique (j'aime penser à celui-ci comme Lambda pour les fonctions de générateur, uniquement sans mot-clé long et long;)) 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, Python Contreparts pour les capacités Linq Jon Skeet nommé:

Projections: (x.foo for ...)

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

  • Jointures (x joindre y sur x.foo équivaut à 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 cela ne sera pas itérale sur l'ensemble de Y pour chaque X_ITEM, il ne fera que le premier match.

  • Jointures de groupe (X Rejoindre Y sur x.foo équivaut à Y.Bar dans g)

C'est plus difficile. Python n'a pas de types anonymes, bien qu'ils soient triviaux de faire vous-même si cela ne vous dérange pas de gâcher 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 et y membres avec les valeurs respectives. La bonne chose consiste généralement aux résultats du constructeur d'une classe d'approrie, disons, XY.

  • Regroupement (groupe x.foo de x.bar)

Maintenant, il devient velu ... il n'y a pas de chemin de construction, Afaik. Mais nous pouvons le 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

Exemple:

>>> 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 tout ce que nous collaborons en cas d'hachage, cependant. Il est possible d'éviter cela et je ferai un coup de poignard s'il y a une demande publique. Mais pour le moment, je suis paresseux :)

Nous pouvons également simplement renvoyer un iérêteur de groupes sans les valeurs que nous avons regroupées, en appelant .values() sur le résultat (bien sûr, nous pouvons alimenter que à list à Obtenez quelque chose que nous pouvons indexer et itérer plusieurs fois). Mais qui sait si nous n'aurons pas besoin des valeurs de groupe ...

  • Commande (Orderby x.foo ascendant, y.bar descendant)

Le tri a besoin de syntaxe spéciale? La construction sorted fonctionne aussi pour itérables, aussi: sorted(x % 2 for x in range(10)) ou sorted(x for x in xs, key=attrgetter('foo')). Trié Ascendant Par défaut, l'argument de mot-clé reverse donne l'ordre décroissant.

Hélas, le tri Afaik par plusieurs attributs n'est pas si facile, surtout lors du mélange ascendant et descendant. Hmm ... sujet pour une recette?

  • Variables intermédiaires (laissez TMP = x.foo)

Non, pas possible dans des compréhensions ou des expressions génératrices - ils sont, comme le nom le dit, censé être des expressions (et seulement seulement une ou deux lignes). Il est parfaitement possible dans la fonction génératrice, cependant:

(x * 2 for x in iterable)

réécrit comme 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 à 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 à la requête d'être traduite en SQL (ou dans d'autres langages de requête) - à nouveau, je ne sais pas si Python prend en charge ce genre de chose ou non. C'est une partie importante de Linq.

Python n'en fait définitivement rien de tel. Les expressions de liste correspondent à un-à-un pour accumuler une liste unie dans une boucle (éventuellement imbriquée), les expressions de générateur correspondent à un à un à un générateur. Compte tenu du module parser et ast, il serait possible en théorie d'écrire une bibliothèque pour convertir une compréhension par ex. une requête SQL. Mais personne ne s'en soucie.

58
user395760

En utilisant le ASQ Python Package, vous pouvez facilement faire la plupart des choses dans Python que vous pouvez faire en C # à l'aide de LINQ-for- objets. Utilisation ASQ, votre Python Exemple devient:

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

Je ne suis pas Python gourou, mais je dirais que Python en prend en charge toutes comme vous pouvez nier la compréhensions de liste et inclure toutes les expressions Lambda que vous souhaitez . (Les compréhensions de liste ont tendance à être difficiles à lire s'ils deviennent trop complexes, cependant ...), mais non, il n'inclut aucune "syntaxe spécifique" pour accomplir tout cela.

La plupart des fonctionnalités peuvent être reproduites en utilisant: - compréhensions de liste ou générateurs - fonctions Lambda ou fonctions intégrées (comme filter() ou map()) ou fonctionne du module itertools

Par exemple, si vous voulez copier le comportement de:

  • Projections : Ce serait la partie gauche d'une compréhension de liste ... qui peuvent ê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 à droite, après le if. Vous pouvez également utiliser filter()
  • Commande : Utilisez simplement la fonction intégrée sorted()
  • regroupement ou agrégats : Utilisez la fonction intégrée min() , max() ou itertools.groupby()

Concernant joint ou aplatissement , je pense que vous devriez "faire à la main "...

(Toujours bon d'avoir la Python rapide référence à la portée)

4
tsimbalar