python 的 tuple 是不是冗餘設計?

一般來說,tuple 是不可變的(immutable)可以包含多種類型成員的數據結構,list 是可變的包含同類型成員的數據結構。

但是 python 的 list 實際上是可以包含不同類型成員的,而 tuple 也支持順序遍歷,也就是說 list 的功能比 tuple 只多不少,或者說 tuple 只是個被解釋器限制為 immutable 的 list,這麼看來似乎沒必要引入 tuple 這個數據結構。

而且 tuple 並沒有相比 list 讓代碼更簡單,所以也不同於 STL 里讓編程變得更方便的容器適配器(比如用 stack 適配 deque),在實踐中經常會看到各種 tuple 和 list 互相轉的情況,反而讓代碼變得更難看,是不是不符合 "simple is better than complex" 的思想?


瀉藥,這個問題應該等價於『tuple有什麼list不可替代的優點』。

tuple這樣的設計當然不是『人為的限制』,事實上,這是一種trade off,tuple以放棄對元素的增刪為代價必然換取了一些list所沒有的優點,是什麼優點呢?

是全面優於list的性能

我們來做幾個實驗

1.創建

In [1]: %timeit [1, 2, 3, 4, 5]
128 ns ± 1.51 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [2]: %timeit (1, 2, 3, 4, 5)
15 ns ± 0.166 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

創建同樣大小的list和tuple,可以看到list的時間開銷幾乎是tuple的10倍

@冒泡 指出元素全部為immutable的tuple會作為常量在編譯時確定,因此產生了如此顯著的速度差異, 因此用用戶定義的類作第二次測試

In [34]: class foo:
...: pass
...:

In [35]: %timeit [foo(), foo(), foo()]
444 ns ± 7.29 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [36]: %timeit (foo(), foo(), foo())
363 ns ± 2.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

可以看到tuple仍然快於list,只是並沒有如此明顯的差異

2. 遍歷

In [3]: lst = [i for i in range(0xffff)]

In [4]: tpl = tuple(i for i in range(0xffff))

In [5]: %timeit for each in lst: pass
782 μs ± 15.8 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [6]: %timeit for each in tpl: pass
760 μs ± 9.38 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

對兩個大表進行遍歷,tuple與list速度相似(感謝 @採石工 指出疏漏)

3.空間開銷

In [8]: from sys import getsizeof

In [9]: getsizeof(lst)
Out[9]: 578936

In [10]: getsizeof(tpl)
Out[10]: 524328

儲存同樣元素的list和tuple, list有更多空間開銷

另外,tuple還具有一些list沒有的特性(也不能算作優點吧),比如因為tuple是 immutable, 所以它是可哈希的(hashable)的。

In [11]: hash(tpl)
Out[11]: 7487529697070271394

In [12]: hash(lst)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
& in &()
----&> 1 hash(lst)

TypeError: unhashable type: "list"

這使得tuple可以作為dict的key,或者扔進set里,list則不行

以上


最重要的好處是可以做dict的key(如果內部也是immutable的類型)。次一點的好處是由於確保不能修改,可以安全地在多個對象之間共享。

tuple類型對於Python自身來說是非常重要的數據類型,比如說函數調用,實際上會將順序傳入的參數先組成一個tuple;多返回值也是靠返回一個tuple來實現的。因為太常用,所以需要一個更有效的數據結構來提高效率,一個不可變的tuple對象從實現上來說可以比list簡單不少。再比如說code對象會記錄自己的參數名稱列表,free variable名稱列表等等,這些如果用list,就可能被從外部修改,這樣可能導致解釋器崩潰;那就只能選擇改成一個函數每次都返回一個新的列表,這樣又很浪費。所以即使是從解釋器自身實現的角度上來說引入這樣一個不可變的序列類型也是很重要的。

對程序員來說如果沒有什麼美學上的追求的話,tuple最大的便利在於它是一個hashable的類型,而且hash演算法與值直接對應,這樣在Python里很容易用多個值的組合來做key生成一個dict,比如說我們網路里有20台交換機,每個交換機有24個口,那要唯一標識每個口就需要用(交換機ID,口編號),這個tuple可以做dict的key的話,編寫程序起來就很方便了。


immutable的好處實在是太多了:

性能優化,多線程安全,不需要鎖,不擔心被惡意修改或者不小心修改。

