1. 程式人生 > >如何利用sorted函式對列表,元組或者物件進行排序

如何利用sorted函式對列表,元組或者物件進行排序

Python中對一個列表或者元組進行排序是很簡單的!既然元組基本上可以認為是一個不可以修改的陣列,我們幾乎可以將其視為一個列表。

對Python列表排序的最簡單的方式

如果你只是想對一個數字列表進行排序,Python內建的函式可以幫你實現。

我們有一個數字列表:

>>> a = [3, 6, 8, 2, 78, 1, 23, 45, 9]
我們想對其進行升序排列。我們可以對列表呼叫sort函式,進行原地排序,或者是呼叫內建的sorted函式,不修改原始列表,並返回一個新的排好序的列表。兩個函式都有相同的引數,基於目的來說可以認為是相同的,除了我們上面提到的不同。

我們試一下:

>>> sorted(a)
[1, 2, 3, 6, 8, 9, 23, 45, 78]
>>>
>>> a.sort()
>>> a
[1, 2, 3, 6, 8, 9, 23, 45, 78]
如果想要實現降序排序呢,如下所示:
>>> sorted(a, reverse=True)
[78, 45, 23, 9, 8, 6, 3, 2, 1]
>>> a.sort(reverse=True)
>>> a
[78, 45, 23, 9, 8, 6, 3, 2, 1]
在這種場景下Python在背後做了什麼呢?它在列表中呼叫一個mergesort的版本。當比較值時,它呼叫__cmp__函式在每一個物件上,基於__cmp__的返回值,來決定哪一個應該放在另一個的前面。返回值為0代表兩個值相等,1代表大於,-1代表小於。後面我們會用到這些資訊來做我們自己的物件的排序。

你可能會說,對元組排序又是什麼情況呢?我們這就開始介紹。

對Python元組排序的最簡單方式

既然元組是一個你不能改變的陣列,所以沒有一個原地排序的函式可以直接被呼叫。它們必須使用sorted函式來返回一個列表。記住,這兒是怎麼做的:

>>> tup = (3, 6, 8, 2, 78, 1, 23, 45, 9)
>>> sorted(tup)
[1, 2, 3, 6, 8, 9, 23, 45, 78]
注意sorted返回的是一個數組。

好了,我們開始看看如何對更復雜的進行排序。

對列表的列表或者元組的列表進行排序

這有點複雜,但是仍然很簡單,所以不用害怕!sorted函式和sort函式都接受一個關鍵字引數key。

key的作用是提供了一種方式指定一個函式,該函式返回你想要你的元素怎樣排序。這個函式有一個"不可見"的引數,代表了一個列表中的元素,並返回一個你想要的元素的key來排序的值。

讓我們用示例說明這個key關鍵字引數!

所以,取一個新的列表,我們想測試根據每個子列表的第一個元素進行排序:

>>> def getKey(item):
...     return item[0]
>>> l = [[2, 3], [6, 7], [3, 34], [24, 64], [1, 43]]
>>> sorted(l, key=getKey)
[[1, 43], [2, 3], [3, 34], [6, 7], [24, 64]]
這裡我們看到列表根據子列表的第一個值進行了升序排序。你也可以使用sort函式,但是我個人認為sorted函式更好,所以在後面的例子中我還會使用它。

發生什麼?還記得我之前談到的"不可見"引數嗎?那就是每一次sorted需要一個值,傳入getKey函式的值。這是一個Python的小技巧。

如果想要對子列表的第二個值進行排序,很簡單,只需要改變getKey函式像下面這樣:

def getKey(item):
    return item[1]
好了,一切都很好。那如何對元組的列表進行排序呢?很高興你問了這個!

實際上和我們上面的例子是完全一樣的,但是列表定義如下:

>>> a = [(2, 3), (6, 7), (3, 34), (24, 64), (1, 43)]
>>> sorted(l, key=getKey)
[(1, 43), (2, 3), (3, 34), (6, 7), (24, 64)]
唯一改變的是我們現在得到了一個元組的列表,而不是一個列表的列表。

同樣的方法也可以作用在元組的元組上,所以我就不深入介紹它了,因為那就顯得多餘了。

對自定義Python物件的列表進行排序

這裡是我自定義的一個物件:
class Custom(object):
    def __init__(self, name, number):
        self.name = name
        self.number = number
為了排序的目的,我們建立一個它們的列表:
customlist = [
    Custom('object', 99),
    Custom('michael', 1),
    Custom('theodore the great', 59),
    Custom('life', 42)
]
好了,我們得到了一個花哨的自定義物件的列表,並且我們想要對它進行排序。我們應該怎麼做呢?

好的,我們可以定義一個函式,就像我們上面做的那樣,接受元素並返回一個列表。所以我們這麼做。

def getKey(custom):
    return custom.number
有一點不同的是,因為我們的物件不再是一個列表。這允許我們根據自定義物件的數字屬性進行排序。

所以如果我們在我們的花裡胡哨的自定義物件上應用sorted函式,我們得到這些:

>>> sorted(customlist, key=getKey)
[<__main__.Custom object at 0x7f64660cdfd0>,
<__main__.Custom object at 0x7f64660d5050>,
<__main__.Custom object at 0x7f64660d5090>,
<__main__.Custom object at 0x7f64660d50d0>]
一大堆我們看不明白的東西。很好。但是不用擔心,親愛的讀者,有一個簡單的方法可以作用到我們的自定義物件上,使它看起來好看一些!

