題一:1+2+3+4+5+…+10000=?
第一種解法:
1+2=3,3+3=6,6+4=10,10+5=15…
這是要算到猴年馬月的節奏呀 果斷棄之
第二種解法:
聰明的高斯,這樣玩:
(1+10000)×10000÷2=50005000 (1+10000)\times10000\div2=50005000(1+10000)×10000÷2=50005000
在這一問題的解答上,高斯的方法真香!
?問題二:冬天里,你和小紅玩猜數字,小紅隨便想一個1~100的數字。你的目標是以最少的次數猜到這個數字。你每次猜測后,小紅會說小了、大了或對了。在10次內猜對了,小紅就答應今晚用你暖床。是不是聽著很刺激
第一種解法:
你從1開始依次往上猜,猜測過程會是這樣,小紅皺著眉不斷說小了
?
?
?
這是簡單查找,更準確的說法是傻找 。每次猜測都只能排除一個數字。如果小紅想的數字是99,你得猜99次才能猜到!,注定今晚你和小紅無緣了
第二種解法:
你從50開始,小紅笑著說小了,你接著說75…
?
?你每次猜測的是中間的數字,從而每次都將余下的數字排除一半。接下來,你猜63(50和75中間的數字)…這就是二分查找,每次猜測排除的數字個數如下:
?不管小紅心里想的是哪個數字,你在7次之內都能猜到,因為每次猜測都將排除很多數字!恭喜你,小紅今晚是你的人了。
解析:對于包含n 個元素的有序列表,用二分查找最多需要log2n log_2 nlog2n步1,而簡單查找最多需要n 步。
二分查找的python實現
def binary_search(list1, item):
low=0 # low和high用于跟蹤要在其中查找的列表部分
high=len(list1) - 1
while low <=high:
mid=(low + high) // 2
guess=list1[mid]
if guess==item: # 猜對了
return mid # 返回位置
elif guess > item: # 猜大了
high=mid - 1
else: # 猜小了
low=mid + 1
return None # 沒有找到
my_list=[i for i in range(1,100)] # 列表推導式生成0-100個數
print(binary_search(my_list, 3)) # 別忘了索引從0開始
回到前面的二分查找。使用它可節省多少時間呢?簡單查找逐個地檢查數字,如果列表包含100個數字,最多需要猜100次。如果列表包含40億個數字,最多需要猜40億次。換言之,最多需要猜測的次數與列表長度相同,這被稱為線性時間(linear time)。
二分查找則不同。如果列表包含100個元素,最多要猜7次;如果列表包含40億個數字,最多需猜32次。厲害吧?二分查找的運行時間為對數時間 (或log loglog時間)。
算法
在計算機領域,我們同樣會遇到各種高效和拙劣的算法。衡量算法好壞的重要標準有兩個。通常指最糟的情形下的:
時間復雜度(采用大O表示法)
空間復雜度(采用大O表示法)
理解:小明和小剛都是初入IT的新人,一天老板給他們布置了同一個需求讓他們用代碼實現出來,小明的代碼運行一次要花100ms,占用內存5MB。小剛的代碼運行一次要花100s,占用內存500MB。于是……小剛第二天就從公司消失了
?你經常會遇到的5種大O運行時間:
O (logn log nlogn),也叫對數時間,這樣的算法包括二分查找
O (n),也叫線性時間,這樣的算法包括簡單查找
O (n?logn n * log nn?logn),這樣的算法包括快速排序——一種速度較快的排序算法。
O (n2 n ^2n2),這樣的算法包括選擇排序——一種速度較慢的排序算法。
O (n! n !n!),這樣的算法包括旅行商問題的解決方案——一種非常慢的算法.
假設你要繪制一個包含16格的網格,且有5種不同的算法可供選擇:
?時間復雜度的概念明白了,那空間復雜度呢:
問題三:給出下圖所示的n個整數,其中有兩個整數是重復的,要求找出這兩個重復的整數。
?
第一種解法:
你采用雙重循環,遍歷整個數列,每遍歷到一個新的整數就開始回顧之前遍歷過的所有整數,看看這些整數里有沒有與之數值相同的,第1步,遍歷整數3,前面沒有數字,所以無須回顧比較。第2步,遍歷整數1,回顧前面的數字3,沒有發現重復數字。后續步驟類似,一直遍歷到最后的整數2,發現和前面的整數2重復。
?這個算法的時間復雜度是O(n2) O(n^2)O(n2)
第二種解法:
當你遍歷整個數列時,每遍歷一個整數,就把該整數存儲起來,就像放到字典中
一樣。當遍歷下一個整數時,不必再慢慢向前回溯比較,而直接去“字典”中查找,看看有沒有對應的整數即可。“字典”左側的Key代表整數的值,“字典”右側的Value代表該整數出現的次數(也可以只記錄Key)。當遍歷到最后一個整數2時,從“字典”中可以輕松找到2曾經出現過,問題也就迎刃而解了。
?讀寫“字典”本身的時間復雜度是O(1) O(1)O(1),所以整個算法的時間復雜度是O(n) O(n)O(n)
第二種解法采用的這個所謂的“字典”,是一種特殊的數據結構,叫作散列表。這個數據結構需要開辟一定的內存空間來存儲有用的數據信息。
計算機的內存空間是有限的,在時間復雜度相同的情況下,算法占用的內存空間自然是越小越好。如何描述一個算法占用的內存空間的大小呢?這就用到了算法的另一個重要指標——空間復雜度(space complexity)。
常量空間,當算法的存儲空間大小固定,和輸入規模沒有直接的關系時,空間復雜度記作O(1) O(1)O(1)
線性空間,當算法分配的空間是一個線性的集合(如數組),并且集合大小和輸入規模n成正比時,空間復雜度記作O(n) O(n)O(n)
二維空間,當算法分配的空間是一個二維數組集合,并且集合的長度和寬度都與輸入規模n成正比時,空間復雜度記作O(n2) O(n^2)O(n2)
遞歸空間,遞歸是一個比較特殊的場景。雖然遞歸代碼中并沒有顯式地聲明變量或集合,但是計算機在執行程序時,會專門分配一塊內存,用來存儲“方法調用棧”。執行遞歸操作所需要的內存空間和遞歸的深度成正比。純粹的遞歸操作的空間復雜度也是線性的,如果遞歸的深度是n,那么空間復雜度就是O(n) O(n)O(n)。
時間與空間的取舍:
人們之所以花大力氣去評估算法的時間復雜度和空間復雜度,其根本原因是計算機的運算速度和空間資源是有限的。雖然目前計算機的CPU處理速度不斷飆升,內存和硬盤空間也越來越大,但是面對龐大而復雜的數據和業務,我們仍然要精打細算,選擇最有效的利用方式。
在尋找重復整數的問題三中,雙重循環的時間復雜度是O(n2) O(n^2)O(n2),空間復雜度是O(1) O(1)O(1),這屬于犧牲時間來換取空間的情況。相反,字典法的空間復雜度是O(n) O(n)O(n),時間復雜度是O(n) O(n)O(n),這屬于犧牲空間來換取時間的情況。在絕大多數時候,時間復雜度更為重要一些,我們寧可多分配一些內存空間,也要提升程序的執行速度。
數據結構
算法的概念理解清楚了,下面就是數據結構。如果把算法比喻成美麗靈動的舞者,那么數據結構就是舞者腳下廣闊而堅實的舞臺。數據結構,對應的英文單詞是data structure,是數據的組織、管理和存儲格式,其使用目的是為了高效地訪問和修改數據。
常見的數據結構:
線性結構,線性結構是最簡單的數據結構,包括數組、鏈表,以及由它們衍生出來的棧、隊列、哈希表。
樹是相對復雜的數據結構,其中比較有代表性的是二叉樹,由它又衍生出了二叉堆之類的數據結構。
?圖是更為復雜的數據結構,因為在圖中會呈現出多對多的關聯關系。
山東掌趣網絡科技有限公司
021年1月3日,比特幣的價格又創下歷史新高,超過33000美元了。
但是,凡事皆有始末。天下,沒有不散的宴席。比特幣的“盛宴”會在何時結束呢?
如果,人類還有未來,我們的后代,是否會在2140年,也就是全部2100萬比特幣發行完畢后,再次進入比特幣的“原始社會”,跟中本聰一樣,是用家用電腦、筆記本電腦來挖礦的。唯一不同的是,中本聰挖礦的獎勵是50個比特幣,而他們得到的獎勵是“0”,加上不確定的交易費。噢,那時應該不能叫挖礦,只能叫記賬了。
也許,這一切,無需等到2140年,因為每隔四年一次的區塊獎勵減半,對于比特幣來說都是一次“嚴重的”冒險。挺不過去,也就完了。
雖然,這一切,還都是假設。因為,這個問題的出現,還有時日,可能是幾年、幾十年,也可能是100年之后,但是對這個問題的思考和回答,將會有助于我們看清比特幣的未來。
不過,當下卻是另一種光景。談這個問題似乎有點不合時宜,有點煞風景,簡直就是杞人憂天。因為,比特幣的“減半漲價”效應, 還在繼續發揮作用,正有不少公司準備大展拳腳。
2020年12月21日,納斯達克上市公司RIOT宣布,他們將花費3500萬美元采購15000臺螞蟻礦機,用于比特幣挖礦。 原因也很簡單,因為,比特幣的價格正在持續攀升。不要嫌我嘮叨,記住,現在不宜入場。具體的,你可以看一下這個《小心!別上當!比特幣從來沒有牛市!》。
2020年12月21日RIOT宣布購買15000臺螞蟻礦機
RIOT公司新購買的礦機,預計從2021年5月開始部署,到10月部署完成。此后,RIOT的挖礦算力,將達到3.8Eh/s,占總算力140Eh/s的2.17%。如果,你對算力這些基本的概念還不清楚,需要抓緊時間補課了。請先看一下《即使美國也“殺不死”比特幣? 》。
不要以為,3.8Eh/s只是枯燥的數字,它的背后就是錢。簡單算筆賬,你就知道了。比特幣的區塊是每10分鐘出一個,每個獎勵6.25比特幣,按照現在2.3萬美元一個計算,每天產生的區塊獎勵是:
2070萬美元=24 X 60 ÷ 10 X 6.25 X 2.3萬美元。
在這里,我們還沒有算比特幣的交易費。RIOT公司可以獲得2.17%的份額,也就是44.919萬美元。你可能會有疑問了,這么確定?是的,很確定,算力的占比就是收入的占比,因為挖礦是當前地球上最公平的比賽之一,這是由算法保證的。他們投資的3500萬美元,只需要78天就收回了。當然還只是估算,里面還有其他各種費用需要計算和扣除。
看到上面簡單的測算,你是不是也蠢蠢欲動了,你是不是也想去挖礦了。
且慢,礦機沒有了。你現在知道中國什么公司最厲害嗎?比特大陸,就是生產螞蟻礦機的比特大陸,RIOT想買的他們的S19 pro礦機 。已經早就賣斷貨了,現在定,最早要到2021年8月1日才能交貨。怎么樣?你是不是也覺得礦機比茅臺強多了,也高級多了。
螞蟻礦機S19 Pro2021年8月1日才能交貨
Tips:S19 Pro贏利能力 下圖來自一個專門幫你測算礦機贏利能力的網站 ,按照截圖時的比特幣價格27000多美元,電費按照每度0.12美元計算,平均每個月可以贏利247美元。
比特幣挖礦算力贏利能力測算
如果你以為,RIOT投資完了、安裝完了,錢就滾滾而來了?錯。他們將不得不面對新一輪的算力軍備競賽。因為,挖礦產生的收入是擺在那里的,所有人都知道。RIOT并不比別人高明,他們想到加大礦機投入,別人自然也會。你之所以不知道,是有的人只做不說,RIOT不同,是上市公司,需要以此提升股價,還沒有做就先說了。果不其然,消息公布后的第二天,RIOT的股價,就從11.04漲到了14.65美元,漲幅高達32%。
RIOT購買大量礦機消息發布后股票狂漲32%
但是,RIOT公司的這筆投資,是絕對不可能在78天內收回,具體何時能收回,要看到時的全網算力占比,是不是還能保持在2.17%。電力當然也是一筆不小的開支。不過,至少目前為止,在下圖的礦池算力的前17名里面 ,我們沒有看到RIOT的身影。2021年10月份,RIOT就會出現在下面這張圖里嗎?不一定。
2020年12月23日礦池算力份額
Tips:全網算力如何計算? Blockchain.com 網站提供了一個計算公式: H=2^{32}\times D\div T , 其中,D代表的是當前挖礦的難度。每挖出2016個區塊,也就是大概2周的時間,難度會調整一次;T代表的是挖出的兩個塊之間的平均時間。一般統計的周期是24小時,下面這張圖來自 fork.lol ,平均12小時的,我一般看這個。
比特幣平均12小時全網算力
算力,是連接現實世界與比特幣世界的一個真實存在。它的增加與減少,也與我們這2個世界密切相關。與4年一次的區塊獎勵減半不一樣,有些變化會隨時發生,它們對算力的影響,也可能是致命的。自然災害,就是其中之一。
2020年8月18日,四川洪災導致中國礦主算力損失20%
2020年8月18日Poolin發布的推特截圖
上圖是一個叫Poolin的人,發布的推特截圖。視頻的內容是,位于四川境內的“礦場”被泥石流摧毀,他正在組織工人搶救礦機的視頻。因為四川洪災,2020年8月18日,當天比特幣全網算力出現了大幅下降,有報道認為中國礦主的算力損失有20% 。
2020年8月18日四川泥石流導致算力下降
下圖,是比特幣的算力增長的歷史變化圖。2018年8月27日的算力增長率達到了858%,為近期最高,當前的增長率是38%。 顯然,比特幣的算力增長正在減緩。
比特幣算力增長變化
但是,“同袍兄弟”BCH的算力,已經開始負增長了。歷史上,BCH曾經多次出現負增長。2020年10月份以來,BCH又再次出現了負增長,達到了-34%。
BCH算力開始出現負增長
BCH的現在,可能就是比特幣的未來。之所以這樣說,是因為它們采用的挖礦方式和算法都一樣,最大的不同是價格。還有,那些BCH消失的算力,只是轉移了,很可能是去挖比特幣了。因為,現在比特幣更有利可圖。2020年12月28日,比特幣的價格是26504.67美元,BCH是323.02美元,比特幣的價格是BCH的82倍。下面這張圖,也是來自fork.lol,3個小時之內的比特幣與BCH之間的算力對比圖。很顯然,當前比特幣占有絕對的優勢,擁有的算力超過了98%。
比特幣與BCH全網算力對比圖
算力的增加或減少,其背后實際上是“赤裸裸”的利益,礦工們不是中本聰,講情懷是要餓死人的。利益,才是算力的保障。
沒有了利益,也就沒有了算力。
比特幣,擁有全球頂級算力。即使全球最快的前100名超級計算機加起來的算力,也只不過是它的十萬分之一。
然而,天下,沒有免費的午餐。比特幣付出的代價,是鑄幣稅,全部的鑄幣稅。礦工們,獲得了100%的鑄幣稅。除此之外,沒有人可以享受比特幣的鑄幣稅,美國政府也不行。
Tips:鑄幣稅 不要被“鑄幣稅”這個名字誤導,鑄幣稅不是稅,而是一種收入。之所以叫“鑄幣稅”,可能是因為:這種收入,只要干,就會有,很穩定,就像稅收一樣穩定。 鑄幣稅的英文是Seigniorage,來自法語。 它是指貨幣發行過程中的面值和成本之間的差額。簡單說,100美元,如果發行成本是2元,98元就是鑄幣稅,這個收入歸美聯儲,最終也就是屬于美國政府。
比特幣的鑄幣稅,就是比特幣的挖礦成本與挖礦收入之間的差額。其全部收入,都歸挖礦的礦工所有,與政府獲得鑄幣稅不同的是,礦工需要負責記賬,當然記賬也不是免費的,還有交易費。
如果,比特幣的鑄幣稅,也能像主權國家發行貨幣那樣,一直有,也不會有問題,也會有越來越多的礦工來挖礦。但是,中本聰,不可能創造一個自己反對的、只是虛擬化了的舊的金融體系。所以,他一開始就規定了發行貨幣的總量是2100萬個。這也就間接的規定了,鑄幣稅的總額。
你需要知道的是,2100萬個比特幣的發行,是通過每一個區塊獎勵實現的。開始的時候,擔心沒有人愿意挖礦,中本聰就把獎勵定得很高,成功挖出一個區塊可以獎勵50個比特幣,然后逐步的減半。關于挖礦,如果你還不了解,需要去看一下《比特幣,真的值得信任嗎?》。
比特幣核心的程序里規定,每挖出21萬個區塊,比特幣的獎勵就減半,已經減半3次了。從50到25,再到12.5,現在的獎勵是6.25個比特幣,以后會越來越少。下面這張圖,告訴你只剩下11%多一點的比特幣還沒有挖出來。在這個網站上,你可以實時看到,比特幣還剩多少。
截止到2020年12月29日已經挖出的比特幣是88.495%
下面這張圖,是比特幣的發行圖, 如果你是我們專欄的老讀者,應該不會陌生,在《比特幣10年增長560萬倍,憑什么?》已經出現過。通過這張圖,你可以了解到,2024年,第四次區塊獎勵減半開始前,93.75%的比特幣就已經挖出來了,余下的比特幣只有7%不到了。
比特幣發行表
現在,每天會挖出900個比特幣左右。這個,你自己也是可以算出來的,每10分鐘一個區塊,每個區塊獎勵6.25個,每天有24小時。
每天挖出的比特幣=6.25 X 24 X 60 / 10=900
你再看一下上面那個表,2064年的時候,一年挖出的比特幣只有640個多一點,還不如現在一天的量。我擔心,比特幣撐不到那一天。
如果,現有的挖礦規則不改,礦工們的退出那是一定的。現在BCH出現的算力負增長,也同樣不可避免地,會出現在比特幣身上。最危險的時間段,就是剛剛減半后。如果比特幣的價格不能漲到以前2倍以上,很多的礦工將不得不離開,因為有的一開機就會虧損。
算力下降之后,可怕的事情將會接踵而至。
51%攻擊,就是有人匯集了超過全網51%的算力,去做壞事。這件事情,以前,之所以沒有發生,是因為,以前挖礦還有利可圖。當然,因為有利可圖,所以比特幣的全網算力是最強大的,要想進行51%的攻擊很難。
但是,當比特幣的算力,不再是全球第一的時候,比特幣網絡,也就不像現在這樣強壯了。再加上,挖礦已經入不敷出的時候,攻擊,也就成為了一種可能的選項。海盜,不都是這么來的嗎?而且,他們干的可能要比海盜文明很多,只是將自己用過的比特幣再拿出來用一下而已。這就是“雙花”。
舉個例子,2064年,我剛剛花了50個比特幣買了所有我想買的,交易也被記錄進比特幣區塊鏈了。比如說,記錄我的這筆交易的區塊高度是654321。那時,我缺錢花,但是有算力。但是,挖礦已經養不活自己了。為了養家糊口,我就找了10個哥們兒,說咱們干一票,把那筆50個比特幣再花一次,得手后均分。
于是,我們把654320區塊,也就是記錄我那筆50個比特幣交易的前面的那個區塊作為上一個區塊。大家一起加油干——挖礦,最終產生了有一個新的654321區塊,并最終使得新的654321所在的鏈成為了最長的鏈。在這個鏈上,我的50個比特幣還是我的。于是,我給參與51%攻擊的每個哥們5個比特幣。就這樣,一次雙花就完成了。
Tips:一次成功的“雙花” 現實世界,比特幣曾經出現過一次成功的雙花案例,時間是2013年3月12日。不過,不是51%攻擊攻擊造成的,而是比特幣核心(bitcoin core)軟件錯誤升級到0.8版本,需要緊急降級處理造成的。下圖是事后,當事人macbook-air在比特幣論壇(bitcoin.org)上的留言截圖。 感興趣的,可以去圍觀一下。
2013年bitcoin core軟件升級錯誤導致的一次成功的雙花
這個世界,遠比你我想象的險惡,像macbook-air這樣的好人可遇而不可求。51%的算力攻擊一直沒有停過,只是還沒有發生在比特幣身上。
2020年1月,Savva Shanaev, Arina Shuraeva, Mikhail Vasenin 和 Maksim Kuznetsov四人聯名在《另類投資雜志》(Journal of Alternative Investments)發表論文——《加密貨幣的價值和51%攻擊》(Cryptocurrency Value and 51% Attacks: Evidence from Event Studies),專門就51%攻擊進行了實證研究。
《加密貨幣的價值和51%攻擊》論文網頁截圖
他們,一共研究了15起已經確認并且成功的51%算力攻擊,發現:每一次51%算力攻擊都會造成平均12.6%的價格下跌,而且在攻擊前都存在內幕交易和拉高出貨(pump-and-dump)的陰謀。
被攻擊的加密貨幣有:Bitcoin Gold、Bitcoin Private、CoiledCoin、Electroneum、Feathercoin、Karbo、Krypton、Litecoin Cash、MonaCoin、Pigeoncoin、 Shift、Terracoin、Verge和ZenCash (最近改名為Horizen)。這些加密貨幣,有的你可能都沒有聽說過,我也沒有聽說過。例如CoiledCoin,它已經不存在了。
論文還確認,沒有發現對比特幣進行的51%攻擊。不過,另一種攻擊倒是被發現了:比特幣礦池之間的DDos攻擊。攻擊的對象不是比特幣區塊鏈,而是競爭對手的礦池。目的也很簡單,就是想在挖礦的競爭中贏得優勢,讓自己的礦池獲得更多機會,贏得更多比特幣。就像不道德的馬拉松選手為了獲得金牌,給對手使絆子一樣。對此,有人從博弈論的角度進行了專門的研究 ,改天我們再談。
也許,比特幣也有會被51%算力攻擊的那一天。也許是2064年,也許會更早。
如果,真有這么一天的話。那時,比特幣已經離開了舞臺中央,淪落為另一個不知名的加密貨幣了。再假如,比特幣,還真的走到了2140年,那時的比特幣又會怎么樣呢?
如果,比特幣真的活到了2140年。那時,挖礦不再有比特幣獎勵,不得不依靠交易費,當然,也只有交易費可以依靠了。其后果,將會很嚴重:區塊獎勵偷竊將不可避免。
Our key insight is that with only transaction fees, the variance of the block reward is very high due to the exponentially distributed block arrival time, and it becomes attractive to fork a "wealthy" block to "steal" the rewards therein.
上面的結論來自2016年的一篇學術論文——《沒有區塊獎勵的比特幣不穩定》(On the Instability of Bitcoin Without the Block Reward)。四位研究者,是經過理論分析,并用比特幣的采礦模擬器驗證后,得出上述結論的。 該論文的引用次數已經達到了238次,有相當高的說服力。
論文《沒有區塊獎勵的比特幣不穩定》網頁截圖
那么,比特幣的未來,在哪里呢?
創新,才能讓比特幣走得更遠。方式有兩種,一是“改革”,二是“開放”。這兩個詞,上了年紀的中國人都耳熟能詳,借用一下,以表達我對80年代的敬意。
改革,就是要根據“區塊獎勵少,挖礦吸引力下降”的實際情況,改變其底層的挖礦記賬規則——工作量證明(PoW)機制,采取新的記賬權確立方式。
中本聰真的聰明,他通過獎勵遞減的方式,啟動了比特幣的挖礦。不過,每4年減半,很難說是最優的策略。但是,我們也不能委曲求全。中本聰,能夠啟動比特幣,而且一直能走到現在,已經很不容易了。余下的,我們應該自己去想辦法解決。
開放,繼續采用挖礦的思路,但是讓礦池的收入來源能夠得到擴展。讓礦工在區塊獎勵、交易費之外,還能有新的收入來源。這一點比較難,但也可能是最終的解決方案。好在是,現實生活中,這樣的成功案例有很多,移動互聯網就是這樣一路走過來的。
手機,以前是只能打電話的。后來可以發短信了,再后來可以上網了,可以視頻聊天了。手機運營商,作為網絡的維護者,好比礦池,當初是只能按照時間收取電話費的,后來可以按照條數收取短信費,現在可以按照流量收取上網費了。區塊獎勵和交易費,就好比是手機通話費。下圖是從1983年到現在的標志性手機, 我用過其中的8款,你用過其中的哪幾款?
手機的進化
什么是礦工們的“短信費”、“流量費”呢?
現在,還沒有清晰的答案。只有不斷的創新,才有可能找到答案。個人覺得,也可能是因為當下比特幣挖礦獎勵的價值太大了,遏制了人們的創新動力。好比,移動通訊初期,手機話費已經足夠讓運營商活得很好了,所以不思進取。
不過,再看看電信運營商的發展軌跡,你就會發現。創新,都是被逼出來的。在手機電話費就能帶來足夠的利潤的時候,沒有人愿意去主動創新的。所以,淘寶、微信,不可能是運營商的產品。當然,基于比特幣區塊鏈的,像“淘寶”“微信”這樣的超級應用還沒有出現,這是另外一番天地,有空再細說。
是創新,讓我們有了比特幣,同樣也只有創新才會讓比特幣走得更遠。
不管是改革,還是開放,都需要時間。幸虧,這個世界有“林迪效應”。
林迪效應,是說:“如果一個東西已經存在了很長時間,它不會像一個人那樣,余下的時間越來越少,相反,它還會繼續存在很長時間。”
This, simply, as a rule, tells you why things that have been around for a long time are not "aging" like persons, but "aging" in reverse.
納西姆·尼可拉斯·塔勒布(Nassim Nicholas Taleb)
林迪效應,是2012年,塔勒布在《反脆弱》的書中提出的概念 。他說,如果一本書已經存在40年了,那么這本書還可以再活40年,而且,接下來再活10年,又會讓它再活50年。
這個效應,是可以被我們的生活所檢驗的。例如:你喝水的杯子,與你的手機相比,已經存在很長時間了,而且我們相信,杯子還會繼續存在更長的時間,至少比手機的時間要長很多。
如果,林迪效應是真的話,那么,比特幣至少還可以再活11年。在這一點上,我選擇相信塔勒布。
問題是,由于區塊獎勵減半的存在,每隔4年,我們所面對的都是一個新的比特幣,也就是說“杯子”已經不再是原來的杯子了。看看下面這張比特幣的發行表 ,你就知道了,挖礦的吸引力正在逐步下降。
Tips:比特幣發行時間表 這張表,很重要,在本文中已經第二次出現了,我建議你把這個表打印出來,張貼在醒目的位置,時刻提醒自己,每隔4年,比特幣都會有一次蛻變,結果如何,存在很大的不確定性。
比特幣發行
我大膽預測一下,至少在2032年,區塊獎勵第6次減半后,比特幣挖礦,還不會遇到大問題。雖然,那時的比特幣獎勵已經很少,只有0.78125個比特幣了,但是,比特幣價格的上漲還是可期的。如果,“減半漲價”效應繼續發揮作用 ,比特幣對于礦工們還是有吸引力的。
問題是,只有時間還是不夠的,還需要人。
改革也好,開放也好,都是會有一個總設計師的。但是,去中心化的比特幣,是沒有總設計師的。否則,也不會中途出現分裂,有現在的BCH了。當然,從進化的角度來看,出現BCH也未必是壞事。
不管怎么說,比特幣都是一個神奇的存在。原本是一篇論文,一個實驗,居然就走到了現在。
我們應該清楚,中本聰,只是想好了開頭,如何走下去還需要不斷探索。
在后面的內容中,你將會了解到,比特幣社區有一群智商極高的人,他們不但有自由主義的理想,還有強大的能力。
他們,應該可以讓比特幣“盛宴”不再只是盛宴,而是不斷創新、不斷進化的“生態”。
(待續,這是專題“比特幣還能走多遠”的第十一篇)
本文首發于“BTC深入淺出”(ID:xinshd30)微信公眾號,每周一篇原創,更新你對比特幣的認知。
ython 和放大鏡的二進制代碼
簡介
Python 是一種高層次的結合了解釋性、編譯性、互動性和面向對象的腳本語言。Python 由 Guido van Rossum 于 1989 年底在荷蘭國家數學和計算機科學研究所發明,第一個公開發行版發行于 1991 年。
特點
基礎語法
運行 Python
交互式解釋器
在命令行窗口執行python后,進入 Python 的交互式解釋器。exit() 或 Ctrl + D 組合鍵退出交互式解釋器。
命令行腳本
在命令行窗口執行python script-file.py,以執行 Python 腳本文件。
指定解釋器
如果在 Python 腳本文件首行輸入#!/usr/bin/env python,那么可以在命令行窗口中執行/path/to/script-file.py以執行該腳本文件。
注:該方法不支持 Windows 環境。
編碼
默認情況下,3.x 源碼文件都是 UTF-8 編碼,字符串都是 Unicode 字符。也可以手動指定文件編碼:
# -*- coding: utf-8 -*-
或者
# encoding: utf-8
注意: 該行標注必須位于文件第一行
標識符
注:從 3.x 開始,非 ASCII 標識符也是允許的,但不建議。
保留字
保留字即關鍵字,我們不能把它們用作任何標識符名稱。Python 的標準庫提供了一個 keyword 模塊,可以輸出當前版本的所有關鍵字:
>>> import keyword
>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
注釋
單行注釋采用#,多行注釋采用'''或"""。
# 這是單行注釋
'''
這是多行注釋
這是多行注釋
'''
"""
這也是多行注釋
這也是多行注釋
"""
行與縮進
Python 最具特色的就是使用縮進來表示代碼塊,不需要使用大括號 {}。 縮進的空格數是可變的,但是同一個代碼塊的語句必須包含相同的縮進空格數。縮進不一致,會導致運行錯誤。
多行語句
Python 通常是一行寫完一條語句,但如果語句很長,我們可以使用反斜杠來實現多行語句。
total=item_one +
item_two +
item_three
在 [], {}, 或 () 中的多行語句,不需要使用反斜杠。
空行
函數之間或類的方法之間用空行分隔,表示一段新的代碼的開始。類和函數入口之間也用一行空行分隔,以突出函數入口的開始。
空行與代碼縮進不同,空行并不是 Python 語法的一部分。書寫時不插入空行,Python 解釋器運行也不會出錯。但是空行的作用在于分隔兩段不同功能或含義的代碼,便于日后代碼的維護或重構。
記住:空行也是程序代碼的一部分。
等待用戶輸入
input函數可以實現等待并接收命令行中的用戶輸入。
content=input("請輸入點東西并按 Enter 鍵")
print(content)
同一行寫多條語句
Python 可以在同一行中使用多條語句,語句之間使用分號;分割。
import sys; x='hello world'; sys.stdout.write(x + '')
多個語句構成代碼組
縮進相同的一組語句構成一個代碼塊,我們稱之代碼組。
像if、while、def和class這樣的復合語句,首行以關鍵字開始,以冒號:結束,該行之后的一行或多行代碼構成代碼組。
我們將首行及后面的代碼組稱為一個子句(clause)。
print 輸出
print 默認輸出是換行的,如果要實現不換行需要在變量末尾加上end=""或別的非換行符字符串:
print('123') # 默認換行
print('123', end="") # 不換行
import 與 from…import
在 Python 用 import 或者 from...import 來導入相應的模塊。
將整個模塊導入,格式為:import module_name
從某個模塊中導入某個函數,格式為:from module_name import func1
從某個模塊中導入多個函數,格式為:from module_name import func1, func2, func3
將某個模塊中的全部函數導入,格式為:from module_name import *
運算符
算術運算符
運算符描述+加-減*乘/除%取模**冪//取整除
比較運算符
運算符描述==等于!=不等于>大于<小于>=大于等于<=小于等于
賦值運算符
運算符描述=簡單的賦值運算符+=加法賦值運算符-=減法賦值運算符*=乘法賦值運算符/=除法賦值運算符%=取模賦值運算符**=冪賦值運算符//=取整除賦值運算符
位運算符
邏輯運算符
成員運算符
身份運算符
運算符優先級
具有相同優先級的運算符將從左至右的方式依次進行。用小括號()可以改變運算順序。
變量
變量在使用前必須先”定義”(即賦予變量一個值),否則會報錯:
>>> name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'name' is not defined
數據類型
布爾(bool)
只有 True 和 False 兩個值,表示真或假。
數字(number)
整型(int)
整數值,可正數亦可復數,無小數。 3.x 整型是沒有限制大小的,可以當作 Long 類型使用,所以 3.x 沒有 2.x 的 Long 類型。
浮點型(float)
浮點型由整數部分與小數部分組成,浮點型也可以使用科學計數法表示(2.5e2=2.5 x 10^2=250)
復數(complex)
復數由實數部分和虛數部分構成,可以用a + bj,或者complex(a,b)表示,復數的實部 a 和虛部 b 都是浮點型。
數字運算
數學函數
注:以下函數的使用,需先導入 math 包。
隨機數函數
注:以下函數的使用,需先導入 random 包。
三角函數
注:以下函數的使用,需先導入 math 包。
數學常量
字符串(string)
轉義字符
字符串運算符
字符串格式化
在 Python 中,字符串格式化不是 sprintf 函數,而是用 % 符號。例如:
print("我叫%s, 今年 %d 歲!" % ('小明', 10))
// 輸出:
我叫小明, 今年 10 歲!
格式化符號:
輔助指令:
Python 2.6 開始,新增了一種格式化字符串的函數 str.format(),它增強了字符串格式化的功能。
多行字符串
實例:
string='''
print(math.fabs(-10))
print(random.choice(li))
'''
print(string)
輸出:
print( math.fabs(-10))
print(
random.choice(li))
Unicode
在 2.x 中,普通字符串是以 8 位 ASCII 碼進行存儲的,而 Unicode 字符串則存儲為 16 位 Unicode 字符串,這樣能夠表示更多的字符集。使用的語法是在字符串前面加上前綴 u。
在 3.x 中,所有的字符串都是 Unicode 字符串。
字符串函數
字節(bytes)
在 3.x 中,字符串和二進制數據完全區分開。文本總是 Unicode,由 str 類型表示,二進制數據則由 bytes 類型表示。Python 3 不會以任意隱式的方式混用 str 和 bytes,你不能拼接字符串和字節流,也無法在字節流里搜索字符串(反之亦然),也不能將字符串傳入參數為字節流的函數(反之亦然)。
bytes 轉 str:
b'abc'.decode()
str(b'abc')
str(b'abc', encoding='utf-8')
str 轉 bytes:
'中國'.encode()
bytes('中國', encoding='utf-8')
列表(list)
創建列表
hello=(1, 2, 3)
li=[1, "2", [3, 'a'], (1, 3), hello]
訪問元素
li=[1, "2", [3, 'a'], (1, 3)]
print(li[3]) # (1, 3)
print(li[-2]) # [3, 'a']
切片訪問
格式: list_name[begin:end:step] begin 表示起始位置(默認為0),end 表示結束位置(默認為最后一個元素),step 表示步長(默認為1)
hello=(1, 2, 3)
li=[1, "2", [3, 'a'], (1, 3), hello]
print(li) # [1, '2', [3, 'a'], (1, 3), (1, 2, 3)]
print(li[1:2]) # ['2']
print(li[:2]) # [1, '2']
print(li[:]) # [1, '2', [3, 'a'], (1, 3), (1, 2, 3)]
print(li[2:]) # [[3, 'a'], (1, 3), (1, 2, 3)]
print(li[1:-1:2]) # ['2', (1, 3)]
訪問內嵌 list 的元素:
li=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ['a', 'b', 'c']]
print(li[1:-1:2][1:3]) # (3, 5)
print(li[-1][1:3]) # ['b', 'c']
print(li[-1][1]) # b
修改列表
通過使用方括號,可以非常靈活的對列表的元素進行修改、替換、刪除等操作。
li=[0, 1, 2, 3, 4, 5]
li[len(li) - 2]=22 # 修改 [0, 1, 2, 3, 22, 5]
li[3]=33 # 修改 [0, 1, 2, 33, 4, 5]
li[1:-1]=[9, 9] # 替換 [0, 9, 9, 5]
li[1:-1]=[] # 刪除 [0, 5]
刪除元素
可以用 del 語句來刪除列表的指定范圍的元素。
li=[0, 1, 2, 3, 4, 5]
del li[3] # [0, 1, 2, 4, 5]
del li[2:-1] # [0, 1, 5]
列表操作符
[1, 2, 3] + [3, 4, 5] # [1, 2, 3, 3, 4, 5]
[1, 2, 3] * 2 # [1, 2, 3, 1, 2, 3]
3 in [1, 2, 3] # True
for x in [1, 2, 3]: print(x) # 1 2 3
列表函數
li=[0, 1, 5]
max(li) # 5
len(li) # 3
注: 對列表使用 max/min 函數,2.x 中對元素值類型無要求,3.x 則要求元素值類型必須一致。
列表方法
列表推導式
列表推導式提供了從序列創建列表的簡單途徑。通常應用程序將一些操作應用于某個序列的每個元素,用其獲得的結果作為生成新列表的元素,或者根據確定的判定條件創建子序列。
每個列表推導式都在 for 之后跟一個表達式,然后有零到多個 for 或 if 子句。返回結果是一個根據表達從其后的 for 和 if 上下文環境中生成出來的列表。如果希望表達式推導出一個元組,就必須使用括號。
將列表中每個數值乘三,獲得一個新的列表:
vec=[2, 4, 6]
[(x, x**2) for x in vec]
# [(2, 4), (4, 16), (6, 36)]
對序列里每一個元素逐個調用某方法:
freshfruit=[' banana', ' loganberry ', 'passion fruit ']
[weapon.strip() for weapon in freshfruit]
# ['banana', 'loganberry', 'passion fruit']
用 if 子句作為過濾器:
vec=[2, 4, 6]
[3*x for x in vec if x > 3]
# [12, 18]
vec1=[2, 4, 6]
vec2=[4, 3, -9]
[x*y for x in vec1 for y in vec2]
# [8, 6, -18, 16, 12, -36, 24, 18, -54]
[vec1[i]*vec2[i] for i in range(len(vec1))]
# [8, 12, -54]
列表嵌套解析:
matrix=[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
new_matrix=[[row[i] for row in matrix] for i in range(len(matrix[0]))]
print(new_matrix)
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
元組(tuple)
訪問元組
訪問元組的方式與列表是一致的。 元組的元素可以直接賦值給多個變量,但變量數必須與元素數量一致。
a, b, c=(1, 2, 3)
print(a, b, c)
組合元組
元組中的元素值是不允許修改的,但我們可以對元組進行連接組合
tup1=(12, 34.56);
tup2=('abc', 'xyz')
tup3=tup1 + tup2;
print (tup3)
# (12, 34.56, 'abc', 'xyz')
刪除元組
元組中的元素值是不允許刪除的,但我們可以使用 del 語句來刪除整個元組
元組函數
元組推導式
t=1, 2, 3
print(t)
# (1, 2, 3)
u=t, (3, 4, 5)
print(u)
# ((1, 2, 3), (3, 4, 5))
字典(dict)
格式如下:
d={key1 : value1, key2 : value2 }
訪問字典
dis={'a': 1, 'b': [1, 2, 3]}
print(dis['b'][2])
修改字典
dis={'a': 1, 'b': [1, 2, 3], 9: {'name': 'hello'}}
dis[9]['name']=999
print(dis)
# {'a': 1, 9: {'name': 999}, 'b': [1, 2, 3]}
刪除字典
用 del 語句刪除字典或字典的元素。
dis={'a': 1, 'b': [1, 2, 3], 9: {'name': 'hello'}}
del dis[9]['name']
print(dis)
del dis # 刪除字典
# {'a': 1, 9: {}, 'b': [1, 2, 3]}
字典函數
字典方法
dic1={'a': 'a'}
dic2={9: 9, 'a': 'b'}
dic1.update(dic2)
print(dic1)
# {'a': 'b', 9: 9}
字典推導式
構造函數 dict() 直接從鍵值對元組列表中構建字典。如果有固定的模式,列表推導式指定特定的鍵值對:
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}
此外,字典推導可以用來創建任意鍵和值的表達式詞典:
>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
如果關鍵字只是簡單的字符串,使用關鍵字參數指定鍵值對有時候更方便:
>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}
集合(set)
集合是一個無序不重復元素的序列
創建集合
{1, 2, 1, 3} # {} {1, 2, 3}
set('12345') # 字符串 {'3', '5', '4', '2', '1'}
set([1, 'a', 23.4]) # 列表 {1, 'a', 23.4}
set((1, 'a', 23.4)) # 元組 {1, 'a', 23.4}
set({1:1, 'b': 9}) # 字典 {1, 'b'}
添加元素
將元素 val 添加到集合 set 中,如果元素已存在,則不進行任何操作:
set.add(val)
也可以用 update 方法批量添加元素,參數可以是列表,元組,字典等:
set.update(list1, list2,...)
移除元素
如果存在元素 val 則移除,不存在就報錯:
set.remove(val)
如果存在元素 val 則移除,不存在也不會報錯:
set.discard(val)
隨機移除一個元素:
set.pop()
元素個數
與其他序列一樣,可以用 len(set) 獲取集合的元素個數。
清空集合
set.clear()
set=set()
判斷元素是否存在
val in set
其他方法
集合計算
a=set('abracadabra')
b=set('alacazam')
print(a) # a 中唯一的字母
# {'a', 'r', 'b', 'c', 'd'}
print(a - b) # 在 a 中的字母,但不在 b 中
# {'r', 'd', 'b'}
print(a | b) # 在 a 或 b 中的字母
# {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
print(a & b) # 在 a 和 b 中都有的字母
# {'a', 'c'}
print(a ^ b) # 在 a 或 b 中的字母,但不同時在 a 和 b 中
# {'r', 'd', 'b', 'm', 'z', 'l'}
集合推導式
a={x for x in 'abracadabra' if x not in 'abc'}
print(a)
# {'d', 'r'}
流程控制
if 控制
if 表達式1:
語句
if 表達式2:
語句
elif 表達式3:
語句
else:
語句
elif 表達式4:
語句
else:
語句
1、每個條件后面要使用冒號 :,表示接下來是滿足條件后要執行的語句塊。 2、使用縮進來劃分語句塊,相同縮進數的語句在一起組成一個語句塊。 3、在 Python 中沒有 switch - case 語句。
三元運算符:
<表達式1> if <條件> else <表達式2>
編寫條件語句時,應該盡量避免使用嵌套語句。嵌套語句不便于閱讀,而且可能會忽略一些可能性。
for 遍歷
for <循環變量> in <循環對象>:
<語句1>
else:
<語句2>
else 語句中的語句2只有循環正常退出(遍歷完所有遍歷對象中的值)時執行。
在字典中遍歷時,關鍵字和對應的值可以使用 items() 方法同時解讀出來:
knights={'gallahad': 'the pure', 'robin': 'the brave'}
for k, v in knights.items():
print(k, v)
在序列中遍歷時,索引位置和對應值可以使用 enumerate() 函數同時得到:
for i, v in enumerate(['tic', 'tac', 'toe']):
print(i, v)
同時遍歷兩個或更多的序列,可以使用 zip() 組合:
questions=['name', 'quest', 'favorite color']
answers=['lancelot', 'the holy grail', 'blue']
for q, a in zip(questions, answers):
print('What is your {0}? It is {1}.'.format(q, a))
要反向遍歷一個序列,首先指定這個序列,然后調用 reversed() 函數:
for i in reversed(range(1, 10, 2)):
print(i)
要按順序遍歷一個序列,使用 sorted() 函數返回一個已排序的序列,并不修改原值:
basket=['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
for f in sorted(set(basket)):
print(f)
while 循環
while<條件>:
<語句1>
else:
<語句2>
break、continue、pass
break 語句用在 while 和 for 循環中,break 語句用來終止循環語句,即循環條件沒有 False 條件或者序列還沒被完全遞歸完,也會停止執行循環語句。 continue 語句用在 while 和 for 循環中,continue 語句用來告訴 Python 跳過當前循環的剩余語句,然后繼續進行下一輪循環。 continue 語句跳出本次循環,而 break 跳出整個循環。
pass 是空語句,是為了保持程序結構的完整性。pass 不做任何事情,一般用做占位語句。
迭代器
迭代器可以被 for 循環進行遍歷:
li=[1, 2, 3]
it=iter(li)
for val in it:
print(val)
迭代器也可以用 next() 函數訪問下一個元素值:
import sys
li=[1,2,3,4]
it=iter(li)
while True:
try:
print (next(it))
except StopIteration:
sys.exit()
生成器
import sys
def fibonacci(n): # 生成器函數 - 斐波那契
a, b, counter=0, 1, 0
while True:
if (counter > n):
return
yield a
a, b=b, a + b
counter +=1
f=fibonacci(10) # f 是一個迭代器,由生成器返回生成
while True:
try:
print(next(f))
except StopIteration:
sys.exit()
函數
自定義函數
函數(Functions)是指可重復使用的程序片段。它們允許你為某個代碼塊賦予名字,允許你通過這一特殊的名字在你的程序任何地方來運行代碼塊,并可重復任何次數。這就是所謂的調用(Calling)函數。
def 函數名(參數列表):
函數體
參數傳遞
在 Python 中,類型屬于對象,變量是沒有類型的:
a=[1,2,3]
a="Runoob"
以上代碼中,[1,2,3] 是 List 類型,”Runoob” 是 String 類型,而變量 a 是沒有類型,她僅僅是一個對象的引用(一個指針),可以是指向 List 類型對象,也可以是指向 String 類型對象。
可更改與不可更改對象
在 Python 中,字符串,數字和元組是不可更改的對象,而列表、字典等則是可以修改的對象。
Python 函數的參數傳遞:
Python 中一切都是對象,嚴格意義我們不能說值傳遞還是引用傳遞,我們應該說傳不可變對象和傳可變對象。
參數
必需參數
必需參數須以正確的順序傳入函數。調用時的數量必須和聲明時的一樣。
關鍵字參數
關鍵字參數和函數調用關系緊密,函數調用使用關鍵字參數來確定傳入的參數值。 使用關鍵字參數允許函數調用時參數的順序與聲明時不一致,因為 Python 解釋器能夠用參數名匹配參數值。
def print_info(name, age):
"打印任何傳入的字符串"
print("名字: ", name)
print("年齡: ", age)
return
print_info(age=50, name="john")
默認參數
調用函數時,如果沒有傳遞參數,則會使用默認參數。
def print_info(name, age=35):
print ("名字: ", name)
print ("年齡: ", age)
return
print_info(age=50, name="john")
print("------------------------")
print_info(name="john")
不定長參數
def print_info(arg1, *vartuple):
print("輸出: ")
print(arg1)
for var in vartuple:
print (var)
return
print_info(10)
print_info(70, 60, 50)
def print_info(arg1, **vardict):
print("輸出: ")
print(arg1)
print(vardict)
print_info(1, a=2, b=3)
匿名函數
Python 使用 lambda 來創建匿名函數。
所謂匿名,意即不再使用 def 語句這樣標準的形式定義一個函數。
lambda 只是一個表達式,函數體比 def 簡單很多。 lambda 的主體是一個表達式,而不是一個代碼塊。僅僅能在 lambda 表達式中封裝有限的邏輯進去。 lambda 函數擁有自己的命名空間,且不能訪問自己參數列表之外或全局命名空間里的參數。 雖然 lambda 函數看起來只能寫一行,卻不等同于 C 或 C++ 的內聯函數,后者的目的是調用小函數時不占用棧內存從而增加運行效率。
# 語法格式
lambda [arg1 [,arg2,.....argn]]:expression
變量作用域
以 L –> E –> G –> B 的規則查找,即:在局部找不到,便會去局部外的局部找(例如閉包),再找不到就會去全局找,再者去內建中找。
Python 中只有模塊(module),類(class)以及函數(def、lambda)才會引入新的作用域,其它的代碼塊(如 if/elif/else/、try/except、for/while等)是不會引入新的作用域的,也就是說這些語句內定義的變量,外部也可以訪問。
定義在函數內部的變量擁有一個局部作用域,定義在函數外的擁有全局作用域。
局部變量只能在其被聲明的函數內部訪問,而全局變量可以在整個程序范圍內訪問。調用函數時,所有在函數內聲明的變量名稱都將被加入到作用域中。
當內部作用域想修改外部作用域的變量時,就要用到global和nonlocal關鍵字。
num=1
def fun1():
global num # 需要使用 global 關鍵字聲明
print(num)
num=123
print(num)
fun1()
如果要修改嵌套作用域(enclosing 作用域,外層非全局作用域)中的變量則需要 nonlocal 關鍵字。
def outer():
num=10
def inner():
nonlocal num # nonlocal關鍵字聲明
num=100
print(num)
inner()
print(num)
outer()
模塊
編寫模塊有很多種方法,其中最簡單的一種便是創建一個包含函數與變量、以 .py 為后綴的文件。
另一種方法是使用撰寫 Python 解釋器本身的本地語言來編寫模塊。舉例來說,你可以使用 C 語言來撰寫 Python 模塊,并且在編譯后,你可以通過標準 Python 解釋器在你的 Python 代碼中使用它們。
模塊是一個包含所有你定義的函數和變量的文件,其后綴名是.py。模塊可以被別的程序引入,以使用該模塊中的函數等功能。這也是使用 Python 標準庫的方法。
當解釋器遇到 import 語句,如果模塊在當前的搜索路徑就會被導入。
搜索路徑是一個解釋器會先進行搜索的所有目錄的列表。如想要導入模塊,需要把命令放在腳本的頂端。
一個模塊只會被導入一次,這樣可以防止導入模塊被一遍又一遍地執行。
搜索路徑被存儲在 sys 模塊中的 path 變量。當前目錄指的是程序啟動的目錄。
導入模塊
導入模塊:
import module1[, module2[,... moduleN]]
從模塊中導入一個指定的部分到當前命名空間中:
from modname import name1[, name2[, ... nameN]]
把一個模塊的所有內容全都導入到當前的命名空間:
from modname import *
__name__ 屬性
每個模塊都有一個 __name__ 屬性,當其值是 '__main__' 時,表明該模塊自身在運行,否則是被引入。
一個模塊被另一個程序第一次引入時,其主程序將運行。如果我們想在模塊被引入時,模塊中的某一程序塊不執行,我們可以用 __name__ 屬性來使該程序塊僅在該模塊自身運行時執行。
if __name__=='__main__':
print('程序自身在運行')
else:
print('我來自另一模塊')
dir 函數
內置的函數 dir() 可以找到模塊內定義的所有名稱。以一個字符串列表的形式返回。
如果沒有給定參數,那么 dir() 函數會羅列出當前定義的所有名稱。
在 Python 中萬物皆對象,int、str、float、list、tuple等內置數據類型其實也是類,也可以用 dir(int) 查看 int 包含的所有方法。也可以使用 help(int) 查看 int 類的幫助信息。
包
包是一種管理 Python 模塊命名空間的形式,采用”點模塊名稱”。
比如一個模塊的名稱是 A.B, 那么他表示一個包 A中的子模塊 B 。
就好像使用模塊的時候,你不用擔心不同模塊之間的全局變量相互影響一樣,采用點模塊名稱這種形式也不用擔心不同庫之間的模塊重名的情況。
在導入一個包的時候,Python 會根據 sys.path 中的目錄來尋找這個包中包含的子目錄。
目錄只有包含一個叫做 __init__.py 的文件才會被認作是一個包,主要是為了避免一些濫俗的名字(比如叫做 string)不小心的影響搜索路徑中的有效模塊。
最簡單的情況,放一個空的 __init__.py 文件就可以了。當然這個文件中也可以包含一些初始化代碼或者為 __all__ 變量賦值。
第三方模塊
easy_install 的用法:
easy_install 包名
easy_install "包名==包的版本號"
easy_install -U "包名 >=包的版本號"
pip 的用法:
pip install 包名pip install 包名==包的版本號
pip install —upgrade 包名 >=包的版本號
pip uninstall 包名
pip list
面向對象
類與對象是面向對象編程的兩個主要方面。一個類(Class)能夠創建一種新的類型(Type),其中對象(Object)就是類的實例(Instance)。可以這樣來類比:你可以擁有類型 int 的變量,也就是說存儲整數的變量是 int 類的實例(對象)。
Python 中的類提供了面向對象編程的所有基本功能:類的繼承機制允許多個基類,派生類可以覆蓋基類中的任何方法,方法中可以調用基類中的同名方法。
對象可以包含任意數量和類型的數據。
self
self 表示的是當前實例,代表當前對象的地址。類由 self.__class__ 表示。
self 不是關鍵字,其他名稱也可以替代,但 self 是個通用的標準名稱。
類
類由 class 關鍵字來創建。 類實例化后,可以使用其屬性,實際上,創建一個類之后,可以通過類名訪問其屬性。
對象方法
方法由 def 關鍵字定義,與函數不同的是,方法必須包含參數 self, 且為第一個參數,self 代表的是本類的實例。
類方法
裝飾器 @classmethod 可以將方法標識為類方法。類方法的第一個參數必須為 cls,而不再是 self。
靜態方法
裝飾器 @staticmethod 可以將方法標識為靜態方法。靜態方法的第一個參數不再指定,也就不需要 self 或 cls。
__init__ 方法
__init__ 方法即構造方法,會在類的對象被實例化時先運行,可以將初始化的操作放置到該方法中。
如果重寫了 __init__,實例化子類就不會調用父類已經定義的 __init__。
變量
類變量(Class Variable)是共享的(Shared)——它們可以被屬于該類的所有實例訪問。該類變量只擁有一個副本,當任何一個對象對類變量作出改變時,發生的變動將在其它所有實例中都會得到體現。
對象變量(Object variable)由類的每一個獨立的對象或實例所擁有。在這種情況下,每個對象都擁有屬于它自己的字段的副本,也就是說,它們不會被共享,也不會以任何方式與其它不同實例中的相同名稱的字段產生關聯。
在 Python 中,變量名類似 __xxx__ 的,也就是以雙下劃線開頭,并且以雙下劃線結尾的,是特殊變量,特殊變量是可以直接訪問的,不是 private 變量,所以,不能用 __name__、__score__ 這樣的變量名。
訪問控制
我們還認為約定,一個下劃線開頭的屬性或方法為受保護的。比如,_protected_attr、_protected_method。
繼承
類可以繼承,并且支持繼承多個父類。在定義類時,類名后的括號中指定要繼承的父類,多個父類之間用逗號分隔。
子類的實例可以完全訪問所繼承所有父類的非私有屬性和方法。
若是父類中有相同的方法名,而在子類使用時未指定,Python 從左至右搜索,即方法在子類中未找到時,從左到右查找父類中是否包含方法。
方法重寫
子類的方法可以重寫父類的方法。重寫的方法參數不強制要求保持一致,不過合理的設計都應該保持一致。
super() 函數可以調用父類的一個方法,以多繼承問題。
類的專有方法:
類的專有方法也支持重載。
實例
class Person:
"""人員信息"""
# 姓名(共有屬性)
name=''
# 年齡(共有屬性)
age=0
def __init__(self, name='', age=0):
self.name=name
self.age=age
# 重載專有方法: __str__
def __str__(self):
return "這里重載了 __str__ 專有方法, " + str({'name': self.name, 'age': self.age})
def set_age(self, age):
self.age=age
class Account:
"""賬戶信息"""
# 賬戶余額(私有屬性)
__balance=0
# 所有賬戶總額
__total_balance=0
# 獲取賬戶余額
# self 必須是方法的第一個參數
def balance(self):
return self.__balance
# 增加賬戶余額
def balance_add(self, cost):
# self 訪問的是本實例
self.__balance +=cost
# self.__class__ 可以訪問類
self.__class__.__total_balance +=cost
# 類方法(用 @classmethod 標識,第一個參數為 cls)
@classmethod
def total_balance(cls):
return cls.__total_balance
# 靜態方法(用 @staticmethod 標識,不需要類參數或實例參數)
@staticmethod
def exchange(a, b):
return b, a
class Teacher(Person, Account):
"""教師"""
# 班級名稱
_class_name=''
def __init__(self, name):
# 第一種重載父類__init__()構造方法
# super(子類,self).__init__(參數1,參數2,....)
super(Teacher, self).__init__(name)
def get_info(self):
# 以字典的形式返回個人信息
return {
'name': self.name, # 此處訪問的是父類Person的屬性值
'age': self.age,
'class_name': self._class_name,
'balance': self.balance(), # 此處調用的是子類重載過的方法
}
# 方法重載
def balance(self):
# Account.__balance 為私有屬性,子類無法訪問,所以父類提供方法進行訪問
return Account.balance(self) * 1.1
class Student(Person, Account):
"""學生"""
_teacher_name=''
def __init__(self, name, age=18):
# 第二種重載父類__init__()構造方法
# 父類名稱.__init__(self,參數1,參數2,...)
Person.__init__(self, name, age)
def get_info(self):
# 以字典的形式返回個人信息
return {
'name': self.name, # 此處訪問的是父類Person的屬性值
'age': self.age,
'teacher_name': self._teacher_name,
'balance': self.balance(),
}
# 教師 John
john=Teacher('John')
john.balance_add(20)
john.set_age(36) # 子類的實例可以直接調用父類的方法
print("John's info:", john.get_info())
# 學生 Mary
mary=Student('Mary', 18)
mary.balance_add(18)
print("Mary's info:", mary.get_info())
# 學生 Fake
fake=Student('Fake')
fake.balance_add(30)
print("Fake's info", fake.get_info())
# 三種不同的方式調用靜態方法
print("john.exchange('a', 'b'):", john.exchange('a', 'b'))
print('Teacher.exchange(1, 2)', Teacher.exchange(1, 2))
print('Account.exchange(10, 20):', Account.exchange(10, 20))
# 類方法、類屬性
print('Account.total_balance():', Account.total_balance())
print('Teacher.total_balance():', Teacher.total_balance())
print('Student.total_balance():', Student.total_balance())
# 重載專有方法
print(fake)
輸出:
John's info: {'name': 'John', 'age': 36, 'class_name': '', 'balance': 22.0}Mary's info: {'name': 'Mary', 'age': 18, 'teacher_name': '', 'balance': 18}Fake's info {'name': 'Fake', 'age': 18, 'teacher_name': '', 'balance': 30}john.exchange('a', 'b'): ('b', 'a')Teacher.exchange(1, 2) (2, 1)Account.exchange(10, 20): (20, 10)Account.total_balance(): 0Teacher.total_balance(): 20Student.total_balance(): 48這里重載了 __str__ 專有方法, {'name': 'Fake', 'age': 18}
錯誤和異常
語法錯誤
SyntaxError 類表示語法錯誤,當解釋器發現代碼無法通過語法檢查時會觸發的錯誤。語法錯誤是無法用 try...except...捕獲的。
>>> print:
File "<stdin>", line 1
print:
^
SyntaxError: invalid syntax
異常
即便程序的語法是正確的,在運行它的時候,也有可能發生錯誤。運行時發生的錯誤被稱為異常。 錯誤信息的前面部分顯示了異常發生的上下文,并以調用棧的形式顯示具體信息。
>>> 1 + '0'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
異常處理
Python 提供了 try ... except ... 的語法結構來捕獲和處理異常。
try 語句執行流程大致如下:
拋出異常
使用 raise 語句拋出一個指定的異常。
raise 唯一的一個參數指定了要被拋出的異常。它必須是一個異常的實例或者是異常的類(也就是 Exception 的子類)。
如果你只想知道這是否拋出了一個異常,并不想去處理它,那么一個簡單的 raise 語句就可以再次把它拋出。
自定義異常
可以通過創建一個新的異常類來擁有自己的異常。異常類繼承自 Exception 類,可以直接繼承,或者間接繼承。
當創建一個模塊有可能拋出多種不同的異常時,一種通常的做法是為這個包建立一個基礎異常類,然后基于這個基礎類為不同的錯誤情況創建不同的子類。
大多數的異常的名字都以”Error”結尾,就跟標準的異常命名一樣。
實例
import sys
class Error(Exception):
"""Base class for exceptions in this module."""
pass
# 自定義異常
class InputError(Error):
"""Exception raised for errors in the input.
Attributes:
expression -- input expression in which the error occurred
message -- explanation of the error
"""
def __init__(self, expression, message):
self.expression=expression
self.message=message
try:
print('code start running...')
raise InputError('input()', 'input error')
# ValueError
int('a')
# TypeError
s=1 + 'a'
dit={'name': 'john'}
# KeyError
print(dit['1'])
except InputError as ex:
print("InputError:", ex.message)
except TypeError as ex:
print('TypeError:', ex.args)
pass
except (KeyError, IndexError) as ex:
"""支持同時處理多個異常, 用括號放到元組里"""
print(sys.exc_info())
except:
"""捕獲其他未指定的異常"""
print("Unexpected error:", sys.exc_info()[0])
# raise 用于拋出異常
raise RuntimeError('RuntimeError')
else:
"""當無任何異常時, 會執行 else 子句"""
print('"else" 子句...')
finally:
"""無論有無異常, 均會執行 finally"""
print('finally, ending')
文件操作
打開文件
open() 函數用于打開/創建一個文件,并返回一個 file 對象:
open(filename, mode)
文件打開模式:
文件對象方法
實例
filename='data.log'
# 打開文件(a+ 追加讀寫模式)
# 用 with 關鍵字的方式打開文件,會自動關閉文件資源
with open(filename, 'w+', encoding='utf-8') as file:
print('文件名稱: {}'.format(file.name))
print('文件編碼: {}'.format(file.encoding))
print('文件打開模式: {}'.format(file.mode))
print('文件是否可讀: {}'.format(file.readable()))
print('文件是否可寫: {}'.format(file.writable()))
print('此時文件指針位置為: {}'.format(file.tell()))
# 寫入內容
num=file.write("第一行內容")
print('寫入文件 {} 個字符'.format(num))
# 文件指針在文件尾部,故無內容
print(file.readline(), file.tell())
# 改變文件指針到文件頭部
file.seek(0)
# 改變文件指針后,讀取到第一行內容
print(file.readline(), file.tell())
# 但文件指針的改變,卻不會影響到寫入的位置
file.write('第二次寫入的內容')
# 文件指針又回到了文件尾
print(file.readline(), file.tell())
# file.read() 從當前文件指針位置讀取指定長度的字符
file.seek(0)
print(file.read(9))
# 按行分割文件,返回字符串列表
file.seek(0)
print(file.readlines())
# 迭代文件對象,一行一個元素
file.seek(0)
for line in file:
print(line, end='')
# 關閉文件資源
if not file.closed:
file.close()
輸出:
文件名稱: data.log
文件編碼: utf-8
文件打開模式: w+
文件是否可讀: True
文件是否可寫: True
此時文件指針位置為: 0
寫入文件 6 個字符
16
第一行內容
16
41
第一行內容
第二次
['第一行內容', '第二次寫入的內容']
第一行內容
第二次寫入的內容
序列化
在 Python 中 pickle 模塊實現對數據的序列化和反序列化。pickle 支持任何數據類型,包括內置數據類型、函數、類、對象等。
方法
dump
將數據對象序列化后寫入文件
pickle.dump(obj, file, protocol=None, fix_imports=True)
必填參數 obj 表示將要封裝的對象。 必填參數 file 表示 obj 要寫入的文件對象,file 必須以二進制可寫模式打開,即wb。 可選參數 protocol 表示告知 pickle 使用的協議,支持的協議有 0,1,2,3,默認的協議是添加在 Python 3 中的協議3。
load
從文件中讀取內容并反序列化
pickle.load(file, fix_imports=True, encoding='ASCII', errors='strict')
必填參數 file 必須以二進制可讀模式打開,即rb,其他都為可選參數。
dumps
以字節對象形式返回封裝的對象,不需要寫入文件中
pickle.dumps(obj, protocol=None, fix_imports=True)
loads
從字節對象中讀取被封裝的對象,并返回
pickle.loads(bytes_object, fix_imports=True, encoding='ASCII', errors='strict')
實例
import pickle
data=[1, 2, 3]
# 序列化數據并以字節對象返回
dumps_obj=pickle.dumps(data)
print('pickle.dumps():', dumps_obj)
# 從字節對象中反序列化數據
loads_data=pickle.loads(dumps_obj)
print('pickle.loads():', loads_data)
filename='data.log'
# 序列化數據到文件中
with open(filename, 'wb') as file:
pickle.dump(data, file)
# 從文件中加載并反序列化
with open(filename, 'rb') as file:
load_data=pickle.load(file)
print('pickle.load():', load_data)
輸出:
pickle.dumps(): b'?]q(KKKe.'
pickle.loads(): [1, 2, 3]
pickle.load(): [1, 2, 3]
命名規范
Python 之父 Guido 推薦的規范
一份來自谷歌的 Python 風格規范:
http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/
還需要更多資料可以私信小編(資料)獲取
*請認真填寫需求信息,我們會在24小時內與您取得聯系。