Java為了返回只讀List,還得加一層:

Collections.unmodifiableList()

很多時候加上限制往往是好事,絕對的自由帶來的更多是混亂和不可控。

你看Java用GC並且不允許直接操控內存,比C++限制太多了,然而它提升了絕大多數程序員的代碼質量。

iOS限制App只能沙盒運行,Android允許App干很多事情,結果同一家的App在兩個平台一個是良民一個是流氓。

最後,Python的tuple還用來返回多值:

x, y = getPoint()

完全不需要包裝類


元組最初意圖是用來記錄欄位位置的,後來變成可迭代,才有了「不可改變的list」這種說法。

my_info = ("Woody", 25, Male())

name, age, gender = my_info

形似這種用法才是本想讓tuple做的事情。

首先是Immutable和Hashable,然後才是 Iterable 。

摘選一段Fluent Python 里提到的軼聞,從中可以體會一二。

The nature of tuples

In 2012 I presented a poster about the ABC language at PyCon US. Before creating Python, Guido had worked on the ABC interpreter, so he came to see my poster. Among other things, we talked about the ABC compounds which are clearly the predecessors of Python tuples. Compounds also support parallel assignment and are used as com‐ posite keys in dictionaries (or tables, in ABC parlance). However, compounds are not sequences. They are not iterable and you cannot retrieve a field by index, much less slice them. You either handle the compound as whole or extract the individual fields using parallel assignment, that』s all. I told Guido that these limitations make the main purpose of compounds very clear: they are just records without field names. His response: 「Making tuples behave as se‐ quences was a hack.」 This illustrates the pragmatic approach that makes Python so much better and more successful than ABC. From a language implementer perspective, making tuples behave as sequences costs little. As a result, tuples may not be as 「conceptually pure」 as com‐ pounds, but we have many more ways of using them. They can even be used as immut‐ able lists, of all things! It is really useful to have immutable lists in the language, even if their type is not called frozenlist but is really tuple behaving as sequence.

下面是翻譯版的

元組的本質

2012年,我在PyCon US上貼了一張關於ABC語言的牆報。Guido在開創Python語 言之前曾做過 ABC 解釋器方面的工作,因此他也去看了我的牆報。我們聊了不少,而 且都提到了 ABC 里的 compounds 類型。compounds 算得上是 Python 元組的鼻祖,它既 支持平行賦值,又可以用在字典(dict)里作為合成鍵(ABC 里對應字典的類型是表 格,即 table)。但 compounds 不屬於序列,它不是迭代類型,也不能通過下標來提取 某個值,更不用說切片了。要麼把 compounds 對象當作整體來用,要麼用平行賦值把 裡面所有的欄位都提取出來,僅此而已。

我跟 Guido 說,上面這些限制讓 compounds 的作用變得很明確,它只能用作沒有欄位名 的記錄。Guido 回應說,Python 里的元組能當作序列來使用,其實是一個取巧的實現。

這其實體現了 Python 的實用主義,而實用主義是 Python 較之 ABC 更好用也更成功的 原因。從一個語言開發人員的角度來看,讓元組具有序列的特性可能需要下點功夫, 結果則是設計出了一個概念上並不如 compounds 純粹,卻更靈活的元組——它甚至能 當成不可變的列表來使用。

說真的,不可變列表這種數據類型在編程語言里真的非常好用(其實 frozenlist 這個 名字更酷),而 Python 里這種類型其實就是一個行為很像序列的元組。


