Bagaimana cara mengurutkan daftar objek berdasarkan atribut objek?

804

Saya punya daftar objek Python yang ingin saya urutkan berdasarkan atribut dari objek itu sendiri. Daftarnya seperti:

>>> ut
[<Tag: 128>, <Tag: 2008>, <Tag: <>, <Tag: actionscript>, <Tag: addresses>,
 <Tag: aes>, <Tag: ajax> ...]

Setiap objek memiliki hitungan:

>>> ut[1].count
1L

Saya perlu mengurutkan daftar berdasarkan jumlah penghitungan yang menurun.

Saya telah melihat beberapa metode untuk ini, tetapi saya sedang mencari praktik terbaik dengan Python.

Nick Sersan
sumber
1
Penyortiran CARA UNTUK mereka yang mencari informasi lebih lanjut tentang penyortiran dengan Python.
Jeyekomon
1
selain dari operator.attrgetter ('atribut_name') Anda juga dapat menggunakan functors sebagai kunci seperti object_list.sort (key = my_sorting_functor ('my_key')), membiarkan implementasi keluar dengan sengaja.
vijay shanker

Jawaban:

1314
# To sort the list in place...
ut.sort(key=lambda x: x.count, reverse=True)

# To return a new list, use the sorted() built-in function...
newlist = sorted(ut, key=lambda x: x.count, reverse=True)

Lebih lanjut tentang penyortiran berdasarkan kunci .

Triptych
sumber
1
Tidak masalah. btw, jika muhuk benar dan itu adalah daftar objek Django, Anda harus mempertimbangkan solusinya. Namun, untuk kasus umum menyortir objek, solusi saya mungkin merupakan praktik terbaik.
Triptych
43
Pada daftar besar Anda akan mendapatkan kinerja yang lebih baik menggunakan operator.attrgetter ('hitung') sebagai kunci Anda. Ini hanya bentuk dioptimalkan (level bawah) dari fungsi lambda dalam jawaban ini.
David Eyk
4
Terima kasih atas jawabannya. Dalam kasus jika ini adalah daftar kamus dan 'hitung' adalah salah satu kuncinya maka perlu diubah seperti di bawah ini: ut.sort (key = lambda x: x ['hitung'], mundur = Benar)
dganesh2002
Saya kira itu layak pembaruan berikut: jika ada kebutuhan untuk mengurutkan berdasarkan beberapa bidang, itu bisa dicapai dengan panggilan berturut-turut untuk mengurutkan (), karena python menggunakan algoritma sorting yang stabil.
zzz777
86

Cara yang bisa tercepat, terutama jika daftar Anda memiliki banyak catatan, adalah dengan menggunakannya operator.attrgetter("count"). Namun, ini mungkin berjalan pada versi pra-operator Python, jadi alangkah baiknya memiliki mekanisme mundur. Anda mungkin ingin melakukan hal berikut, kemudian:

try: import operator
except ImportError: keyfun= lambda x: x.count # use a lambda if no operator module
else: keyfun= operator.attrgetter("count") # use operator since it's faster than lambda

ut.sort(key=keyfun, reverse=True) # sort in-place
tzot
sumber
7
Di sini saya akan menggunakan nama variabel "keyfun" alih-alih "cmpfun" untuk menghindari kebingungan. Metode sort () tidak menerima fungsi perbandingan melalui argumen cmp = juga.
akaihola
Ini tampaknya tidak berfungsi jika objek memiliki atribut yang ditambahkan secara dinamis, (jika Anda telah melakukan self.__dict__ = {'some':'dict'}setelah __init__metode). Saya tidak tahu mengapa itu bisa berbeda.
tutuca
@tutuca: Saya tidak pernah mengganti instance __dict__. Perhatikan bahwa "objek yang memiliki atribut yang ditambahkan secara dinamis" dan "pengaturan __dict__atribut objek" adalah konsep yang hampir ortogonal. Saya mengatakan itu karena komentar Anda tampaknya menyiratkan bahwa menetapkan __dict__atribut adalah persyaratan untuk menambahkan atribut secara dinamis.
tzot
@tzot: Saya melihat ini: github.com/stochastic-technologies/goatfish/blob/master/… dan menggunakan iterator di sini: github.com/TallerTechnologies/dishey/blob/master/app.py#L28 raises kesalahan atribut. Mungkin karena python3, tapi masih ...
tutuca
1
@tzot: jika saya mengerti penggunaan operator.attrgetter, saya bisa menyediakan fungsi dengan nama properti apa pun dan mengembalikan koleksi yang diurutkan.
IAbstract
64

Pembaca harus memperhatikan bahwa kunci = metode:

ut.sort(key=lambda x: x.count, reverse=True)

