类是一种数据结构,可以包含数据成员和函数成员。程序中可以定义类,并创建和使用其对象实例。
面向对象概念
面向对象的程序设计具有三个基本特征:封装、继承、多态,可以大大的增加程序的可靠性、代码的可重用性和程序的可维护性,从而提高程序开发效率。
对象的定义
所谓对象,从概念层面讲,就是某种事物的抽象(功能)。抽象原则包括数据抽象和过程抽象两个方面:数据抽象就是定义对象属性;过程抽象就是定义对象操作。
面向对象的程序设计强调把数据(属性)和操作(服务)结合为以一个不可分的系统单位(即对象),对象的外部只需要知道它做什么,而不必知道它如何做
从规格层面讲,对象是一系列可以被其他对象使用的公共接口(对象交互)。从语言实现层面来看,对象封装了数据和代码(数据和程序)
封装
封装是面向对象的主要特性。所谓封装,也就是把客观事物抽象并封装成对象,即将数据成员,属性,方法和事件等集合在一个整体内。通过访问控制,还可以隐藏内部成员,只允许可信对象访问或操作自己的部分数据或方法。
封装保证了对象的独立性,可以防止外部程序破坏对象的内部数据,同时便于程序的维护和修改
继承
继承是面向对象的程序设计中代码重用的主要方法。继承是允许使用现有类的功能,并在无须重新改写原来的类的情况下,对这些功能进行扩展。继承可以避免代码复制和相关代码维护问题
多态性
派生类具有基类的所有非私有数据和行为以及新类自己定义的所有其他数据或行为,即子类具有两个有效类型:子类的类型及继承的基类的类型。
对象可以表示多个类型的能力称为多态
多态性允许每个对象以自己的方式去响应共同消息,从而允许用户以更明确的方式建立通用软件,提高软件开发的可维护性。
类对象和实例对象
类是一个数据结构,类定义数据类型的数据(属性)和行为(方法)。对象是类的具体实体,也可以称为类的实例。
在Python语言中,类称为类对象:类的实例称为实例对象
类对象
类使用关键字
class
声明。类的声明格式如下:
class 类名:
类体
其中,类名为有效的标识符,命名规则一般为多少个单词组成的名称,每个单词除第一个单词大写字母,其余全是小写,类体由缩进组成
定义在类体内的元素都是类的成员。类的主要成员包括两种类型,即描述状态的数据成员(属性)和描述操作的函数成员(方法)
实例对象
类是抽象的,要使用类定义的功能,就必须实例化类,即创建类的对象。创建对象后,可以使用
.
运算符来调用其成员注意:创建类的对象、创建类的实例、实例化类等说法是等价的,都说明以类为模版生成了一个对象的操作
anObject = 类名(参数列表)
anObject.对象函数 或 anObject.对象属性
属性
类的数据是在类中定义的成员变量(域),用来存储描述类的值,称为属性。属性可以被该类中定义的方法访问,也可以通过类对象或实例对象进行访问。而在函数体或代码块中定义的局部变量,则只能在其定义的范围内进行访问。
属性实际上是类中的变量。Python变量不需要声明,可直接使用。建议在类定义的开始位置初始化类属性,或者在构造函数
(__int__)
中初始化实例属性
实例属性
通过
self.变量名
定义的属性,称为实例属性
,也称为实例变量
。类的每个实例都包含该类的实力变量的一个单独副本,实例变量属于特定的实例。实例变量在类的内部通过self
访问,在外部通过对象实例访问。实例属性一般在
__int__
方法中通过如下形式初始化:self.实例变量名=初始值
#在实例函数中,通过self访问
self.实例变量名=值 #写入
self.实例变量名 #读取
#创建对象实例后,通过对象实例访问
obj1=类名() #创建对象实例
obj1.实例变量名=值 #写入
obj1。实例变量名 #读取
类属性
Python也允许声明属于类对象本身的变量,即类属性,也称为类变量、静态属性、类属性属于整个类,不是特定实例的一部分,而是所有实例之间共享的一个副本
#类属性一般在类体中通过如下形式初始化
类变量名=初始值
类名.类变量名=值 #写入
类名.类变量名 #读取
类属性如果通过
obj.属性名
来访问,则属于该实例的实力属性,虽然类属性可以使用对象实例来访问,但这样容易造成困惑。所以不建议不要这样使用,而是应该使用标准的访问方式:类名.类变量名
私有属性和公有属性
Python类的成员没有访问控制限制,这与其他面向对象的与语言不同。
通常约定
两个下划线开头的,但是不以两个下划线结束的
属性是私有的(private)
,其他为公共的(public)
。不能直接访问私有属性,但可以在方法中访问。
@property装饰器
面向对象编程的封装性原则要求不直接访问类中的数据成员。Python中可以通过定义私有属性,然后定义相应的访问改私有属性的函数,并使用
@property
装饰器装饰这些函数,程序可以把函数’当作’属性来访问,从而提供更加友好的访问方式@property装饰器默认提供一个只读属性,如果需要,可以使用对应的
getter
、setter
和deleter
装饰器实现其他访问器函数。
property
的调用格式为:property(fget=None,fset=None,fdel=None,doc=None)
其中,fget
为``get访问器;fset
为set
访问器;fdel
为del
访问器
特殊属性
Python对象中包含许多以双下划线开始和结束方法,称为特殊属性。常用的特殊属性如下图:
自定义属性
Python中可以赋予一个对象自定义的属性,即类定义中不存在的属性。对象通过特殊属性
__dict__
存储自定义属性。
>>> class C1:pass
...
>>> o = C1()
>>> = 'custom name'
>>>
'custom name'
>>> o.__dict__
{'name': 'custom name'}
>>>
通过重载
__getattr__
和__setattr__
,可以拦截对成员的访问,从而自定义属性的行为。__getattr__
只有在访问不存在的成员时才会被调用,__getattribute__
拦截所有(包括不存在的成员)的获取操作。在__getattribute__
中不要使用return self.__dict__[name]
来返回结果,因为在访问self.__dict__
时同样会被__getattribute__
拦截,从而造成无限递归形成死循环
。
__getattr__(self,name) #获取属性,比__getattribute__优先调用
__getattribute__(self,name) #获取属性
__setattr__(self,name,value) #设置属性
__delattr__(self,name) #删除属性
方法
实例方法
方法是与类相关的函数,类方法的定义与普通的函数一致。
一般情况下,
类方法的第一个参数一般为self
,这种方法称为实例方法
。实例方法对类的某个给定的实例进行操作,可以通过self
显示地访问该实例。实例方法的声明格式如下:
def 方法名(self,[形参列表]):
函数体
#方法调用格式如下:
对象.方法名([实参列表])
虽然类方法的第一个参数为
self
,但调用时,用户不需要也不能给该参数传值。事实上,Python自动把对象实例传递给该参数。
#假设声明了一个类MyCalss和类方法my_func(self,p1,p2),则:
obj1 = MyClass #创建MyClass的对象实例obj1
obj1.my_func(p1,p2) #调用对象obj1的方法
调用对象
obj1
的方法obj1.my_func(p1,p2)
,Python自动转换为:obj.my_func(obj1,p1,p2)
,即自动把对象实例obj1
传值给self
参数注意:Python中的
self
等价于C++
中的self
指针和Java
、C#
中的this
关键字。虽然没有限制第一个参数名必须为self
,但建议读者遵循惯例,这样便于阅读和理解,且集成开发环境IDE
也会提供相应的支持
静态方法
Python也允许声明属于与类的对象实例无关的方法,称为
静态方法
。静态方法不对特定实例进行操作,在静态方法中访问对象实例会导致错误。静态方法通过装饰器@staticmethod
来定义,其声明格式如下:
@staticmethod
def 静态方法名([形参列表]):
函数体
静态方法一般通过类名来访问,也可以通过对象实例来调用。其调用格式如下:
类名.静态方法([实参列表])
类方法
Python也允许声明属于类本身的方法,即类方法。类方法不对特定的实例进行操作,在类方法中访问对象实例属性会导致错误。类方法通过
@staticmethod
来定义,第一个形式参数必须为对象本身,通常为cls
,类方法的声明格式如下:
@classmethod
def 类方法名(cls,[形参列表]):
函数体
类方法一般通过类名来访问,也可以通过对象实例来调用。其调用格式如下:
类名.类方法名([实参列表])
值得注意的是,虽然类方法的第一个参数为cls
,但调用时,用户不需要也不能给该参数传值。事实上,Python自动把类类类对象传递给参数,类对象与类的实例对象不同,在Python中,类本身也是对象。调用子类继承父类方法时,传入cls
是子类对象,而非类对象。
__init__方法(构造函数)和__new__方法
Python类体中,可以定义特殊的方法:
__new__
方法__init__
方法。__new__
方法是一个类方法,创建对象时调用,返回当前对象的一个实例,一般无须重载该方法__init__
方法即构造函数(构造方法),用于执行类的实例的初始化工作。创建完对象后调用,初始化当前对象的实例,无返回值
__del__方法(析构函数)
Python类体中,可以定义一个特殊的方法:__del__方法。
__del__
方法即析构函数(析构方法),用于实现销毁类的实例所需的操作,如释放对象占用的非托管资源(例如:打开的文件、网络连接等)
默认情况下。当对象不再被使用时,__del__
方法运行,由于Python解释器实现自动垃圾回收,即无法保证这个方法究竟在什么时候运行
通过del
语句,可以强制销毁一个对象实例,从而保证调用对象实例的__del__
方法
私有方法与公有方法
与私有属性类似,Python约定两个下划线开头,但不以两个下划线结束的方法是私有方的
(private)
,与其他为公有的(public)
。以下划线开始和结束的是Python专有的特殊方法。不能直接访问私有方法,但可以在其他方法中访问。
方法重载
在其他程序设计语言中,方法可以重载,即可以定义多个重名方法,只要保证方法签名是唯一的。方法签名包括三个部分:
方法名
、参数数量
、参数类型
但Python本身是动态语言,方法的参数没有声明类型(调用传值时确定参数的类型),参数的数量由可选参数和可变参数来控制。故Python对象方法不需要重载,定义一个方法既可以实现多种调用,从而实现相当于其他程序设计的预言重载功能。
在Python类体中,可以定义多个重名的方法,虽然不会报错,但只有最后一个方法有效,所以建议不要定义重名的方法
继承
Python支持多重继承,即一个派生类可以继承多个基类。派生类声明格式如下:
class 派生类(基类,[基类2,....]):
类体
其中,派生类名后为所有基类的名称元组,如果在类定义中没有指定基类,则默认其基类为
object
。object
是所有对象的根基类,定义了公用方法的默认实现,如__new__()
class Foo: pass
= class Foo(object): pass
声明派生类时,必须在其构造函数中调用基类的构造函数,调用格式如下:
基类名.__init__(self,参数列表)
查看继承的层次关系
多个类的继承可以形成层次关系,通过类的方法
mro()
或类的属性__mro__
可以输出其继承的层次关系
>>> class A:pass
...
>>> class B(A):pass
...
>>> class C(B):pass
...
>>> class D(A):pass
...
>>> class E(B,D):pass
...
>>> D.mro()
[<class '__main__.D'>, <class '__main__.A'>, <class 'object'>]
>>> E.__mro__
(<class '__main__.E'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)
>>>
类成员的继承和重写
通过继承,派生类继承基类中除构造方法之外的所有成员。如果在派生类重新定义从基类继承的方法,则派生类中定义的方法覆盖从基类中继承的方法
对象的特殊方法
对象的特殊方法概述
Python对象中包含许多以双下划线开始和结束的方法,称为特殊方法。特殊方法通常在针对对象的某种操作时自动调用
运算符重载与对象的特殊方法
Python的运算符实际上是通过调用对象的特殊方法实现的
>>> x=12;y=23
>>> x+y
35
>>> x.__add__(y)
35
>>>
在Python类体中,通过重写各运算符对应的特殊方法,即可以实现运算符的重载
@functools.total_ordering装饰器
支持大小比较的对象需要实现特殊方法:
__eq__
、__lt__
、__le__
、__ge__
、__gt__
。使用functools
模块的total_ordering
装饰器装饰类,则只需要实现__eq__
,以及__lt__
、__le__
、__ge__
、__gt__
中的任意一个。total__ordering
装饰器实现其他比较运算,以简化代码量
__call__方法和可调用对象
Python类体中可以定义一个特殊方法:
__call__
方法。定义了__cal___
方法的对象称为可调用对象(callabe),即该对象可以像函数一样被调用。
对象的引用、浅拷贝和深拷贝
对象的引用
对象的赋值实际上是对象的引用,创建一个对象并把它赋值给一个变量,该变量是指向该对象的引用,其
id()
返回值保持一致。
对象的浅拷贝
对象的赋值引用同一个对象,即拷贝对象。如果要拷贝对象,可以使用下列方法之一:
- 切片操作
- 对象实例化
- copy模块的copy函数
Python拷贝一般是浅拷贝,即拷贝对象时,对象中包含的子对象并不拷贝,而是引用同一个子对象。
对象的深拷贝
如果要递归拷贝对象中包含的子对象,可以使用
copy
模块的deepcopy
函数