#python #functional-programming
#python #функциональное программирование
Вопрос:
У меня есть список объектов, и я хочу отфильтровать список таким образом, чтобы в результате было только одно вхождение каждого значения атрибута.
Допустим, у меня есть три объекта
obj1.my_attr = 'a'
obj2.my_attr = 'b'
obj3.my_attr = 'b'
obj_list = [obj1, obj2, obj3]
И, и в конце, я хочу получить [obj1, obj2]
. На самом деле порядок не имеет значения, поэтому [obj1, obj3]
он точно такой же хороший.
Сначала я подумал о типичных императивных неуклюжих способах, таких как следующие:
record = set()
result = []
for obj in obj_list:
if obj.my_attr not in record:
record.add(obj.my_attr)
result.append(obj)
Затем я подумал о том, чтобы сопоставить его со словарем, использовать ключ для переопределения любой предыдущей записи и, наконец, извлечь значения:
result = {obj.my_attr: obj for obj in obj_list}.values()
Этот вариант выглядит неплохо, но я хотел бы знать, существует ли какой-либо более элегантный, эффективный или функциональный способ достижения этой цели. Может быть, какая-нибудь приятная штука, скрытая в стандартной библиотеке… Заранее спасибо.
Ответ №1:
Если вы хотите использовать функциональный стиль программирования на Python, вы можете ознакомиться с пакетом toolz. С toolz
вы могли бы просто сделать:
toolz.unique(obj_list, key=lambda x: x.my_attr)
Для повышения производительности вы могли бы использовать operator.attrgetter('my_attr')
вместо лямбда-функции для ключа. Вы также могли бы использовать cytoolz, который представляет собой быструю реализацию toolz
, написанную на Cython.
Комментарии:
1. Это точно заполняет пробел FP, которого мне не хватало в python. Большое вам спасибо!
Ответ №2:
Вы могли бы использовать объект, который определял бы пользовательскую __hash__
функцию:
class HashMyAttr:
def __init__(self, obj):
self.obj = obj
def __hash__(self):
return self.obj.my_attr.__hash__()
def __eq__(self, other):
return self.obj.my_attr == other.obj.my_attr
И используйте его как:
obj_list = [x.obj for x in set(HashMyAttr(obj) for obj in obj_list)]
Комментарии:
1. Не совсем. Не работает, если атрибут является
int
:AttributeError: 'int' object has no attribute '__eq__'
2. в этом случае атрибут выглядит как строка, но я думаю,
==
тогда это сработало бы.3. Это определенно работает и является интересным подходом, но я нахожу его немного излишним и довольно подробным. Вместо этого я бы предпочел использовать понимание по словарю (скорее всего, более эффективное, поскольку для этого не нужно создавать оболочки).
4. Если вы можете переопределить
__eq__
и__hash__
функции непосредственно в исходном классе ваших объектов, то вам не нужна большая часть накладных расходов здесь.5. @njzk2 а что, если мне позже понадобится выполнить фильтрацию по другому атрибуту? или у меня уже есть
__hash__
реализация?