jauh lebih cepat daripada menambahkan operator perbandingan kaya ke objek. Saya terkejut membaca ini (halaman 485 "Python in a Nutshell"). Anda dapat mengonfirmasi ini dengan menjalankan tes pada program kecil ini:

#!/usr/bin/env python
import random

class C:
    def __init__(self,count):
        self.count = count

    def __cmp__(self,other):
        return cmp(self.count,other.count)

longList = [C(random.random()) for i in xrange(1000000)] #about 6.1 secs
longList2 = longList[:]

longList.sort() #about 52 - 6.1 = 46 secs
longList2.sort(key = lambda c: c.count) #about 9 - 6.1 = 3 secs

Tes saya, sangat minimal, menunjukkan jenis pertama lebih dari 10 kali lebih lambat, tetapi buku itu mengatakan itu hanya sekitar 5 kali lebih lambat secara umum. Alasan mereka mengatakan ini karena algoritma pengurutan yang sangat optimal yang digunakan dalam python ( timsort ).

Namun, sangat aneh bahwa .sort (lambda) lebih cepat daripada .sort (). Saya harap mereka memperbaikinya.

Jose M Vidal
sumber
1
Mendefinisikan __cmp__sama dengan memanggil .sort(cmp=lambda), bukan .sort(key=lambda), jadi tidak aneh sama sekali.
tzot
@tzot benar sekali. Urutan pertama harus membandingkan objek satu sama lain berulang kali. Sort kedua mengakses setiap objek hanya sekali untuk mengekstraksi nilai hitungannya, dan kemudian melakukan sortasi numerik sederhana yang sangat dioptimalkan. Perbandingan yang lebih adil adalah longList2.sort(cmp = cmp). Saya mencoba ini dan kinerjanya hampir sama .sort(). (Juga: perhatikan bahwa parameter sortir "cmp" telah dihapus dengan Python 3.)
Bryan Roach
43

Pendekatan berorientasi objek

Adalah praktik yang baik untuk membuat logika pemilahan objek, jika berlaku, properti kelas daripada dimasukkan dalam setiap contoh pemesanan diperlukan.

Ini memastikan konsistensi dan menghilangkan kebutuhan kode boilerplate.

Minimal, Anda harus menentukan __eq__dan __lt__operasi agar ini berfungsi. Maka gunakan saja sorted(list_of_objects).

class Card(object):

    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __eq__(self, other):
        return self.rank == other.rank and self.suit == other.suit

    def __lt__(self, other):
        return self.rank < other.rank

hand = [Card(10, 'H'), Card(2, 'h'), Card(12, 'h'), Card(13, 'h'), Card(14, 'h')]
hand_order = [c.rank for c in hand]  # [10, 2, 12, 13, 14]

hand_sorted = sorted(hand)
hand_sorted_order = [c.rank for c in hand_sorted]  # [2, 10, 12, 13, 14]
jpp
sumber
1
Itu yang saya cari! Bisakah Anda mengarahkan kami ke beberapa dokumentasi yang menguraikan mengapa __eq__dan __lt__apakah persyaratan implementasi minimum?
FriendFX
1
@FriendFX, saya yakin ini tersirat oleh ini :•The sort routines are guaranteed to use __lt__() when making comparisons between two objects...
jpp
2
@FriendFX: Lihat portingguide.readthedocs.io/en/latest/comparisons.html untuk Perbandingan dan Penyortiran
Cornel Masson
37
from operator import attrgetter
ut.sort(key = attrgetter('count'), reverse = True)

sumber
16

Ini terlihat seperti daftar contoh model ORM Django.

Mengapa tidak mengurutkannya pada permintaan seperti ini:

ut = Tag.objects.order_by('-count')
muhuk
sumber
Ya, tetapi menggunakan tagging django, jadi saya menggunakan built-in untuk meraih satu set Tag dengan menggunakan set query tertentu, seperti: Tag.objects.usage_for_queryset (QuerySet, counts = True)
Nick Sersan
11

Tambahkan kaya operator perbandingan ke kelas objek, lalu gunakan metode sort () dari daftar.
Lihat perbandingan kaya dalam python .


Pembaruan : Meskipun metode ini akan berhasil, saya pikir solusi dari Triptych lebih cocok untuk kasus Anda karena cara yang lebih sederhana.

rampok
sumber
3

Jika atribut yang ingin Anda urutkan berdasarkan properti , maka Anda dapat menghindari mengimpor operator.attrgetterdan menggunakan metode properti fgetsebagai gantinya.

Misalnya, untuk kelas Circledengan properti radiuskita bisa mengurutkan daftar circlesberdasarkan jari-jari sebagai berikut:

result = sorted(circles, key=Circle.radius.fget)

Ini bukan fitur yang paling terkenal tetapi sering menyelamatkan saya sejalan dengan impor.

Georgy
sumber