不冗餘,很實用。但是設計的概念不夠清晰。

  • 學過一點函數式語言的人都知道,從類型的角度:
    • Tuple是由幾個類型有序構成的匿名類型;
    • List是某單一類型的任意長度序列;

  • 如果要拿C語言來類比,Tuple對應的是struct,而List對應的是array。

  • 然而我們還有更魔幻的依賴類型語言。裡面我們可以把類型當值用,Tuple可以認為是List of Types,比如可以寫一個類型安全的列印任意Tuple的函數(?)。然而大部分程序員(包括我)還不具備在依賴類型層面表述代碼的能力(

  • 從實現層面,Tuple作為匿名類型的性質使得它的空間可以在編譯期確定,而List任意長度的性質使得運行時的再分配和重用都需要設計,至於用動態數組、鏈表還是手指樹神馬的都行。

  • 以上都是從類型出發的考慮。至於值是否可變是另一個獨立的問題。你完全可以搞一門新語言,Tuple的分量值是可以更新的(就像更新結構的欄位),List是不可變的(純函數式語言都這樣)。

  • 回到Python本身,無非是在以上幾個問題里作了些選擇,且實現了Tuple的可迭代方法(個人覺得這是個槽點)。但我覺得用著Python、腦子裡想著函數式還是極好的,畢竟新出來的async/await就是個假冒的Monadic-do的語法糖。

  • 若合理區分Tuple和List,當未來某靜態類型的Python的子集語言面世,移植工作也簡單一點(


python不熟, 但是如JavaScript或php之類語言, 因為不支持元組, 嚴重影響了編程體驗和代碼可讀性,用過從語言層面原生支持元組語法的語言後, 才會覺得不支持元組的語言真的low到了家,元組真的是編程語言中不可多得的優良特性。

元組最重要的特點是用來實現多值返現, 對於天生就支持多值返回特性的語言, 如golang, 元組存在的意義不大。 然而, 對於只支持單反回值函數的語言碰到有多返回值需求的場景時就操蛋了, 假如要實現多返回值效果, 通常有兩種方案。

第一種, 把這些要返回的值塞入一個對象,然後返回這個對象。然而, 這種做法會增加不必要的代碼量, 在類似於Java之類的死板語言中的做法是先寫一個類,實例化後填充數據再返回, 折騰,造孽。JavaScript和php中稍微好點, 直接返回object或array就可以,但是要為返回值指定名稱這一步也是冗餘的,很多時候通過返回值的類型就能確定返回值的作用了,指定名稱多此一 舉。

第二種, 把這些要返回的值塞入一個列表, 然後返回這個列表。 然而, 這種做法會對代碼的可讀性造成影響, 在Java之類的靜態語言中,根本無法使用這種方法, 它的語言特性規定 , 列表數據結構中只能存放同類型的元素。 當然, 你可以使用Object類型的列表, 但以這種方式寫出來的代碼, 但是我相信任何一個技術水平過關或者有職業操守的程序員都不會這麼做。 而對於支持多類型元素列表的語言如JavaScript、php以及python到的確可以以這種方式實現,但是在這些語言的世界裡,所有的列表都是一樣的

[]

[1,2,3]

["a","b","c"]

[1,2,3,"a","b","c"]

這些列表對於這類語言來說長的是沒差的。

而元組卻不同, 每個不同規格的元組是不同的

(1,2,3)

("a","b","c")

(1,2,3,"a","b","c")

是不同的, 因為它們背後的定義分別是

(int,int,int)

(string,string,string)

(int,int,int,string,string,string)

或許你認為,動態類型語言中, 根本沒有編繹期類型檢查一說, 所有類型都是在運行期確定的, 所以不從性能的角度考慮, 元組和列表還是沒有差別的。 然而, 現在編程語言的IDE基本都具備了語法檢查的能力, 就算是動態類型語言,不按照函數返回值的規格去使用返回值,IDE也能給出錯誤提示, 因此,把元組和列表進行區分,對代碼的可讀性還是很有幫助的。 而且在靜態類型語言中這種好處會被放大,連c#都要把元組做成語言內置的語法特性,這足以說明元組能起到的作用不容小噓。

最後,我想

Let tuple = (1,"a",true);

Let v1, v2,v3 = tuple;

總比

Let list = [1,"a",true];

Let v1 = list[0];

Let v2 = list[1];

Let v3 = list[2];

來的方便吧。

因此, 有什麼理由不使用元組呢?


刷leetcode的時候就知道tuple的好了……能不能hash簡直是兩個世界


我覺得不是啊。immutable 的類型跟 mutable 的類型本質上就是不一樣的,即使不是為了性能,很多地方都應該優先考慮使用 immutable 的類型。


我從演算法上來說一下,

從均攤分析可知, 如果一個array, 一旦滿了之後, 重新開一個2(大於1即可)倍長的array, 把原有的拷貝過來, 那麼對於每一個append操作來說均攤複雜度就是O(1).

這樣大多數情況下, 這個array都會比實際需要的大以應對動態append.

使用了tuple之後, Python解釋器就不用預留內存空間, 所以內存佔用更少和對象創建速度更快.

list對應在C++的vector中甚至還有方法手動釋放這些預留的內存空間.

完全不是什麼"拍腦袋"的設計.

---

tuple更是一種編碼承諾, 在Python沒有類似C++ const和類型推導情況下的一種較弱的形式化驗證.


就可hash這點,tuple很有必要。

其它看看 Why do we need tuples in Python (or any immutable data type)?


不可變意味著線程安全,直接無鎖多線程,性能提升巨大。而List是更靈活,更多特性,但是犧牲了性能,看場景需要自己取捨了。


immutable這麼重要的屬性豈是說扔就扔了的,這可是推導function purity的必要條件。對於functional programming還是要多學習一個。


覺得這個問題挺好,值得好好思考。

一. Immutable

首先說說Immutable的優勢:

  1. 為什麼FP在多核時代重獲重視?一個很重要的原因就是FP的Immutable特性。Immutable類型不存在Mutable類型的同步問題;
  2. 因為不可變,Immutable類型的內存結構設計就少了很多假設性條件,帶來的直接好處就是性能優化;
  3. Python里只有Immutable類型是Hashable的,因為同樣是Immutable使得Hash Table的設計來得簡單;
  4. 業務上不該改變的就不允許其發生中途變化!

二. Tuple的使用場景

List跟Tuple使用場景上的一點主要區別

看到好多Python程序員都喜歡第一時間就用List,不管合不合適(當然有時候是需要可修改的):

[["張三", 35], ["陳八", 28]]

List存放的數據應該是同質數據;而Tuple呢?其存儲的應該是像資料庫記錄這樣的結構化數據——這個區別是List和Tuple使用上最直白的區別。

所以上述代碼應該改為:

[("張三", 35), ("陳八", 28)]

Tuple是Hashable的

這可以應用在一些有趣的場景,比如把一些「記錄」作為Key:

Out[11]:
[(("張三", 1995), "√ 少林長拳"),
(("張三", 1999), "√ 武當太極"),
(("張三", 2015), "√ 破空神掌"),
(("李七", 2007), "√ 大摔碑手"),
(("李七", 2017), "√ 長空劍法")]

一個武者習武履歷記錄的時間軸就出來了。

此外,其實Python中大量運用Tuple。好比上圖代碼里,在sorted中指定排序順序的欄位。然後再看看person.items(),其結構類似上面的輸出,裡面同樣藏著Tuple結構。

比如還有print

In [15]: print("%s is %s." % ("Foo", "Bar"))
Foo is Bar.

Tuple解構

在上面的print里,其實就是Tuple解構。

比如:

In [28]: name, year = ("張三", 1995)

In [29]: name
Out[29]: "張三"

In [30]: year
Out[30]: 1995

In [31]: for (name, year), gongfu in sorted(person.items(), key=lambda item: (item[0], item[1])):
...: print("{n} -- {y} -- {gf}".format(n=name, y=year, gf=gongfu))
...:
張三 -- 1995 -- √ 少林長拳
張三 -- 1999 -- √ 武當太極
張三 -- 2015 -- √ 破空神掌
李七 -- 2007 -- √ 大摔碑手
李七 -- 2017 -- √ 長空劍法

Tuple解構特性對於函數返回多值是非常有意義的。

三. collections.namedtuple具名元組

附帶提提collections.namedtuple,一個工廠函數,其在官方文檔中的定義是:

factory function for creating tuple subclasses with named fields.
Returns a new tuple subclass named typename. The new subclass is used to create tuple-like objects that have fields accessible by attribute lookup as well as being indexable and iterable...

In [57]: from collections import namedtuple
...:
...: GF = namedtuple("GongFu", "name, gf, pai, props")
...: data = [
...: GF("張三丰", "太極拳", "武當派", ("男", 60)),
...: GF("劉小七", "八卦掌", "八卦門", ("男", 41)),
...: GF("石破天", "無影手", "無影門", ("男", 39))
...: ]
...: for item in data:
...: print(f"{item.name} -- {item.gf} -- {item.pai} -- {item.props[0]} -- {item.props[1]}.")
...:
張三丰 -- 太極拳 -- 武當派 -- 男 -- 60.
劉小七 -- 八卦掌 -- 八卦門 -- 男 -- 41.
石破天 -- 無影手 -- 無影門 -- 男 -- 39.

collections.namedtuple可以帶上名稱屬性,在邏輯及調試上更加清晰。在作為「記錄」使用時,無疑collections.namedtuple更為合適。而且collections.namedtuple的構建在內存上跟Tuple是一樣的,從而也足夠優化。

而且collections.namedtuple也能夠解構:

In [65]: name, gf, pai, (props0, props1) = data[2]

In [66]: name, gf, pai, props0, props1
Out[66]: ("石破天", "無影手", "無影門", "男", 39)

四. 番外篇

Tuple的一個定義:

Tuple其實是在大量編程語言中得以大量使用的。在一些FP語言中Tuple的基礎其實是Pair,比如Idris中,("Baz", "Foo", "Bar", 39)被當成("Baz", ("Foo", ("Bar", 39)))

同步在我的專欄:Python的Tuple是不是冗餘設計? - 知乎專欄


前面大佬說了那麼多,我就簡單幾句吧,性能與內存優化還不夠么?另外從功能上來說,tuple 可以當作一個數據結構,用來存不希望被修改和變動的數據,Emmmm,另外再想一下 namedtuple 這個東西是不是就在功能上也說的通了?


在我看來是.

可以拿 Ruby 對比一下: Ruby 沒有 Tuple 這種類型, 所有 Python Tuple 的應用場景在 Ruby 中都能由 Array 搞定. 而在 Ruby 里, 所有對象都可以當 Hash 的 key! 你可能會問對象內容變化導致哈希變化怎麼辦? 是的 Hash 就查不到了... 但實際上我從來沒碰到過有人會亂改 Hash 的 key 的, 如果真改了就調用一下 rehash 方法好了. 如果是為了穩定哈希而添加 Tuple, 就是個費勁而成效極其微弱的決定.

如果說這是為了性能, 那你經常會讓 List 和 Tuple 相互轉換, 也很廢性能的... 相比之下 Ruby 對小型 Array 有專門的緊湊數據結構, 還有 on-stack array 和專門的 array 指令, 很多情況下都能達到和 Tuple 相近的性能, 而這些優化都隱藏在實現中, 用戶使用不應感覺得到有區別. 其實編譯器做好點, 還能通過編譯優化消除掉一些中間狀態的 Tuple 和 Array, 那就更沒有性能區別了.

我不是說 Ruby 就沒冗餘設計... 它也有: 例如類變數和從 Elisp 繼承下來的 symbol. 類變數完全可以由實例變數代替, symbol 完全可以由 string literal 代替...


補充前面的回答

tuple 作為不可變類型性能上優於list,這只是一方面,更重要的是 tuple 可用於存儲異構(heterogeneous)數據,可作為沒有欄位名的記錄(record)來用,比如用tuple來記錄一個人的身高、體重、年齡。

name, age, height, weight= ("zhangsan", 20, 180, 80)

而list一般用於存儲同構數據(homogenous),同構數據就是具有相同意義的數據,比如下面都是字元串類型,代表用戶的名字

["zhangsan", "Lisi", "wangwu"]

可能有人要跳出來說 list 可以存儲任何類型的數據,但是,你操作資料庫的時候你不會把任何東西都往一個list塞吧。

正確的方式應該是:

[("zhangsan", 20, 180, 80), ("wangwu", 20, 180, 80)]

正因為tuple作為沒有名字的記錄來使用在某些場景有一定的局限性,所以又有了一個namedtuple類型的存在,namedtuple可以指定欄位名了。


不是冗餘的, 並且tuple的數據結構還是python極其重要的一部分.

1. 更省內存

list對象的實現與C++中的verctor類似, 空間動態並且成倍增長. 也就是說, 儘管list對象中只保存了5個元素, 但是python底層其實是為這個list開闢了8個元素的空間, 這樣有3個空間屬於空的, 就造成浪費.

而tuple對象是佔滿申請空間的, 在底層創建tuple時就確定了空間大小:

PyObject *
PyTuple_New(Py_ssize_t size)
{
...
}

並且呢, tuple的操作比list少, 因此又進一步省空間了.

2. 線程安全

正因為tuple是只讀的, 這就使得它天生能避免讀寫衝突和寫寫衝突. 在多線程時候可以大膽放心的使用, python底層中也用了很多tuple.

比如確定類的 __mro__ 屬性.

MRO 全稱 Method Resolution Order,它代表了類繼承的順序。

這個是在類確定的時候就能確定的. 同樣python底層用到的tuple還有函數的參數. 以及函數返回多個值時候的解包

甚至當你用items遍歷dict對象時:

for key, value in d.items():
pass

python底層發生了什麼呢? 來看看:

static PyObject *
dict_items(PyDictObject *mp)
{
....
for (i = 0; i &< n; i++) { item = PyTuple_New(2); PyList_SET_ITEM(v, i, item); } ... }

也就是將dict的每個鍵值對封裝成tuple再給變數key和value. 因為對於可變對象的dict, 它的元素存儲的內存地址是可能因為哈希表的擴充而改變的. 因此這邊tuple的封裝可以保證線程上的安全. python底層中用到tuple的比比皆是.

3.可哈希

可哈希帶來的好處其他答主也都回答得很好, 這邊就不舉例子了.


上面幾位前輩已經了關鍵部分, 我只是做一個簡單的陳述, 再舉一個例子來證明一下.

總結

1) Tuple 跟 List沒有繼承關係

2) Immutable vs mutable

