野路子码农系列(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类的入门内容了,实际我也暂时只接触了这么多,继承之类的还暂时没有遇到,但愿这篇说的还算容易理解。继续加油,野路子码农~

相关文章
相关标签/搜索