Python入門教程(三)


  按理說,這篇是收尾了。可能有一點術語,但大都是顧名思義的。重要概念【】在第二個標題下說明。


函數式編程(縮寫:FP)

  如果對此有興趣,可以看scheme視頻教程(SICP公開課)(scheme是lisp的一種),也可以直接看SICP中譯本。我只是“知其大略”罷了。

  語言如果有以下特徵之一,可以被認爲有“函數式編程風格”(如果代碼使用了這些特徵,也說這些代碼“有函數式編程風格”):

  1:函數和數平起平坐,畢竟都是用符號表示而已。如果你在中學時對函數有了不錯的理解(測試:f(x)什麼意思?爲什麼這樣寫?),那麼會很容易明白下面這幾行代碼在做什麼:

def f(x):
	return 2*x

g = f
print(g(3))

  2:允許用lambda 表達式。這個慢慢會理解的,意思是函數的本質在內容,不在名字,因此操作可以不取名,直接用內容表示。用處主要在,某些函數(比如map)要求你傳入一個函數當做參數,你可以不取名(用def定義必須取名)而一下子傳入。在tk中遇到過用lambda強制傳參,呃……到時候上網查。

  3:可以在函數內再定義函數,python中可以這麼做,但官方不希望這麼做。函數內部的東西是對外隱藏的,因此如果在A函數內部定義B函數,那麼只有在A內部纔可以使用B函數(變量也一樣)。但是Python作爲解釋性語言,每次調用函數就是把內部語句(包括def)全執行一遍,這非常浪費。我們僅僅是想表示“B函數只能被A引用”,卻要負擔如此後果,似乎不值得。但是並列放置,不免看起來沒有層次……(這對強迫症尤其重要)


面向對象(縮寫:OOP)

  也是一種風格,和FP不衝突,和FP也有交集。顧名思義,就是關注對象。說某種語言面向對象,主要是說有“類”這麼個概念。面向對象也被稱爲“貼近人的思考過程”,人就是面向對象的(可以先不理解這個說法)。

  ……python的類真的不大好講。比如要儲存一個點,你可以用一個字典(比元組好,因爲不需要順序並且分量有名字),像這樣:

p1 = {'x' : 3, 'y' : 4}       #表示橫座標爲3,縱座標爲4的點
print(

  類和這很相似,以至於我不懂類的時候,把一個pygame程序中的(簡單的)類全用字典表示了。如果用類,可以達到這種效果:(省略了定義類的步驟,所以不要嘗試運行)

p1 = Point(3,4)
print(p1.x)
print(p1.y)

  如果你需要好幾個點,那麼這無疑比字典好得多了。或者遇到這種情況:

a = People('Jack', 7)
print(a.name)
print(a.age)

  這至少看着很優雅。這裏,People和Point是“類”,p1和a是對應類的“實例”,創建實例又稱“實例化”。我現在補上定義部分,應該很容易懂,至少對於一部分……

class Point:
	def __init__(self,a,b):
		self.x = a
		self.y = b
		
class People:
	def __init__(self,a,b):
		self.name = a
		self.age = b

  我一開始也不懂self是啥,而且教程說“可以把self換成任何東西”。你一定希望寫這樣的代碼,或者假如是這樣你就能懂——(你知道init是英文“定義”的縮寫)

class Point:
	def init(a,b):
		x = a
		y = b
		
class People:
	def init(a,b):
		name = a
		age = b

  實不相瞞,如果不是python,比如C#,那麼就是這樣子的,並沒有self 。你完全就可以把self當成擺設,先用習慣,然後聽我下面的解釋(我認真的)。

  以下兩段代碼做的事是完全相同的:

class People:
	def __init__(self,a,b):
		self.name = a
		self.age = b

x = People('Jack',7)
class People: pass
x = object.__new__(People)
x.name = 'Jack'
x.age = 7

  首先,name和age看似成了“固有屬性”,其實壓根沒有這回事。在默認情況下,python中的類是可以【隨時】添加【任意】屬性的。你可以在上述(任一)代碼段後加上一句x.hello = 1,然後hello就也成了x的屬性。對於第二段代碼,第一行顯然是“創建了一個叫People 的類,但沒有內容”,第二行是創建了一個People的對象,但顯然它沒有屬性,畢竟定義類的時候什麼都沒寫,剩下兩行給這個對象追加了兩個屬性。其實你大概已經想到,創建和初始化是兩個過程,創建是創建空對象,初始化則添加屬性。而添加什麼屬性,就寫在初始化函數裏。self你也該明白個大概了,如果我再改一改寫法——(前後雙下劃線表示這個函數是官方的,比較特殊,初始化顯然如此)

class People:
	def self.__init__(a,b):
		self.name = a
		self.age = b

  如果寫成這樣,就“差不多是那個意思”了,把self全換成x,就是x的初始化過程。其實還有一重內幕,那就是用點表示屬性也是爲了好看,真實情況是這樣的:

x.__init__(a,b)     #其實是下一行的縮寫,相傳python真的會一模一樣地處理
type(x).__init__(x,a,b)    #type函數返回x的類,等價於下一行
People.__init__(x,a,b)

  既然python內部都把__init__當做有三個參數,那我們定義的時候寫三個參數就合乎情理了。思考:假如你不寫self,你覺得python會怎麼錯誤地【試圖】運行你的東西?

  簡潔模式的People('Jack', 7),其實就是先搞一個空對象,然後(不讓你看地)執行了x.__init__('Jack', 7) 。請注意!這個函數的唯一特殊之處就是,會在用普通方法創建對象時被調用。你可以在裏面寫任何東西,包括print,就像對待一個普通函數——而不是方法。你如果不用類而寫一個一模一樣的函數,除了不能用“.”來調用,其他事情完全相同。甚至你可以創建完了繼續調用__init__,把x.__init__('Jack', 7)寫多少遍都沒問題(可以在修改後用它還原),反正python在創建時執行了一遍就不管了。你可以隨便寫多少個方法。不寫self的後果是,你依然可以用People.f(a,b)調用之,原理就是剛纔的過程。


編程風格/範式

  可以參考《冒號課堂——編程範式與OOP思想》,不過要注意python沒有變量類型,導致沒有參數類型,所以本身就是“泛型”;python的一切都是指針(沒有變量類型導致), a = 1 意味着在某處創建一個 1,然後把 a 指向那裏——而 b = a,表示把 a 儲存的位置(這裏是那個1的位置)複製給 b ,函數的參數自然本質上也是這些東西,但是這些地址一般都是不可見也不需要可見的;也因此(“不讓你看到任何與內存有關的東西”),python不自帶數組,python列表和lisp鏈表是一致的,等等。python很少在乎底層算法,python使我們更容易寫出程序結構。


迭代器

  試試 a = range(100) 後 print(a) ,你會發現很奇怪的結果。但是list(a) 就是一個從0到99的列表了,a也並非全無用處,可以用 for i in a取代for i in range(100) 。在老版的python中,range(100)就等於0-99的列表,如果打印就會顯示一個列表。但是只是爲了重複100遍,完全沒必要先建立一個如此長的列表,只需要計數就可以了,這就是迭代器。它很像一個列表,但並沒有儲存每一項,而是每次計算下一項。這是一類專門用於迭代的對象,可以用list()來轉換成列表,也可以直接用for循環遍歷。除了range返回的對象有這種性質,map等函數也是如此。如果你想得到列表卻得到了奇怪的東西(不止一種),那很有可能這個東西具備上述兩個性質,是迭代器。迭代器被稱爲“很具有python風格的用法”


修飾器(裝飾器)

  有一種有趣的函數,它的輸入是一個函數,輸出也是一個函數。這種函數就可以作爲修飾器,修飾器有個特殊用法,可以通過在某個def上一行寫@後跟修飾器名,就可以把那個函數傳入修飾器,並用返回的新函數(“修飾後的函數”)覆蓋原有函數。比如這個例子:

def h(func):
	def func2(x):
		r = func(x)
		print("Be called:",func)
		return r
	return func2
	
	
@h
def f(x):
	return 2*x


	

a = f(3) + 1

  另外自行搜索@property,還有知乎回答這個鏈接解釋了@staticmethod 和 @classmethod,我是這個回答下的評論。(我感覺自己是對的……)


內置函數

  就是python自帶的函數,比如print,input等等。

  (耐心查看) 

  Python 內置函數 | 菜鳥教程



  最後,你應該知道,python開發人員給我們留下了一些忠告,雖然不是硬性規定——(運行以下代碼)

import this

  (完)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章