3) hashable vs unhashable

假設我們允許list作為dict的key, 比如 l = [1], d = {l:"1"}, 然後我們修改了l, 閉上眼睛想一想, 是不是世界觀都要崩潰了? 反正程序是要崩的.


#格式化字元串
&>&>&> "numbers are %s,%s,%s,%s,%s" % (1,2,3,4,5)
"numbers are 1,2,3,4,5"
&>&>&> "numbers are %s,%s,%s,%s,%s" % [1,2,3,4,5]
Traceback (most recent call last):
File "&", line 1, in &
TypeError: not enough arguments for format string
&>&>&> "list is %s" % [1,2,3,4,5]
"list is [1, 2, 3, 4, 5]"
&>&>&>

我來說一個,在格式化多個參數的字元串時,必須用tuple來包裝參數,python會把list當成一個參數不會自動unpack。這裡list無法替代tuple來進行格式化。


絕對不是,首先我想說在編程領域一切方案的提出都是為了解決問題的。

1.不談應用環境單談設計就是在耍牛氓。

2.不談問題單談解決方案也是在耍牛氓。

@林誠 的答案基本認同,弱類型驗證這個觀點也概括得非常不錯。

但貌似沒人從應用環境開始說的(說線程安全的不算啊)

1.由於Python是腳本這個特殊實例,導致.py文件完全可以當配置文件用,而通常容器長度並不會在運行時改變。

