该系列文章:python
在上一篇文章《python中的数据类型(list,tuple,dict,set,None)》的1.2小节里咱们就简要介绍过对象(object)跟类(class)的概念。也知道了python中内置的全部数据类型都是对象,拥有本身的方法。那么当这些内置的数据类型没法知足咱们的需求时,咱们如何建立咱们本身的类型(type)呢?答案就是经过建立咱们本身的类(class)。经过咱们本身动手实现的类,咱们就能够建立以这个类为模板的对象。从这样的流程来看,面向对象的编程方式是自顶而下,首先须要全盘考虑,才能建立一个足够好的模板,也即类。而后才能将类实例化为对象,经过对象中的属性来解决问题或者与其余对象互动。git
建立一个最简单的类能够经过下面这样的写法:github
class User:
pass
#更多代码
#更多代码
复制代码
上面的代码中class是关键字,代表咱们要建立一个类了,User是咱们要建立的类的名称。经过“:”和缩进来代表全部缩进的代码将会是这个类里的内容。从User类中建立一个该类的实例经过下面的写法:编程
""" 建立一个实例,经过类名加括号的形式,相似调用函数 """
u=User()
复制代码
对象(客体)有本身的特征和本身能够作到的事,对应到程序里就是字段(field) 和方法(method) ,这两个都是对象的属性(attribute) 。对象的字段相似于普通变量,所不一样的是对象的字段是对象独有的。对象的方法相似于普通函数,所不一样的是对象的方法是对象独有的。上篇文章中咱们已经见到过如何使用字段跟方法,那就是经过.
操做符。bash
在类中定义对象的方法(method)比较简单,跟实现普通函数相似,只有一点不一样,那就是无论方法需不须要参数,你都须要把self
做为一个参数名传进去,self
这个参数在咱们调用方法时咱们能够直接忽略,不赋值给它。举个例子:ssh
class User:
def hi(self):
print("hi!")
u=User()
u.hi()
""" 程序输出: hi! """
复制代码
self
这个参数名是约定俗成的。在User
类的代码块里定义hi
方法时,传入的参数self
将会是某个实例(对象)自己。当u
做为User
类的实例被建立,而且经过u.hi()
调用hi
方法时,python解释器会自动将其转换成User.hi(u)
。经过传入实例(对象)自己,也即self
,方法(method)就可以访问实例的字段(filed),并对其进行操做,咱们以后能够重新的例子中看到。函数
要在类中声明对象的字段,有一个特殊的方法(method)能够作到,那就是__init__
方法,这个方法在init
先后都要写上两个下划线__
。__init__
方法会在实例一开始建立的时候就被调用,init
是initialization
的缩写,顾名思义,就是初始化的意思。__init__
方法在建立对象的时候由python解释器自动调用,不须要咱们手动来调用。看个例子:post
class User:
""" 注意self老是在括号里的最左边 """
def __init__(self,name,age):
self.name=name
self.age=age
def hi(self):
print("hi!I'm {}".format(self.name))
u=User("li",32)
u.hi()
print(u.name+","+str(u.age))
""" 程序输出: hi!I'm li li,32 """
复制代码
上面的代码里,在User
类的__init__
方法中self
被传入,那么就能够经过self.name
跟self.age
来声明对象的两个字段,并将传入该方法的参数name
跟age
赋值给它们。当咱们建立类型为User
的对象的时候,传入实际的参数"li"
跟32
,这两个参数被python解释器传入__init__
方法中,"li"
对应name
,32
对应age
,__init__
方法当即被调用,将实例u
的字段一一创建。ui
self.name=name
粗看貌似都是name
变量,但self.name是实例的字段,专属于实例,name
是建立对象时将要传入的一个参数,将它赋值给self.name
是没有歧义的。spa
事实上,字段除了独属于实例以外,跟普通变量没有什么差异,因此实例的字段也被称为实例变量。在类的定义中,与实例变量对应的还有类变量,类变量与实例变量相似,经过.
操做符来访问。类变量是任何实例共享的,能够理解为是该类型所共有的特征,好比,在User
类中,咱们能够计算一共有多少被实例化了的用户:
class User:
""" 计算被实例化的用户数量 """
count=0
def __init__(self,name,age):
"""每次建立一个对象,用户数量在原有基础上加1"""
User.count=User.count+1
self.name=name
self.age=age
def hi(self):
print("hi!I'm {}".format(self.name))
def die(self):
print("I'm {}, dying...".format(self.name))
User.count=User.count-1
del self
@classmethod
def print_count(cls):
print("共有{}名用户".format(cls.count))
u=User("li",32)
User.print_count()
u.hi()
u1=User("ma",30)
u1.__class__.print_count()
u1.hi()
u.die()
User.print_count()
u1.die()
User.print_count()
""" 程序输出: 共有1名用户 hi!I'm li 共有2名用户 hi!I'm ma I'm li, dying... 共有1名用户 I'm ma, dying... 共有0名用户 """
复制代码
上面代码中的count
就是类变量,能够经过User.count
来访问。python经过@classmethod
来代表它下面定义的方法是类的方法(method),类的方法中的cls
是类自己,跟self
使用方法相似,调用类方法时能够直接忽略。类变量跟类的方法(method)均可以被称为类的成员。除了使用相似User.count
这样的方式来访问和使用以外,该类的实例还能够经过__class__
属性来访问和使用类成员,好比上面代码中的u1.__class__.print_count()
。
上面代码中定义的字段跟方法都是公开的,能够经过.
操做符访问。但若是属性是以形如__name
这样以双下划线为开头的名称,则python会自动将名称换成_classname__name
,其中classname
就是类的名称,这样,经过an_object.__name
是访问不到的。
现实世界里,某一类类似客体的类型被抽象为一个概念(名称),同时又有另外一类类似客体的类型被抽象为一个概念(名称),这时候咱们可能会发现,这两个概念(名称)之间很类似,因而咱们把这两个类似概念再抽象成一个概念(名称),这样的过程能够重复屡次。举个例子,咱们有幼儿园,同时有小学,这时候咱们能够把幼儿园跟小学抽象成学校。那跟现实相似,对象的类型跟类型之间,能够抽象成另外一个类型。在文章最开头咱们说面向对象编程是自顶而下,这跟一层层向上抽象的过程正好相反,咱们会在一开始思考如何建立某个类,而后把这个类做为基类(父类) ,更具体的建立一(几)个新类,这一(几)个新类不只拥有基类的属性,还会增长本身独有的属性。咱们把这样的新类(class)叫作子类,是从基类继承而来的。
从1.2小节的代码例子中,咱们建立了一个User
(用户)类,假如咱们如今须要区分免费用户和付费用户,免费用户跟付费用户都具备name
、age
、hi
属性,同时免费用户跟付费用户还具备本身独有的一些属性。若是咱们直接分别建立免费用户类跟付费用户类,那么这两个类之间共同的属性就会被定义两次,须要复制粘贴一样的代码,这样的代码质量不高而且不够简洁。相反,咱们可让免费用户跟付费用户都从User
类继承下来,这样就能够重用(reuse) User
类的代码,而且让代码相对简洁高效一点。还有一个好处就是,当免费用户跟付费用户之间共同的属性有了变化(增减),咱们能够直接修改父类User
,而不用分别修改,当子类的数量不少时,这种好处会显得很是明显。修改父类以后,各子类的独有属性不会受到影响。
1.2小节的代码例子中,咱们对实例的数量进行统计,当子类从User
类继承下来后,在子类实例化对象的时候,父类的实例数量在python中默认也会增多,这说明咱们能够把子类的实例看作是父类的实例,这被称为多态性(polymorphism)。免费用户类跟付费用户类如何从父类继承,以下:
class User:
""" 计算被实例化的用户数量 """
count=0
def __init__(self,name,age):
"""每次建立一个对象,用户数量在原有基础上加1"""
User.count=User.count+1
self.name=name
self.age=age
def hi(self):
print("hi!I'm {}".format(self.name))
def die(self):
print("I'm {}, dying...".format(self.name))
User.count=User.count-1
del self
@classmethod
def print_count(cls):
print("共有{}名用户".format(cls.count))
class Free_user(User):
def __init__(self,name,age,number_of_ads):
User.__init__(self,name,age)
self.number_of_ads=number_of_ads
def hi(self):
User.hi(self)
print("I'm free_user")
class Paying_user(User):
def __init__(self,name,age,plan):
User.__init__(self,name,age)
self.plan=plan
def hi(self):
print("hi!I'm {},paying_user".format(self.name,))
u=Free_user("li",32,5)
User.print_count()
u.hi()
u1=Paying_user("ma",30,"5$")
User.print_count()
u1.hi()
u.die()
User.print_count()
u1.die()
User.print_count()
""" 程序输出: 共有1名用户 hi!I'm li I'm free_user 共有2名用户 hi!I'm ma,paying_user I'm li, dying... 共有1名用户 I'm ma, dying... 共有0名用户 """
复制代码
上面代码中首先建立了User
基类,而后从User
类继承下来两个子类:Free_user
跟Paying_user
。子类要从某个类继承而来,须要在类名后面跟上括号,在括号中填入基类的名称,形如这样:Free_user(User)
。
在子类的__init__
方法中,经过调用基类的__init__
方法把继承自基类的共有字段建立出来,调用的时候将self
跟传入的属于基类部分的参数原样传入,上面代码中将传入的name
跟age
原样传入了基类的__init__
方法中了。由于咱们在子类中定义了__init__
方法,因此python不会自动调用基类的__init__
方法,而须要咱们显式地调用它。若是子类中没有定义__init__
方法,则python会在建立子类的实例时自动调用基类的__init__
方法。这是为何呢?咱们在子类中对基类的hi
方法进行了重写,但对die
方法则没有,可是咱们经过子类建立的实例可以调用die
方法,这说明在调用die
方法时子类的实例被看作了父类的实例了,同时在调用hi
方法时却都调用了重写的子类中的方法了,这说明,当实例调用一个方法时,会首先在实例化本身的类中寻找对该方法的定义,若是没有,则在该类的父类中寻找,若是父类中仍是没有,则会在父类的父类中寻找,这样的过程一直重复,直到再也没有父类了,若是仍是没有该方法,那程序就会报错。
最后,从前一小节咱们知道,以双下划线为前缀的属性名会被python自动替换成另外一个名称,这时候当父类中有个以双下划线为前缀的属性,咱们在子类中也有一个相同的属性的时候,因为python自动替换了这两个属性名称,子类中的方法并无覆盖掉父类中的属性,而只是,子类中的该属性跟父类中的该属性是两个不一样的属性。看例子:
#其余一些属性用“,”省略了
>>> class a:
... def __a_method(self):
... pass
...
>>> dir(a())
['__class__', ,,, '_a__a_method']
>>> class b(a):
... def __a_method(self):
... pass
...
>>> dir(b())
['__class__', ,,, '_a__a_method', '_b__a_method']
>>>
复制代码
能够看到,__a_method
在a
类的实例中被替换成了_a__a_method
,在b
类的实例中被替换成了_b__a_method
,这跟_a_a_method
不一样,b
类的实例中继承了_a_a_method
,同时,还有本身类型的_b_a_method
。
看个例子:
>>> list.__base__
<class 'object'>
>>> type(list)
<class 'type'>
>>> class User:
... pass
...
>>> User.__base__
<class 'object'>
>>> type(list)
<class 'type'>
复制代码
类的属性__base__
能够指明该类是继承自哪一个类。从上面的例子能够看到,python中定义内置数据类型的类(class
)(以list
为例)从object
类继承而来,可是其类型(type)是type
类,咱们本身建立而且没有明确指定从是哪一个类继承而来的类(以User
为例),跟内置类型同样,从object
类继承而来,其类型(type)是type
类。若是咱们本身建立的,而且从层次更低的类(好比User
类)中继承而来,那么该类的__base__
(基类)跟type函数所显示的类型是怎么样的呢,接上面的例子:
>>> Free_user.__base__
<class '__main__.User'>
>>> type(Free_user)
<class 'type'>
>>>
复制代码
能够看到,Free_user
从User
继承而来,但其类型依然是type
,因此全部的类(class)的类型应当都是type
,是否是这样呢,object
会不会例外呢?type
本身呢?它的类型呢?下面的例子验证了它们的类型也是type
:
>>> type(object)
<class 'type'>
>>> type(type)
<class 'type'>
复制代码
从例子中咱们知道了,全部的类(class
)的类型都是type
类,因此,全部的类(class
)都是从type
类中实例化出来的实例,甚至type
类实例化了本身,因此全部的类包括object
和type
都是实例,这说明全部的类都是对象 。为了清晰的指代不一样种类的对象,咱们把自己是类的对象称为类对象,把从类中实例化而来而且自己不是类的对象成为实例对象,实例对象是层次最低的对象。
咱们能够看到object
类跟type
类是python中抽象层次最高的对象了。那么它们两个的关系如何呢?看例子:
>>> type.__base__
<class 'object'>
>>> print(object.__base__)
None
>>>
复制代码
能够看到,type
从object
继承而来,而且咱们已经知道object
的类型是type
。从例子中能够看到object
没有基类,因此若是把类的层层继承想象成一条锁链,那么object
将是继承链上的顶点。前面咱们提到的对象的一些特殊属性如__init__
、__class__
继承自object
,而__base__
这个特殊属性则只有类对象才有,是在type
中定义的。以下:
#其余一些属性用“,”省略了
>>> dir(object)
['__class__', ,,, '__init__', ,,, '__subclasshook__']
>>> dir(type)
['__abstractmethods__', '__base__', '__bases__', ,,, 'mro']
>>>
复制代码
欢迎浏览个人我的博客,https://diwugebingren.github.io