我們重新定義物件類:

class Custom(object):
    def __init__(self, name, number):
         self.name = name
         self.number = number
    
    def __repr__(self):
         return '{}: {} {}'.format(self.__class__.__name__,
                                   self.name,
                                   self.number)
好的,我們對它做了什麼呢?首先,__repr__函式告訴Python我們想讓物件如何被表達。在更復雜的情況下,當它被列印在螢幕上時,它告訴直譯器如何顯示物件。
現在我們試著對它再一次排序:
>>> sorted(customlist, key=getKey)
[Custom: michael 1, Custom: life 42,
 Custom: theodore the great 59, Custom: object 99]
這看起來好很多了!我們現在實際已經知道排序是正確的!

但是,仍然有一點小問題。看起來有點吹毛求疵,但是我不想每次我想要呼叫sorted的時候都輸入key關鍵字

我們再次重新定義我們的物件,如下所示:

class Custom(object):
    def __init__(self, name, number):
        self.name = name
        self.number = number
 
    def __repr__(self):
        return '{}: {} {}'.format(self.__class__.__name__,
                                  self.name,
                                  self.number)
 
    def __cmp__(self, other):
        if hasattr(other, 'number'):
            return self.number.__cmp__(other.number)
看起來很好。它所做的是告訴Python如何去比較當前物件的值與在列表中的另一個物件的值。如我所述,sorted函式將會呼叫__cmp__函式在物件上,為了決定它應該放在那兒根據與其它物件的關係。

現在我們可以呼叫sorted而不用擔心包括key關鍵字,如下所示:

>>> sorted(customlist)
[Custom: michael 1, Custom: life 42, Custom: theodore the great 59, Custom: object 99]
它工作的很好。請注意所有上面的也作用在自定義物件組成的元組上。但是,正如你知道的,我喜歡節省我的數字樹(digital tree)。

對各種各樣的Python自定義物件列表的排序

好吧。既然Python是一門動態語言,它並不是很關心我們扔到列表中的是什麼物件。它們可以是相同的型別,或者完全不同。

所以,我們定義另一個不同的物件來使用我們的Custom物件。

class AnotherObject(object):
     def __init__(self, tag, age, rate):
          self.tag = tag
          self.age = age
          self.rate = rate

     def __repr__(self):
         return '{}: {} {} {}'.format(self.__class__.__name__,
                                      self.tag,
                                      self.age,
                                      self.rate)

     def __cmp__(self, other):
         if hasattr(other, 'age'):
             return self.age.__cmp__(other.age)
這是一個相似的物件,但是和我們的Custom物件仍然有些不同。
讓我們建立一個這些物件與Custom物件的列表:
customlist = [
    Custom('object', 99),
    Custom('michael', 1),
    Custom('theodore the great', 59),
    Custom('life', 42),
    AnotherObject('bananas', 37, 2.2),
    AnotherObject('pants', 73, 5.6),
    AnotherObject('lemur', 44, 9.2)
]
現在我們試著在列表上執行sorted函式:
>>> sorted(customlist)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: an integer is required
我們得到了一個可愛的錯誤。為什麼?因為Custom沒有叫做age的屬性,AnotherObject沒有叫做number的屬性。

我們能做什麼呢?Panic!

只是開個玩笑而已。我們知道做什麼。讓我們再次重新頂一個這些物件。

class Custom(object):
    def __init__(self,name,number):
        self.name = name
        self.number = number
 
    def __repr__(self):
        return '{}: {} {}'.format(self.__class__.__name__,
                                  self.name,
                                  self.number)
 
    def __cmp__(self, other):
        if hasattr(other, 'getKey'):
            return self.getKey().__cmp__(other.getKey())
 
    def getKey(self):
        return self.number
 
 
class AnotherObject(object):
    def __init__(self, tag, age, rate):
        self.tag = tag
        self.age = age
        self.rate = rate
 
    def __repr__(self):
        return '{}: {} {} {}'.format(self.__class__.__name__,
                                     self.tag,
                                     self.age, self.rate)
 
    def __cmp__(self, other):
        if hasattr(other, 'getKey'):
            return self.getKey().__cmp__(other.getKey())
 
    def getKey(self):
        return self.age

多麼令人驚歎呀!我們剛才做了些什麼?我們定義了一個公共的getKey函式,兩個物件都有該公共的函式,所以我們可以輕易地進行比較。

現在我們再執行一次sorted函式,我們得到:

>>> sorted(customlist)
[Custom: michael 1, AnotherObject: bananas 37 2.2,
Custom: life 42, AnotherObject: lemur 44 9.2,
Custom: theodore the great 59, AnotherObject: pants 73 5.6,
Custom: object 99]
很好!現在我們的物件可以比較並排序,根據它們核心的內容。

你說你仍然喜歡使用key關鍵字?
你也可以這麼做。如果你省去每個物件中的__cmp__函式,並且在函式外定義類似於下面的函式:

def getKey(customobj):
    return customobj.getKey()
然後像下面這樣呼叫sorted:
>>> sorted(customlist, key=getKey)
[Custom: michael 1, AnotherObject: bananas 37 2.2,
Custom: life 42, AnotherObject: lemur 44 9.2,
Custom: theodore the great 59, AnotherObject: pants 73 5.6,
Custom: object 99]
這裡你學會了它。非常簡潔,但是並不像一些人想的那樣簡潔。Python使用內建的sorted函式使他變得很容易。

離成為Python專家又更近了一步。




本文翻譯自這篇文章