啥叫佩奇?啥叫類?啥叫面向對象?後面兩個問題之前在大學裏「祖傳譚浩強」的時候我常常會有所疑問。老師說着一堆什麼public, private,我都是一臉懵逼,啥叫私有?爲啥要私有?而後就神遊天外了……安全
後來因爲一直接觸數據挖掘類的內容,寫得那根本不是程序,都是腳本,並且基本上也用不到類這種東西。直到前幾天,在一個項目中,我須要對8個分類分別應用同一個模型,我才又回想起了被類支配的恐懼。本文即劫後餘生的產物,沒有網絡上哪些看到想吐的Foo, Bar,只有最通俗易懂的語言。網絡
P1 原由函數
正如上文所說,我須要對8個分類分別應用同一個模型,這8個分類的數據不一樣,但扔進模型並跑一跑的過程是類似的,除此以外,我可能還須要對每一個模型進行一些調參,每一個模型還須要在不一樣的時間段上進行驗證(即傳入模型的數據不一樣)。spa
若是我只用一個腳本的話,面臨的問題就是,我須要分別記錄這8組參數,不一樣分類之間的運行調試可能會互相干擾,修改代碼也不方便。那有沒有方便的方法呢?有啊,懶人福音——類(class)。調試
注意,本文因爲做者水平不行,只說基礎內容。code
P2 什麼是類對象
若是你要我說正經的解釋,抱歉,我徹底說不出來,畢竟我是野路子碼農……但我能夠給出一個稍微通俗一些的解釋,類是一個容器,裏面能夠存放數據和操做。blog
舉個例子的話,銀行帳戶能夠當成一個類。類裏存放有數據,好比名字和金額;類裏也存放有操做(即函數),好比餘額查詢、存錢、取錢。類能夠實例化,就像銀行帳戶能夠爲張三開立,也能夠爲王二狗開立,大多數狀況下,他們之間互不影響。繼承
P3 開始玩類字符串
好了,假設咱們就把銀行帳戶當成一個類吧:
1 class Bank_Account: 2 pass
咱們建立了一個叫Bank_Account的類,pass表明咱們什麼都沒作。這一步就像銀行櫃員拿出了一張空卡。但是卡里總要有點內容吧?
1 class Bank_Account: 2 def __init__(self): 3 self.name = None # 戶主的名字 4 self.balance = 0 # 帳戶餘額
這裏咱們建立了第一個函數__init__(),這是用來實例化的時候初始化用的,這裏咱們記錄了帳戶戶主的名字,帳戶的餘額。初始化就像拿出一張新卡,新卡沒有戶主的名字,帳戶餘額是0,很好理解吧?
注意這裏的self關鍵字,這表明了實例化以後東西,聽起來可能比較拗口。好比這卡發給張三,那麼self就表明張三的帳戶,發給王二狗,那self就表明王二狗的帳戶。
櫃員把這張卡簽發給了張三,但注意,此時裏面毛都沒有。
1 john_san = Bank_Account() # 這個帳戶簽發給了張三 2 print(john_san.name) # 初始時沒有名字 3 print(john_san.balance) # 初始時餘額爲0
返回結果
None
0
self.name這種前面帶self.的咱們稱之爲實例變量,它根據每一個實例的不一樣而不一樣,咱們能夠分別設定,實例之間互不影響:
1 john_san = Bank_Account() # 簽發給張三的帳戶 2 john_san.name = 'John San' 3 4 wanna_go = Bank_Account() # 簽發給王二狗的帳戶 5 wanna_go.name = 'Wanna Go' 6 7 print(john_san.name) 8 print(wanna_go.name)
john_san和wanna_go雖然同屬一個類,但他們的實例變量相互獨立,互不影響(這一點很重要)。返回結果:
John San
Wanna Go
好了,張三和王二狗的帳戶都開好了,可這卡啥事都幹不了,咱們須要添加一些操做(函數),咱們加個存錢函數吧:
1 class Bank_Account: 2 def __init__(self): 3 self.name = None # 戶主的名字 4 self.balance = 0 # 帳戶餘額 5 6 def deposit(self, amt): 7 # self依然表明實例自己,amt表明存入金額 8 self.balance = self.balance + amt
存錢函數一共有2個參數,一個是self,即表明實例自己,這個參數在實際調用函數時沒必要傳入,另外一個是真正的參數amt,即存入金額。咱們調用deposit函數時只須要傳入amt便可,self不用管它。
1 john_san = Bank_Account() 2 john_san.name = 'John San' 3 4 john_san.deposit(100) # 只需傳入amt參數,張三存了100 5 print(john_san.balance)
返回結果:
100
咱們能夠注意到,類中咱們定義的這個deposit函數和通常正經的函數還不太同樣,別人都有return,它卻沒有。但別慌,這很正常,由於參數參與的運算結果已經傳回給了self.balance,不須要再return了。
P4 變量實際上是個傳參用的記事本
好了,有了存錢的操做以後,咱們還想加入一個餘額查詢的操做,雖然咱們能夠調用john_san.balance直接查看,但咱們仍是但願顯得像正經人一些。
1 class Bank_Account: 2 def __init__(self): 3 self.name = None # 戶主的名字 4 self.balance = 0 # 帳戶餘額 5 6 def deposit(self, amt): 7 # self依然表明實例自己,amt表明存入金額 8 self.balance = self.balance + amt 9 10 def check_balance(self): 11 # 這個函數實際調用時不須要任何參數 12 print(self.balance)
注意,此處check_balance這個函數只有類內部數據的傳遞(self.balance),查個帳戶餘額也不須要傳入任何外部參數,因此實際調用時無需任何參數。咱們從新讓張三存入100,再看看結果。
1 john_san = Bank_Account() 2 john_san.name = 'John San' 3 4 john_san.deposit(100) 5 john_san.check_balance()
返回結果
100
和以前如出一轍,成功啦。這裏咱們能夠看到,類內咱們定義的這些實例變量(就是那些self.xxx們)其實就像個記事本,它們主要的做用就是在函數間傳遞數據。就像以前咱們所看到的,self.balance首先在__init__函數中記錄了帳戶餘額的數據(也就是0);以後張三調用deposit函數存錢時,self.balance又記錄了存錢以後的帳戶餘額數據;然後咱們在用check_balance查詢帳戶餘額時,self.balance又將以前記錄的數據傳給這個函數。所以,在類的每一個實例中(張三的帳戶、王二狗的帳戶),實例變量(self.balance)都扮演着一個內部的記事本的角色。它記錄了一些內部數據(帳戶餘額),並在函數中相互傳遞(存錢 -> 查詢餘額)。
所以,每當咱們內部須要傳遞一組數據時,咱們就須要在__inti__下方登記一個實例變量,這就至關於咱們領了一本記事本,用這個本子專門記錄某一組內部數據,好比像下面這樣:
1 class Bank_Account: 2 def __init__(self): 3 self.name = None # 戶主的名字 4 self.balance = 0 # 帳戶餘額 5 self.nickname = None # 新建一個實例變量用於記錄暱稱 6 7 def deposit(self, amt): 8 # self依然表明實例自己,amt表明存入金額 9 self.balance = self.balance + amt 10 11 def check_balance(self): 12 # 這個函數實際調用時不須要任何參數 13 print(self.balance) 14 15 def set_nickname(self, name): 16 # 設定暱稱 17 self.nickname = name 18 19 def get_nickname(self): 20 # 顯示暱稱 21 print(self.nickname) # self.nickname在函數set_nickname與get_nickname中傳遞暱稱數據
P5 公有仍是私有
張三有個仇人李四,他總想對張三作點什麼,李四知道張三開了個帳戶以後,就想經過修改張三的帳戶餘額來使其蒙受損失(能夠說很科幻了)。咱們來看看咱們以前定義的這個類,以及它的變量和函數:
# 變量 self.name self.balance self.nickname # 函數 __init__(self) deposit(self, amt) check_balance(self) set_nickname(self, name) get_nickname(self)
咱們看到除了__init__先後有下劃線,長得比較奇怪之外,其餘的都跟平時咱們定義的變量和函數差很少,這些都是公有的(public),啥意思呢?
意思就是變量能夠直接指定值:
1 john_san.balance = 2000
方法,或者說函數能夠直接調用:
1 john_san.deposit(-1000)
對張三的帳戶來講,這是件很是危險的事情!任何人均可以直接修改他的帳戶餘額,包括李四。咱們必須把某些重要的東西保護起來,不讓外部隨便修改。
一個很是簡單的方法就是把self.balance變成私有變量(private),怎麼變?變量名字前面加兩道下劃線:
1 class Bank_Account: 2 def __init__(self): 3 self.name = None # 戶主的名字 4 self.__balance = 0 # 帳戶餘額(前面加兩道下劃線變爲私有變量) 5 self.nickname = None # 暱稱 6 7 def deposit(self, amt): 8 # self依然表明實例自己,amt表明存入金額 9 self.__balance = self.__balance + amt 10 11 def check_balance(self): 12 # 這個函數實際調用時不須要任何參數 13 print(self.__balance) 14 15 def set_nickname(self, name): 16 # 設定暱稱 17 self.nickname = name 18 19 def get_nickname(self): 20 # 顯示暱稱 21 print(self.nickname) # self.nickname在函數set_nickname與get_nickname中傳遞暱稱數據
帳戶餘額balance變爲私有變量以後,咱們就沒法從外部接觸到這個變量了,不信你能夠試試:
1 john_san = Bank_Account() 2 john_san.name = 'John San' 3 4 john_san.__balance
會拋出一個AttributeError,說Bank_Account這個類沒有__balance這個屬性。這樣一來帳戶餘額再也沒法從外部直接查看和修改了,只能經過類內部的函數間接查看和修改(好比調用deposit或check_balance)。這樣一來,咱們有效地保護了帳戶餘額這個變量不會被亂改或者錯改。
1 john_san = Bank_Account() 2 john_san.name = 'John San' 3 4 john_san.deposit(250) 5 john_san.check_balance()
返回結果:
250
在實際應用的過程當中,咱們可能會遇到這樣一種狀況:我跑了一個分類器,我但願獲得訓練時的機率最大值(好比0.9),而我又但願後續預測過程當中以這一律率的50%做爲閾值。假設咱們將最大機率記錄在self.max_proba中,咱們有可能會由於誤操做或者貪圖方便,直接指定了max_proba的值,當咱們回頭又須要訓練時獲得的max_proba時,咱們會發現它已經被咱們本身更改了,得從頭開始訓練模型,而這可能很是耗時。所以,若咱們把max_proba變爲私有變量,那就不會存在這一問題了,由於咱們沒法從外部直接修改它,也就避免了沒必要要的麻煩。
固然,函數也是相似的道理,假設咱們定義了一個新的函數bonus,它的做用是獎勵帳戶餘額的1%。咱們在deposit函數中調用它,也就意味着每次用戶存款以後,銀行都會給予他(她)帳戶餘額1%的金額做爲獎勵(這銀行吃棗藥丸……)。
1 class Bank_Account: 2 def __init__(self): 3 self.name = None # 戶主的名字 4 self.__balance = 0 # 帳戶餘額(前面加兩道下劃線變爲私有變量) 5 self.nickname = None # 暱稱 6 7 def deposit(self, amt): 8 # self依然表明實例自己,amt表明存入金額 9 self.__balance = self.__balance + amt 10 self.bonus() # 每次存款會得到一次獎勵 11 12 def check_balance(self): 13 # 這個函數實際調用時不須要任何參數 14 print(self.__balance) 15 16 def set_nickname(self, name): 17 # 設定暱稱 18 self.nickname = name 19 20 def get_nickname(self): 21 # 顯示暱稱 22 print(self.nickname) # self.nickname在函數set_nickname與get_nickname中傳遞暱稱數據 23 24 def bonus(self): 25 # 獎勵帳戶餘額1%的金額 26 self.__balance = 1.01 * self.__balance
咱們存款試試:
1 john_san = Bank_Account() 2 john_san.name = 'John San' 3 4 john_san.deposit(250) 5 john_san.check_balance()
返回結果:
252.5
然而,這又存在一個安全性問題,若是張三是心裏險惡之人,他能夠屢次反覆調用bonus函數,不斷得到獎勵而根本不用存錢,這家銀行豈不是藥丸得更快了?如何避免這一點呢?咱們將bonus轉爲私有函數,一樣也是前面加兩根下劃線:
1 class Bank_Account: 2 def __init__(self): 3 self.name = None # 戶主的名字 4 self.__balance = 0 # 帳戶餘額(前面加兩道下劃線變爲私有變量) 5 self.nickname = None # 暱稱 6 7 def deposit(self, amt): 8 # self依然表明實例自己,amt表明存入金額 9 self.__balance = self.__balance + amt 10 self.__bonus() # 類內依然能夠調用,但類外就不行了 11 12 def check_balance(self): 13 # 這個函數實際調用時不須要任何參數 14 print(self.__balance) 15 16 def set_nickname(self, name): 17 # 設定暱稱 18 self.nickname = name 19 20 def get_nickname(self): 21 # 顯示暱稱 22 print(self.nickname) # self.nickname在函數set_nickname與get_nickname中傳遞暱稱數據 23 24 def __bonus(self): 25 # 如今它變成了私有函數 26 self.__balance = 1.01 * self.__balance
如今咱們把bonus變成了私有函數,只有類內的其餘函數能用調用它,而咱們在外部是沒法調用的,不信能夠試試:
1 john_san = Bank_Account() 2 john_san.name = 'John San' 3 4 john_san.__bonus()
一樣會拋出一個AttributeError,說Bank_Account這個類沒有__bonus這個屬性。這樣一來,咱們就避免了張三利用漏洞做弊的可能。
除此以外,私有化也能讓你的自動補全變得更爲簡潔,咱們可能有不少預處理函數或者中間變量,它們的做用只是完成一些中間步驟,並將結果傳入最終處理的函數中,咱們可能並不但願按下Tab時會看到這些東西,會有些混亂。那麼在肯定沒問題的狀況下,咱們能夠將它們所有私有化,那它們也不會出如今自動補全裏了。
P6 類變量
以前咱們說的那些帶self.的變量都稱爲實例變量,那咱們也會聽到類變量,類變量又是什麼鬼呢?
1 class Bank_Account: 2 bank_name = 'Bank of XXX' # 類變量,銀行的名字 3 4 def __init__(self): 5 self.name = None # 戶主的名字 6 self.__balance = 0 # 帳戶餘額(前面加兩道下劃線變爲私有變量) 7 self.nickname = None # 暱稱 8 9 def deposit(self, amt): 10 # self依然表明實例自己,amt表明存入金額 11 self.__balance = self.__balance + amt 12 self.__bonus() # 類內依然能夠調用,但類外就不行了 13 14 def check_balance(self): 15 # 這個函數實際調用時不須要任何參數 16 print(self.__balance) 17 18 def set_nickname(self, name): 19 # 設定暱稱 20 self.nickname = name 21 22 def get_nickname(self): 23 # 顯示暱稱 24 print(self.nickname) # self.nickname在函數set_nickname與get_nickname中傳遞暱稱數據 25 26 def __bonus(self): 27 # 如今它變成了私有函數 28 self.__balance = 1.01 * self.__balance
咱們在__init__()函數以前加了一個變量,這個變量的名字前面沒有self.,它是直接屬於Bank_Account這個類的變量,在這裏就是銀行的名字。每一個簽發的帳戶都會有這個屬性,並且如出一轍。這和實例變量的初始化並無什麼區別。
1 john_san = Bank_Account() 2 john_san.name = 'John San' 3 4 wanna_go = Bank_Account() 5 wanna_go.name = 'Wanna Go' 6 7 print(john_san.bank_name) 8 print(wanna_go.bank_name)
返回結果:
Bank of XXX
Bank of XXX
如今銀行忽然更名字了,咱們看一下結果:
1 Bank_Account.bank_name = 'Bank of OOO' # 注意,這裏是設定類,而不是某個實例 2 3 print(john_san.bank_name) 4 print(wanna_go.bank_name)
返回結果:
Bank of OOO
Bank of OOO
這就至關省心了,咱們無需一個一個實例地去改,直接改類變量的值就好了。咱們也能夠試試修改實例變量,例如:
1 Bank_Account.nickname = 'Leese'
你會發現什麼都沒有發生……
固然,還要注意的是,若是某個實例已經改過類變量了,那麼贊成更改類變量的時候,這個實例上就不會發生任何事情。好比李四這廝把本身帳戶的銀行名稱私自改爲李四666:
1 john_san = Bank_Account() 2 john_san.name = 'John San' 3 4 wanna_go = Bank_Account() 5 wanna_go.name = 'Wanna Go' 6 7 leese = Bank_Account() 8 leese.name = 'Leese' 9 leese.bank_name = 'Leese 666' 10 11 print(john_san.bank_name) 12 print(wanna_go.bank_name) 13 print(leese.bank_name)
返回結果:
Bank of XXX
Bank of XXX
Leese 666
如今銀行更名字了:
1 Bank_Account.bank_name = 'Bank of OOO' 2 3 print(john_san.bank_name) 4 print(wanna_go.bank_name) 5 print(leese.bank_name) # 神馬都沒發生
返回結果:
Bank of OOO
Bank of OOO
Leese 666
你看,李四帳戶的銀行名稱並無發生變化,李四真的6。
P7 setattr與getattr
這一段可能提及來就沒那麼通俗了,正常狀況下,咱們在類內能夠用賦值語句直接指定變量的值,能夠方便的更改和查看變量。那麼若是如今我有一個循環,生成了12個變量須要儲存,要怎麼辦呢?我一個個手寫?若是是2000個變量呢?那豈不是手寫到天明……
咱們可能會想到用循環,那麼怎麼在類內循環調用不一樣名字的變量呢?eval()是行不通的,咱們須要使用setattr和getattr(attr就是attribute的意思)。
舉個例子,在下面這個類中,咱們初始化了12個實例變量,表明12個月,它們的名字是mon_1, mon_2, ... , mon_12,咱們將它們的值對應設成1~12。
1 class Months: 2 def __init__(self): 3 for i in range(12): 4 setattr(self, 'mon_%i'%(i+1), i+1) 5 6 def print_months(self): 7 for i in range(12): 8 print(getattr(self, 'mon_%i'%(i+1)))
咱們試試:
1 mm = Months() 2 mm.print_months()
返回結果:
1 2 3 4 5 6 7 8 9 10 11 12
這樣咱們就能經過循環和%佔位符生成、修改與查看多個變量了。咱們經過setattr來修改這些變量,而用getattr來查看這些變量。其中setattr有3個參數,第一個通常就是self,表示實例自己,第二個是個字符串,表明變量的名字,第三個表明設定的值。舉例來講:
1 setattr(self, 'new_1', 3)
其實等價於:
1 self.new_1 = 3
而getattr有2個參數,第一個通常就是self,表示實例自己,第二個是個字符串,表明變量的名字。
1 getattr(self, 'new_1')
其實等價於:
1 self.new_1
實際應用過程當中,咱們可能在k折中訓練了k個模型,咱們想暫時保存起來,以後預測的時候再調用。咱們能夠經過循環和setattr來將模型保存在實例變量中,而後在須要的時候經過getattr來調用這些模型。
P8 類的優點
假設如今咱們有四個客戶,張3、李4、王二狗、狗剩,他們各開設一個銀行帳戶,帳戶能記錄他們的名字和帳戶餘額,帳戶有存錢和查詢兩個功能。
若是咱們採用傳統的方法,咱們須要定義2×4共8個變量,每一個人用一個變量表明名字,一個變量表明餘額。咱們還須要一個函數,用來實現存錢。
咱們給這4我的進行開卡、存入一些錢,而後查帳:
1 name_1 = 'John San' 2 name_2 = 'Leese' 3 name_3 = 'Wanna Go' 4 name_4 = 'Go Some' 5 6 balance_1 = 0 7 balance_2 = 0 8 balance_3 = 0 9 balance_4 = 0 10 11 def deposit(balance, amt): 12 # 存錢函數 13 return balance + amt 14 15 balance_1 = deposit(balance_1, 100) # 存錢 16 balance_2 = deposit(balance_2, 200) 17 balance_3 = deposit(balance_3, 50) 18 balance_4 = deposit(balance_4, 300) 19 20 print(balance_1) # 查帳 21 print(balance_2) 22 print(balance_3) 23 print(balance_4)
若是人數不是4個而是40個乃至400個就很容易混淆,戶主名字與帳戶餘額不是綁定的,一不當心可能搞錯了戶主和帳戶間的對應關係。此外帳戶餘額這類信息想改就改,徹底沒有安全性可言。
咱們來看看利用類來實現的寫法:
1 class Bank_Account: 2 def __init__(self): 3 self.name = None # 戶主的名字 4 self.__balance = 0 # 帳戶餘額( 5 6 def deposit(self, amt): 7 # 存錢 8 self.__balance = self.__balance + amt 9 10 def check_balance(self): 11 # 查帳 12 print(self.__balance) 13 14 john_san = Bank_Account() 15 john_san.name = 'John San' 16 john_san.deposit(100) 17 18 leese = Bank_Account() 19 leese.name = 'Leese' 20 leese.deposit(200) 21 22 wanna_go = Bank_Account() 23 wanna_go.name = 'Wanna Go' 24 wanna_go.deposit(50) 25 26 go_some = Bank_Account() 27 go_some.name = 'Go Some' 28 go_some.deposit(300) 29 30 john_san.check_balance() 31 leese.check_balance() 32 wanna_go.check_balance() 33 go_some.check_balance()
不管咱們開了多少個實例,每一個實例的戶主名字與帳戶餘額都是綁定的,不會弄錯,此外還能夠經過私有化達到必定的安全性。
因此不一樣的狀況下,你們能夠根據本身的需求選擇不一樣的實現方法。
以上就差很少是Python類的入門內容了,實際我也暫時只接觸了這麼多,繼承之類的還暫時沒有遇到,但願這篇說的還算容易理解。繼續加油,野路子碼農~