对于学习过C++或者Java等高级语言的人来说,面向对象可能不是那么陌生,但是没有接触过编程语言或只是简单地了解C语言的人可能不是很了解面向对象。C语言是一种面向过程的设计语言,而C++是一门面向对象的编程语言,面向对象的设计核心更注重于人类的思维,即我们有必要了解下这一编程范式。
类(class)
编程的发展已经由简单控制流中按步的指令序列进入到更有组织的方式中,依靠代码块可以形成命名子程序和完成既定的功能。结构化的或过程性编程可以让我们把程序组织成逻辑块,以便重复使用或重用。面向对象编程在某种程度上增强了结构化编程,实现了数据与动作的融合:数据层和逻辑层由一个可用以创建这些对象的简单抽象层来描述。面向对象设计提供了建模和解决现实世界问题和情形的途径。
类是现实世界的抽象的实体以编程形式出现。类和实例相互关联,类是对象的定义,实例是这些对象的具体化。例如:猫、白猫和黑猫的关系,猫-类,白猫或黑猫-实例。
类的创建
Python中使用class关键字来创建类:
calss ClassName(base): # 新式类
'class documentation string' # 类文档字符串
class_suit # 类体
calss ClassName: # 经典类
'class documentation string' # 类文档字符串
class_suit # 类体
新式类和经典类的唯一区别在于新式类必须继承至少一个父类,参数base可以是单继承也可以是多继承,如果没有继承任何父类,默认为object。
类的实例化
创建一个实例的过程被称为实例化。
class Person(object):
'define Person classic class'
pass
person1 = Person()
实例化类似于函数调用,通常会把这个新建的实例赋值给一个变量,如果我们没有将这个实例保存到一个变量中,它很快会被自动垃圾收集器回收,保存到一个变量相当于为这个实例分配了一块内存。
类属性
属性就是属于另一个对象的数据或者函数元素,可以通过"."属性表示符进行访问。
数据属性
数据属性是所定义的类的变量,像所有的变量一样在类创建后进行访问。这种属性类似于其他面对对象编程语言中的静态变量,不依赖于任何类实例,是与它们所属的类对象绑定的。
>>> class Person(object):
name = 'Joe'
>>> Person.name
'Joe'
实例属性
>>> class Person(object):
def __init__(self, name, phone):
self.name = name
self.phone = phone
print 'Created instance for:', self.name
def UpdataPhone(self, newphone):
self.phone = newphone
peinr 'Updated phone# for:', self.name
>>> Joe = Person('Joe', '400-238-188')
Created instance for: Joe
>>> Joe
<__main__.Person object at 0x02C62B10>
>>> Joe.name
'Joe'
>>> Joe.phone
'400-238-188'
方法属性
在Python的类中所定义的函数被称为方法。方法一般是通过"."标识符和它的实例绑定的。方法的实现途径分为以下三步:
- 定义类
- 创建实例
- 使用实例调用方法。
-
class Person(object):
def init(self, name, phone):
self.name = name
self.phone = phone
print 'Created instance for:', self.name
def UpdataPhone(self, newphone):
self.phone = newphone
peinr 'Updated phone# for:', self.nameJoe = Person('Joe', '400-238-188')
Created instance for: Joe
Joe.UpdataPhone('400-222-123')
Updated phone# for: Joe
Joe.phone
'400-222-123'
你可能注意到在所有的方法中都有self这个参数,但是我们在调用时并没有给他传递任何参数,是因为这个参数代表实例对象本身,当你调用方法时,由解释器悄悄地传递给方法的,相等于其他语言中的"this"。
注意:Python严格要求,没有实例,方法是不能被调用的。
查看类属性
要知道一个类有哪些属性,我们通常通过使用dir()内建函数或者访问类的字典属性__dict__
查看。
>>> dir(Person)
['UpdataPhone', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> print Person.__dict__
{'__module__': '__main__', 'UpdataPhone': <function UpdataPhone at 0x02C64670>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, '__init__': <function __init__ at 0x02C64630>}
dir()返回的是对象属性的一个名字列表,__dict__返回的是一个字典,键是属性名,键值时相应的属性对象的数据值。
-
C.__name__
类C的名字 -
C.__doc__
类C的文档字符串 -
C.__bases__
类C的所有父类构成的元组 -
C.__dict__
类C的属性 -
C.__module__
类C定义所在的模块 -
C.__class__
实例C对应的类 -
Person.name
'Person'
Person.bases
(<type 'object'>,)
Person.module
'main'
Person.class
<type 'type'>
注意:文档字符串不能被派生类继承。
实例属性VS类属性
类属性是与类相关的数据值,与实例无关。静态成员是不会因为实例而改变它们的值。
访问类属性
类属性可以通过类或实例访问的。
>>> class C(object): # 定义类
version = 1.2
>>> c = C() # 实例化
>>> C.version #通过类名来访问
1.2
>>> c.version # 通过实例来访问
1.2
类属性的更新
>>> C.version += 0.1 # 通过类来更新
>>> C.version
1.3
>>> c.version
1.3
>>> c.version += 0.1 # 通过实例来更新
>>> C.version
1.3
>>> c.version
1.4
我们发现,通过类来更新类属性可以影响到类和实例的属性,而通过实例属性只能影响到实例属性,无法改变类属性,这是因为实例属性覆盖了对类属性的访问,而类属性仍在类域中。
>>> del c.version
>>> c.version
1.3
>>> C.version
1.3
我们删除以后,又恢复了之前的状态。如果我们同样创建一个新的实例属性,类属性不变。因为属性已存在于类字典[__dict__]
中,通过赋值,将其加入到实例的__dict__
中。
>>> class C(object):
x = {'Language1': 'C++'}
>>> c = C()
>>> c.x
{'Language1': 'C++'}
>>> c.x['Language2'] = 'Python' 通过实例来更新
>>> c.x
{'Language2': 'Python', 'Language1': 'C++'}
>>> C.x # 生效了
{'Language2': 'Python', 'Language1': 'C++'}
__init__()
构造器
当类被调用时,实例化的第一步是创建实例对象,一旦对象被创建,Python会检查是否实现__init__()
方法,如果没有定义,对实例不会施加任何特别的操作,如果__init__()
被实现,那么它将会被调用,调用类时,传进的参数都会传给它。类似于C++中的构造函数。
>>>Joe = Person('Joe', '400-238-188')
'Joe'和'400-238-188'首先分别会传递给self.name和self.phone
注意:由于实例对象是自动在实例化调用后返回的。__init__()
不应该返回任何对象(应为None)。如果返回非None的任何其他对象都会导致TypeError异常。
__del__()
解构器
同样地,Python中有一个相应的特殊解构器__del__()
。Python具有垃圾对象回收机制,这个函数直到该实例对象的引用都被清除掉后才被调用。类似于C++中的析构函数。
>>> class DelExmple(Person):
def __init__(self):
print 'initialized.'
def __del__(self):
Person.__del__(self)
print 'deleted.'
>>> ex1 = DelExmple()
initialized.
>>> ex2 = ex1
>>> ex3 = ex1
>>> id(ex1), id(ex2), id(ex3) # 确定引用同意对象的三个别名
(46542416, 46542416, 46542416)
>>> del ex1
>>> del ex2
>>> del ex3
deleted.
注意:不要在__del__()
中干与实例无关的事情,尽量不要去显式地使用它。
继承
有时候,我们需要对一个已经定义好的类进行拓展或者修改,而不能影响到系统中使用此类的其他代码段,面向对象设计为我们提供了一个比较好的方法就是派生,派生的关系图类似于树,树的主干就相当于基类,各个分支就相当于子类,一个子类可以继承它的基类的任何属性。
class Parent(object): # 定义父类
'Parent_class documentation string' # 基类字符串文档
def ParentMethod(self):
print 'calling parent method.'
class Child(Parent): # 定义子类
'Child_class documentation string' # 子类字符串文档
def ChildtMethod(self):
print 'calling child method.'
>>> parent = Parent()
>>> parent.ParentMethod()
calling parent method.
>>> child = Child()
>>> child.ChildMethod()
calling child method.
继承覆盖方法
我们可以在子类的代码实现中,编写和基类同名函数以实现对基类的方法的覆盖。
def Parent(object):
def __init__(self):
print "calling Parent's constructor."
def Child(Parent):
def __init__(self):
print "calling Child's constructor."
>>> parent = Parent()
calling Parent's constructor.
>>> child = Child()
calling Child's constructor.
我们可以在子类的实现过程中调用基类的同名函数:
def Parent(object):
def __init__(self):
print "calling Parent's constructor."
def Child(Parent):
def __init__(self):
Parent.__init__(self)
print "calling Child's constructor."
>>> child = Child()
calling Parent's constructor.
calling Child's constructor.
子类的__init__()方法首先调用基类的__init__()方法,然后再执行子类内部的__init__()方法。
还可以通过super()内建函数实现以上功能:
def Parent(object):
def __init__(self):
print "calling Parent's constructor."
def Child(Parent):
def __init__(self):
super(Child, self).__init__()
print "calling Child's constructor."
>>> child = Child()
calling Parent's constructor.
calling Child's constructor.
使用super()函数可以使你无需提供基类,意味着如果你改变了类继承关系,只需修改一行代码而不必去查找修改所有与之相关的类的名字。
多重继承
Python允许子类继承多个基类,即多重继承。
class Parent1(object): # 定义父类1
'Parent_class documentation string' # 基类字符串文档
def ParentMethod1(self):
print 'calling parent method.'
class Parent2(object): # 定义父类2
'Parent_class documentation string' # 基类字符串文档
def ParentMethod2(self):
print 'calling parent method.'
class Child(Parent1, Parent2): # 定义子类
'Child_class documentation string' # 子类字符串文档
def ChildtMethod(self):
print 'calling child method.'
方法解释顺序
在Python2.2以前的版本中采用“深度优先,从左到右搜索”。而在新的版本中(Python2.3以后)使用广度优先的算法。是因为类,类型和内建类型的子类的修改,深度优先的算法不再适合新的数据结构,在多重继承中,新式类使用此算法会引起“菱形效应”。
# 经典类
B C
D
class B:
pass
class C:
def __init__(self):
print 'The default constructor.'
class D(B, D):
pass
>>> d = D()
The default constructor.
# 新式类
A(object)
B C
D
class B(object):
pass
class C(object):
def __init__(self):
print 'The default constructor.'
class D(B, D):
pass
由于在新式类中的类声明加了(object),导致继承结构变成了一个菱形,如果我们使用深度优先的算法,当实例化D时,不再得到C.__init__()
的结果,而是得到object.__init__()
。经典类继续沿用老式算法,新式类使用新式算法。
网友评论