初探 sort 方法和 sorted 內置函數

本文主要介紹 Python 中 sort 方法和 sorted 函數的基本用法、高級用法、Timsort 算法的簡單介紹、二者異同等。閱讀本文預計 6 min.

1. 前言

Python 中列表的 sort 方法和內置函數 sorted 非常有用。自己以前只是用到了一點皮毛,沒有進一步深入瞭解。今天剛好看了相關的知識,對於 sort 和 sorted 有了進一步的認知,就總結了一篇,大家一起學習交流。

2. sort 方法和 sorted 函數的基本用法

先簡單說明一下方法(Method)和函數(Function),很多時候大多數人不區分方法和函數,基本當做一回事,這其實沒啥問題。但是看 Python 官方文檔的時候,有時候出現 Method 有時候又是 Function,讓人很頭疼,所以區分一下方法和函數還是有好處的。其實區分也比較簡單。

方法(Method):是通過 obj.funcname() 來引用調用。如:列表的 sort 方法,調用時就是 list.sort()。
函數(Function):是通過 funcname() 直接調用。如內置函數(built-in function) sorted,調用時就是 sorted()。

注:Python API 的一個慣例(convention)是:如果一個函數或者方法是原地改變對象,那麼應該返回 None。這麼做的目的是爲了告訴調用者對象被原地改變了。這個約定的弊端是無法級聯調用(cascade call)這些方法。而返回新對象的方法可以級聯調用,從而形成連貫的接口(fluent interface)。

接下來,進入 sort 方法和 sorted 基本使用的學習。

  • list.sort():對列表進行原地(in place)排序,默認是升序,返回值是 None。
  • sorted():對可迭代對象進行排序,默認是升序,並把排序結果作爲一個新的列表返回,原迭代對象順序不受影響。

舉栗子:

>>> list_a = [1, 5, 6, 4, 2, 3]
>>> sorted(list_a)  # 返回一個新的排好序的 list
[1, 2, 3, 4, 5, 6]
>>> list_a  # list_a 不改變
[1, 5, 6, 4, 2, 3]
>>> print(list_a.sort())  # list_a.sort() 就地排序,返回值是 None
None
>>> list_a  # list_a 發生改變
[1, 2, 3, 4, 5, 6]

此外,sorted 函數除了用於列表,還可用於其他可迭代對象,如元組、生成器等。

>>> sorted((1, 5, 9, 2, 6, 8, 7))  # 對元組排序
[1, 2, 5, 6, 7, 8, 9]
>>> sorted('afbcedh')  # 對字符串排序
['a', 'b', 'c', 'd', 'e', 'f', 'h']
>>> list(range(10,0,-1))
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> sorted(range(10,0,-1))  # 對生成器排序
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> sorted({1: 'D', 2: 'B', 3: 'B', 4: 'E', 5: 'A'})
[1, 2, 3, 4, 5]

以上就是 sort 方法和 sorted 函數最基本的用法。可以發現都是升序排列的,如果我們想降序排列怎麼辦?

3. 可選僅限關鍵字參數 reverse

sort 方法和 sorted 函數都可以接收一個可選僅限關鍵字參數(keyword-only arguments) reverse,用於指定是升序(Ascending)還是降序(Descending)。默認 reverse=False 即升序排序。

舉栗子:

>>> list_a = [3, 1, 2, 4]
>>> sorted(list_a)  # 默認升序
[1, 2, 3, 4]
>>> sorted(list_a, reverse=True)  # 降序排序
[4, 3, 2, 1]
>>> list_a  # list_a 不變
[3, 1, 2, 4]
>>> list_a.sort()  # 默認原地升序排序
>>> list_a
[1, 2, 3, 4]
>>> list_a.sort(reverse=True)  # 原地降序排序
>>> list_a
[4, 3, 2, 1]

注: 僅限關鍵字參數 即只能通過 keyword=value 的形式傳參的參數叫僅限關鍵字參數,有關 Python 函數僅限關鍵字參數、僅限位置參數、可變參數、默認參數、位置參數、可變關鍵字參數等可以參看我的另一篇總結。 一文了解Python函數

接下來我們介紹另一個更加強大的可選僅限關鍵字參數 key。

4. 可選僅限關鍵字參數 key

sort 方法和 sorted 函數還可以接收一個可選僅限關鍵字參數 key,key 是一個只有一個參數的函數,這個函數會依次作用於序列的每一個元素,並將所得的結果作爲排序的依據。key 默認是 None,即恆等函數(identity function),也就是默認用元素自己的值排序。

舉栗子:

>>> list_a = ['This', 'is', 'a', 'test', 'string', 'from', 'Andrew']
>>> sorted(list_a, key=str.lower)
['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
>>> list_a
['This', 'is', 'a', 'test', 'string', 'from', 'Andrew']

這裏的 key=str.lower 就是把 list_a 中的每個元素都用 str.lower 方法處理,即:都轉爲小寫,然後再進行比較排序。

對於複雜的數據對象,我們可以用對象的索引,取對象的一部分作爲比較的 key,這時匿名函數會幫助我們,舉栗子:

>>> student_tuples = [
...     ('john', 'A', 15),
...     ('jane', 'B', 12),
...     ('dave', 'B', 10),
... ]
>>> sorted(student_tuples, key=lambda student: student[2])   # 根據年齡排序
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

我們還可以取對象的屬性作爲比較的 key,舉栗子:

>>> class Student:
...     def __init__(self, name, grade, age):
...         self.name = name
...         self.grade = grade
...         self.age = age
...     def __repr__(self):
...         return repr((self.name, self.grade, self.age))
...
>>> student_objects = [
...     Student('john', 'A', 15),
...     Student('jane', 'B', 12),
...     Student('dave', 'B', 10),
... ]
>>> sorted(student_objects, key=lambda student: student.age)   # 按年齡進行排序
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

可以看到 key 非常的強大!使得複雜的數據結構可以按照我們自己的意願進行排序。
list.sort、sorted、max 和 min 中可選僅限關鍵字參數 key 的設計非常棒!使用 key 會更加高效且簡潔。簡單是指你只需要定義一個只有一個參數的函數,用於排序即可。高效是指 key 的函數在每個元素上只會調用一次,而雙參數比較函數,則每次兩兩比較的時候都會被調用。(PS:這一點還不是特別理解)

5. operator 模塊的使用

因爲通過索引或者屬性以及函數來排序非常常用,所以 Python 內置的 operator 模塊提供了 itemgetter(), attrgetter() 和 methodcaller() 函數來更加簡單而快速的實現相關的功能。

舉栗子:

>>> from operator import itemgetter, attrgetter  # 導入相關的函數
>>> sorted(student_tuples, key=itemgetter(2))  # 按索引取值排序
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
>>> sorted(student_objects, key=attrgetter('age'))  # 按屬性取值排序
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

此外,通過 operator 模塊還可以輕鬆實現多級排序,比如上面的學生列表,先按成績排序,再按年齡排序。

>>> sorted(student_tuples, key=itemgetter(1,2))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
>>> sorted(student_objects, key=attrgetter('grade', 'age'))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

6. sort 方法和 sorted 函數背後的排序算法

sorted 和 list.sort 背後的排序算法都是 Timsort 算法,它是一種自適應算法,會根據原始數據的特點交替使用歸併排序(merge sort)和插入排序(insertion sort)。

在實際應用場景中這是非常有效的一種穩定排序算法。Timsort 在 2002 年開始在 CPython 中使用,2009 年起,開始在 Java 和 Android 中使用。

這裏解釋一下穩定排序算法,它指排序前和排序後,值相同的元素不交換位置。

舉個栗子就明白了:

>>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
>>> sorted(data, key=itemgetter(0))  # 按照顏色排序
[('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]

可以看到原始序列是 (‘red’, 1) 在 (‘red’, 2)前面,排序後,因爲 red 相同,二者不交換位置,(‘red’, 1) 依然在 (‘red’, 2)前面,這就叫做穩定排序。反之,如果交換了,就叫不穩定。

穩定排序非常重要,因爲它可以讓我們進行多步排序。
比如我們要實現對學生數據進行排序,要求是以成績降序排列,如果成績相同,則再按年齡升序排序。
那麼我們可以通過兩步實現:第 1 步是先以年齡升序排列,第 2 步再以成績等級降序排列。注:按照算法,升序的話,B 的等級排在 A 後面。

代碼如下:

>>> s = sorted(student_objects, key=attrgetter('age'))
>>> sorted(s, key=attrgetter('grade'), reverse=True)
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

上面就實現了成績降序排序,成績相同則年齡升序排序。上面的代碼我們也可以整合到一行,要注意順序:

>>> sorted(sorted(student_objects, key=attrgetter('age')), key=attrgetter('grade'), reverse=True)
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Python 大多數排序(sort 方法和 sorted 函數)用的都是 Timsort 排序算法,它是一種混和的穩定排序算法,更多關於 Timsort 排序算法的知識可以參考學習Wiki Timsort

7. sort 方法和 sorted 函數的異同

基本上學習完了,我們來小結一下 sort 和 sorted 的異同吧。

先放一下二者的官方用法

sort 的用法:

sort(self, /, *, key=None, reverse=False)
    Sort the list in ascending order and return None.

    The sort is in-place (i.e. the list itself is modified) and stable (i.e. the
    order of two equal elements is maintained).

    If a key function is given, apply it once to each list item and sort them,
    ascending or descending, according to their function values.

    The reverse flag can be set to sort in descending order.

sorted 的用法:

Help on built-in function sorted in module builtins:

sorted(iterable, /, *, key=None, reverse=False)
    Return a new list containing all items from the iterable in ascending order.

    A custom key function can be supplied to customize the sort order, and the
    reverse flag can be set to request the result in descending order.

接下來比較二者的異同。

相同點:

  1. sort 和 sorted 都有兩個可選僅限關鍵字參數 key 和 reverse,都是默認升序排序。

不同點:

  1. sort 是列表的一個方法,它的第一個參數是 self,即列表實例對象本身;sorted 是內置函數,它的第一個參數是 iterable,即可迭代對象。所以 sorted 不止可以作用於列表,還可以作用於元素、字典等可迭代對象。
  2. sort 方法是對列表原地排序,返回值是 None;sorted 函數是返回一個新的列表,不改變原可迭代對象。
  3. sort 方法不能級聯調用,sorted 函數可以級聯調用。

8. 巨人的肩膀

  1. Sorting HOW TO
  2. Timsort
  3. 《Fluent Python》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章