Ответ 1
(Предупреждение: мамонт отвечает вперед. Часть до первой горизонтальной линии делает хороший раздел tl; dr, я полагаю)
Я не уверен, что я отношусь к гуру Python... но у меня есть четкое понимание итерации на Python, поэтому попробуйте:)
Во-первых: Afaik, запросы LINQ выполняются лениво - если в этом случае выражения генератора являются более близкой концепцией Python (в любом случае, представления list-, dict- и set понимаются концептуально просто выражения генератора, подаваемые в список /dict/set constructor!).
Кроме того, существует концептуальная разница: LINQ - это, как говорится в названии, запрос структур данных. List-/dict-/set assrehensions - это возможное применение этого (например, фильтрация и проецирование элементов списка). Поэтому они на самом деле менее общие (как мы увидим, многие вещи, встроенные в LINQ, не встроены в них). Подобным образом, выражения генератора - это способ сформулировать одноразовый итератор вперед на месте (мне нравится думать об этом как лямбда для функций генератора, только без уродливого длинного ключевого слова;)), а не для описания сложного запроса, Они перекрываются, да, но они не идентичны. Если вы хотите использовать все возможности LINQ в Python, вам нужно будет написать полноценный генератор. Или объедините многочисленные мощные генераторы, встроенные и в itertools
.
Теперь, аналоги Python для возможностей LINQ Джон Скит назвал:
Проецирование: (x.foo for ...)
Фильтрация: (... if x.bar > 5)
- Объединение (x join 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)
, я полагаю.
Обратите внимание, что это не будет перебирать по всему y для каждого x_item, оно получит только первое совпадение.
- Групповое объединение (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
с соответствующими значениями.
Правильная вещь обычно подает результаты конструктору класса approriate, скажем, 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
, чтобы получить что-то, что мы можем индексировать и повторять несколько раз). Но кто знает, нужны ли нам групповые значения...
- Заказ (orderby x.foo по возрастанию, y.bar по убыванию)
Для сортировки нужен специальный синтаксис? Встроенный sorted
работает и для итераций: sorted(x % 2 for x in range(10))
или sorted(x for x in xs, key=attrgetter('foo'))
. Сортированный по возрастанию по умолчанию аргумент ключевого слова reverse
дает убывающий порядок.
Увы, сортировка afaik по нескольким атрибутам не так проста, особенно при смешении восходящего и нисходящего. Хм... тема для рецепта?
- Промежуточные переменные (пусть 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 определенно не делает этого. Выражения списка соответствуют взаимно однозначным для накопления простого списка в (возможно, вложенном) для цикла, генераторные выражения соответствуют друг другу одному генератору.
Учитывая модуль parser
и ast
, теоретически можно было бы написать библиотеку для преобразования понимания в, например, SQL-запрос. Но никто не заботится.