Понимание списка Python против .NET LINQ - PullRequest
54 голосов
/ 13 октября 2010

Следующий простой код LINQ

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();

можно перевести на python, используя понимание списка следующим образом.

words = ["hello", "wonderful", "linq", "beautiful", "world"]
shortWords = [x for x in words if len(x) <=5]
print shortWords
  • Является ли LINQ просто еще одной идеей для реализации понимания списка?
  • Какими примерами может быть то, что может сделать LINQ, но понимание списка не может.

Ответы [ 4 ]

58 голосов
/ 13 октября 2010

(Внимание: ответ мамонта впереди. Часть до первой горизонтальной линии дает хороший tl; секция dr, я полагаю)

Я не уверен, что я квалифицируюсь как гуру Python ... но я хорошо разбираюсь в итерациях в Python, поэтому давайте попробуем:)

Прежде всего: Afaik, запросы LINQ выполняются лениво - в этом случае выражения-генераторы являются более близким понятием Python (в любом случае, понимания списков, диктов и множеств концептуально являются выражениями-генераторами, подаваемыми в список / dict / установить конструктор!).

Кроме того, есть концептуальное отличие: LINQ предназначен, как следует из названия, для запроса структур данных. Возможны применения этого в качестве списков / диктовок / наборов (например, фильтрация и проецирование элементов списка). Таким образом, они на самом деле менее общие (как мы увидим, многие вещи, встроенные в LINQ, не встроены в них). Аналогично, выражения генератора - это способ сформулировать одноразовый прямой итератор на месте (мне нравится думать о нем как о лямбде для функций генератора, только без уродливого, длинного ключевого слова;)), а не способ описать сложный запрос. , Да, они совпадают, но они не идентичны. Если вам нужна вся мощь LINQ в Python, вам придется написать полноценный генератор. Или объединить многочисленные мощные генераторы встроенные и в itertools.


Теперь коллеги из Python по возможностям LINQ Джон Скит назвал:

Прогнозы: (x.foo for ...)

Фильтрация: (... if x.bar > 5)

  • Joins (x присоединиться к y на x.foo равно y.bar)

Ближайшая вещь была бы ((x_item, next(y_item for y_item in y if x_item.foo == y_item.bar)) for x_item in x), я полагаю.

Обратите внимание, что для каждого x_item это не будет выполнять итерацию по всему y, а будет только первое совпадение.

  • Групповые объединения (x join y на x.foo равен y.bar в g)

Это сложнее. В Python нет анонимных типов, хотя они тривиальны, если вы не против возиться с __dict__:

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

Тогда мы могли бы сделать (Anonymous(x=x, y=y) for ...), чтобы получить список объектов, которые имеют x и y членов с соответствующими значениями. Правильно обычно передавать результаты конструктору класса, скажем, XY.

  • Группировка (группа x.foo по x.bar)

Теперь это становится волосатым ... нет встроенного способа, афаик. Но мы можем определить это сами, если нам это нужно:

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

Пример: * * тысяча пятьдесят-четырь

>>> 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: [...], ...})

Это требует, чтобы все, что мы группируем, было хэшируемым. Этого можно избежать, и я сделаю удар, если будет общественный спрос. Но сейчас я ленивый:)

Мы также можем просто вернуть итерируемую группу без значений, которые мы сгруппировали, вызвав .values() результат (конечно, мы можем передать , что , list, чтобы получить то, что мы можем индексировать и повторить несколько раз). Но кто знает, не понадобятся ли нам групповые значения ...

  • Порядок (по возрастанию x.foo, по убыванию y.bar)

Для сортировки нужен специальный синтаксис? Встроенный sorted работает и для итераций: sorted(x % 2 for x in range(10)) или sorted(x for x in xs, key=attrgetter('foo')). По умолчанию сортируется по возрастанию, ключевое слово аргумента reverse дает порядок убывания.

Увы, афаик сортировка по нескольким атрибутам не так проста, особенно при смешивании по возрастанию и по убыванию. Хм ... тема для рецепта?

  • Промежуточные переменные (пусть tmp = x.foo)

