Jeśli użycie pakietu innej firmy byłoby w porządku, możesz użyć iteration_utilities.unique_everseen:
>>> from iteration_utilities import unique_everseen
>>> l = [{'a': 123}, {'b': 123}, {'a': 123}]
>>> list(unique_everseen(l))
[{'a': 123}, {'b': 123}]
Zachowuje kolejność oryginalnej listy i ut może również obsługiwać elementy, których nie można zhasować, takie jak słowniki, wycofując się na wolniejszym algorytmie ( O(n*m)gdzie zamiast elementów oryginalnej listy nznajdują się elementy oryginalnej listy i munikalne elementy na oryginalnej liście O(n)). W przypadku, gdy zarówno klucze, jak i wartości są hashowalne, możesz użyć keyargumentu tej funkcji do utworzenia pozycji hashable dla „testu unikalności” (tak, aby działał O(n)).
W przypadku słownika (który porównuje niezależnie od kolejności) musisz odwzorować go na inną strukturę danych, która porównuje w ten sposób, na przykład frozenset:
>>> list(unique_everseen(l, key=lambda item: frozenset(item.items())))
[{'a': 123}, {'b': 123}]
Zwróć uwagę, że nie powinieneś używać prostego tuplepodejścia (bez sortowania), ponieważ równe słowniki niekoniecznie mają tę samą kolejność (nawet w Pythonie 3.7, gdzie kolejność wstawiania - a nie kolejność bezwzględna - jest gwarantowana):
>>> d1 = {1: 1, 9: 9}
>>> d2 = {9: 9, 1: 1}
>>> d1 == d2
True
>>> tuple(d1.items()) == tuple(d2.items())
False
A nawet sortowanie krotki może nie działać, jeśli klucze nie są sortowalne:
>>> d3 = {1: 1, 'a': 'a'}
>>> tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'
Reper
Pomyślałem, że warto zobaczyć, jak wypada porównanie tych podejść, więc zrobiłem mały test porównawczy. Wykresy porównawcze przedstawiają czas w funkcji rozmiaru listy na podstawie listy nie zawierającej duplikatów (która została wybrana arbitralnie, czas wykonania nie zmienia się znacząco, jeśli dodam kilka lub wiele duplikatów). Jest to wykres log-log, więc obejmuje cały zakres.
Absolutne czasy:

Czasy w stosunku do najszybszego podejścia:

Drugie podejście od czwórki jest tutaj najszybsze. unique_everseenPodejście z keyfunkcji znajduje się na drugim miejscu, jednak jest to najszybszy podejście, że przetwory zamówić. Inne podejścia z jcollado i the fourye są prawie tak samo szybkie. Podejście unique_everseenbez klucza i rozwiązania Emmanuel i Scorpil są bardzo powolne w przypadku dłuższych list i O(n*n)zamiast tego zachowują się znacznie gorzej O(n). Podejście stpk z jsonnie jest, O(n*n)ale jest znacznie wolniejsze niż podobne O(n)podejścia.
Kod do odtworzenia wzorców:
from simple_benchmark import benchmark
import json
from collections import OrderedDict
from iteration_utilities import unique_everseen
def jcollado_1(l):
return [dict(t) for t in {tuple(d.items()) for d in l}]
def jcollado_2(l):
seen = set()
new_l = []
for d in l:
t = tuple(d.items())
if t not in seen:
seen.add(t)
new_l.append(d)
return new_l
def Emmanuel(d):
return [i for n, i in enumerate(d) if i not in d[n + 1:]]
def Scorpil(a):
b = []
for i in range(0, len(a)):
if a[i] not in a[i+1:]:
b.append(a[i])
def stpk(X):
set_of_jsons = {json.dumps(d, sort_keys=True) for d in X}
return [json.loads(t) for t in set_of_jsons]
def thefourtheye_1(data):
return OrderedDict((frozenset(item.items()),item) for item in data).values()
def thefourtheye_2(data):
return {frozenset(item.items()):item for item in data}.values()
def iu_1(l):
return list(unique_everseen(l))
def iu_2(l):
return list(unique_everseen(l, key=lambda inner_dict: frozenset(inner_dict.items())))
funcs = (jcollado_1, Emmanuel, stpk, Scorpil, thefourtheye_1, thefourtheye_2, iu_1, jcollado_2, iu_2)
arguments = {2**i: [{'a': j} for j in range(2**i)] for i in range(2, 12)}
b = benchmark(funcs, arguments, 'list size')
%matplotlib widget
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.style.use('ggplot')
mpl.rcParams['figure.figsize'] = '8, 6'
b.plot(relative_to=thefourtheye_2)
Aby uzyskać kompletność, oto czas dla listy zawierającej tylko duplikaty:
# this is the only change for the benchmark
arguments = {2**i: [{'a': 1} for j in range(2**i)] for i in range(2, 12)}

Czasy nie zmieniają się znacząco, z wyjątkiem unique_everseenbraku keyfunkcji, co w tym przypadku jest najszybszym rozwiązaniem. Jest to jednak najlepszy przypadek (a więc niereprezentatywny) dla tej funkcji z wartościami, których nie można zhasować, ponieważ jej czas wykonywania zależy od liczby unikatowych wartości na liście: O(n*m)w tym przypadku jest to tylko 1, a zatem działa O(n).
Zastrzeżenie: jestem autorem iteration_utilities.