2.確定長度,能更好定義數據結構(或者說是當宏用),例如:

red=(xx, xx, xx)

yellow=(xx, xx, xx)

blue=(xx, xx, xx)

這是在pygame中一段定義顏色的源代碼。說明了這兩個作用。

而這些數組長度和元素值不變,這種情況下使用list就太浪費了,殺雞用牛刀(強迫症絕逼不能忍),這時候tuple就夠用了。

總結

由於tuple和list實現原理上的差別(具體可以參照Python源碼,當然前提是你要懂c或c++)tuple運行時的時間效率和空間效率完虐list,這是用實例不可變性來換時間和空間。所以它絕對有存在的價值。

最後說一句:在編程領域中,一個好的設計所需要的就是權衡利弊,在各項條件和限制中尋找平衡點。或者說,確定最適用的應用環境,為更好適配(支持)該環境而權衡利弊。所以不合理的設計是不存在的,因為一個沒有核心競爭力的設計遲早會被取代的。perl(是這個吧)就是活生生的例子,當它內置支持正規式的時候,大量程序員使用,而當其他語言以各種各樣的形式(庫,引擎等等)引入正規式的時候這個語言的核心競爭力就沒有了,這兩年排名也就逐漸下降了。

//第一次打這麼多字,手機碼字不容易,請大家多支持(逃


推薦閱讀:

C++中int A::*a里的指針a是什麼?
C/C++ 里指針聲明為什麼通常不寫成 int* ptr 而通常寫成 int *ptr ?
c語言b++<15是b和15比,還是b+1和15比?

TAG:Python | 數據結構 | CC | 編程語言理論 |