野路子碼農系列(2)Python中的類,多是最通俗的解說

啥叫佩奇?啥叫類?啥叫面向對象?後面兩個問題之前在大學裏「祖傳譚浩強」的時候我常常會有所疑問。老師說着一堆什麼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類的入門內容了,實際我也暫時只接觸了這麼多,繼承之類的還暫時沒有遇到,但願這篇說的還算容易理解。繼續加油,野路子碼農~

相關文章
相關標籤/搜索