Нет, невозможно в выражениях или в выражениях-генераторах - они, как следует из названия, должны быть выражениями (и обычно занимают только одну или две строки). Это вполне возможно в функции генератора, хотя:

(x * 2 for x in iterable)

переписано как генератор с промежуточной переменной:

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

Сглаживание: (c for s in ("aa","bb") for c in s )


Обратите внимание: хотя LINQ to Objects работает с делегатами, другие поставщики запросов (например, LINQ to SQL) могут работать с деревьями выражений, которые описывают запрос, а не просто представляют исполняемые делегаты.Это позволяет переводить запрос в SQL (или другие языки запросов) - опять же, я не знаю, поддерживает ли Python подобные вещи или нет.Это важная часть LINQ.

Python определенно не делает ничего подобного.Выражения списка соответствуют взаимно однозначному накоплению простого списка в (возможно, вложенном) цикле for, выражения генератора соответствуют однозначному генератору.Учитывая parser и ast модуль, было бы теоретически написать библиотеку для преобразования понимания, например, в SQL-запрос.Но никому нет до этого дела.

24 голосов
/ 13 октября 2010

Ну, вам нужно различать некоторые вещи:

  • Стандартные операторы запросов LINQ
  • Выражения запроса LINQ в C #
  • выражения запроса LINQ в VB

C # не поддерживает так много выражений запросов, как VB, но вот что он поддерживает :

  • Прогнозы (select x.foo)
  • Фильтрация (where x.bar > 5)
  • Соединения (x join y on x.foo equals y.bar)
  • Групповые объединения (x join y on x.foo equals y.bar into g)
  • Группировка (group x.foo by x.bar)
  • Заказ (orderby x.foo ascending, y.bar descending)
  • Промежуточные переменные (let tmp = x.foo)
  • Сглаживание (from x in y from z in x)

Я не знаю, сколько из них поддерживается непосредственно в списках Python.

Обратите внимание, что хотя LINQ to Objects работает с делегатами, другие поставщики запросов (например, LINQ to SQL) могут иметь дело с деревьями выражений , которые описывают запрос, а не просто представляют исполняемые делегаты. Это позволяет переводить запрос в SQL (или другие языки запросов) - опять же, я не знаю, поддерживает ли Python подобные вещи или нет. Это важная часть LINQ.

16 голосов
/ 07 июня 2011

Используя пакет Python asq , вы легко можете сделать в Python большинство вещей, которые вы можете делать в C # с помощью LINQ-for-objects. Используя asq, ваш пример Python становится:

from asq.initiators import query
words = ["hello", "wonderful", "linq", "beautiful", "world"]
shortWords = query(words).where(lambda x: len(x) <= 5)
4 голосов
/ 13 октября 2010

Я не гуру Python, но я бы сказал, что Python на самом деле поддерживает их все, так как вы можете вкладывать в список понимания и включать все лямбда-выражения, которые вы хотите.(Понимания списка, как правило, трудно читать, если они становятся слишком сложными, хотя ...), но нет, он не включает в себя «особый синтаксис» для выполнения всего этого.

Большинство функций можно воспроизвести с помощью: - списочных представлений или генераторов - лямбда-функций или встроенных функций (например, filter() или map()) илифункции из itertools модуля

Например, если вы хотите скопировать поведение:

  • Проекции : это будет левая частьпонимание списков ... которые могут быть как отдельными значениями, так и кортежами.пример: [ (k,v) for k,v in my_dict.items() if k.startswith("abc"].Вы также можете использовать map()
  • Filtering : это будет выражение справа после if.Вы также можете использовать filter()
  • Заказ : просто используйте встроенные sorted()
  • Группировка или агрегаты : используйте встроенные min(), max() или itertools.groupby()

Относительно объединений или уплощения , я думаю, вам придется "сделайте это вручную "...

(Всегда полезно иметь Python Quick Reference в пределах досягаемости)

...