面向对象(Object Oriented Program)
在Python中,所有的数据(包括数字和字符串)实际都是对象,同一类型的对象都有相同的类型。
我们可以使用type()函数来获取关于对象的类型信息。
什么是类,什么是对象?
-
在Python中,使用类来定义同一种类型的对象。
-
类(class)是广义的数据类型,能够定义复杂数据的特性,包括静态特性(即数据抽象)和动态特性(即行为抽象,也就是对数据的操作方法)。
-
一个Python类使用变量存储数据域,定义方法来完成动作。
-
对象是类的一个实例,可以创建一个类的多个对象。
-
创建类的一个实例的过程被称为实例化。在术语中,对象和实例经常是可以互换的。对象就是实例,而实例就是对象。
-
类是对象的抽象,而对象是类的具体实例。
-
类是抽象的,而对象是具体的。
-
每一个对象都是某一个类的实例。
-
每一个类在某一时刻都有零或更多的实例。
-
类是静态的,它们的存在、语义和关系在程序执行前就已经定义好了,对象是动态的,它们在程序执行时可以被创建和删除。
-
类是生成对象的模板。
语法如下,注意缩进
注意析构函数也是方法,是一种特殊的方法class 类名: <类属性> <定义方法一> #注意析构函数也是方法,是一种特殊的方法 <定义方法二>
什么是类属性
其中类属性是在类中方法之外定义的,类属性属于类,可通过类名访问(尽管也可通过对象访问,但不建议这样做)
类属性并不在构造函数中
类属性的修改和增加都是直接通过“类名.属性名”访问。
这就有点不太好了,类属性可以直接在类外面直接修改
类的属性 两种 -> 类属性和实例属性
类的属性有两种:类属性和实例属性。
类属性是在类中方法之外定义的
实例属性是在构造函数__init__
中定义的,定义时以self为前缀,只能通过对象名访问。
数据成员
大致分为两类:
-
属于类的数据成员 -> 类属性
是该类所有对象共享的,不属于任何一个对象,在定义类时这类数据成员一般不在任何一个成员方法的定义中。 -
属于对象的数据成员 -> 实例属性
一般在构造方法__init__()中定义,当然也可以在其他成员方法中定义,在定义和在实例方法中访问数据成员时以self作为前缀,同一个类的不同对象(实例)的数据成员之间互不影响
在主程序中或类的外部,对象数据成员属于实例(对象),只能通过对象名访问;而类数据成员属于类,可以通过类名或对象名访问,但是不建议通过对象名访问。
什么是构造函数
一个类中有一个特殊的方法:__init__
(注意init前后是两个下划线),这个方法被称为构造函数或初始化程序,它是在创建和初始化这个新对象时被调用的。
如果用户未设计构造函数,Python将提供一个默认的构造函数。
构造函数属于对象,每个对象都有自己的构造函数。
构造函数的作用
在内存中为类创建一个对象;
调用类的初始化方法来初始化对象。
在对象被建立之后,self被用来指向对象
方法中的函数定义与普通函数的差别
每个方法的第一个参数都是self,self代表将来要创建的对象本身。
在访问类的实例属性时需要以self为前缀;
方法只能通过对象来调用(意思是不能通过类来调用,和类属性不一样),即向对象发消息请求对象执行某个方法
所以说如果一个对象调用方法后括号内没有参数,意味着在定义此方法的时候只设置了一个参数,那就是self
get()方法和set()方法
为了避免客户端直接修改数据的问题,我们提供get()方法返回值,set()方法设置新值。
通常get()方法被称为获取器或访问器,set()方法被称为设置器或修改器。
============================================
我以为这个get()方法或者set()方法是内置方法,本来就有的,可实际上竟然是自己设置的,果然python有了灵活性,就丧失了严格
# _*_ coding:utf-8 _*_
class Rectangle:
content = "一个矩形"
def __init__ (self,h,w):
self.height = h
self.width = w
def getWidth(self):
return self.width
def getarea(self):
return self.height* self.width
def getPerimeter(self): # 如果说我的方法函数不传入参数,为什么会报错呢?
return ((self.height+self.width)*2)
def setHeight(self,h):
if h > 0:
self.height = h
else :
print("不符合要求")
def setWidth(self,w):
if w > 0:
self.width = w
else:
print("不符合要求")
# 访问类属性
print(Rectangle.content)
# >>> 一个矩形
# 访问示例属性
test1 = Rectangle(2,3)
print(test1.width)
# >>> 3
# 获取器或访问器
test1.getarea()
# >>> 6
# 设置器或修改器
test1.setWidth(4)
print(test1.getWidth())
test1.setHeight(-1)
# >>> 不符合要求
公有属性和私有属性(注意类属性和实例属性的区分)
属性以__
(两个下划线)开头是私有属性,否则是公有属性。
私有属性在类外只能通过对象名._类名__私有成员名
来访问
不能在类外直接通过 对象名.属性名
来访问
私有成员和共有成员
在Python中,以下划线开头的变量名和方法名有特殊的含义,尤其是在类的定义中。
_xxx
:受保护成员;
__xxx__
:系统定义的特殊成员;
__xxx
:私有成员,只有类对象自己能访问,子类对象不能直接访问到这个成员,但在对象外部可以通过对象名._类名__xxx
这样的特殊方式来访问。
注意:Python中不存在严格意义上的私有成员
类的方法的分类
类的方法有3种:
公有方法
私有方法
静态方法
每个对象都有自己的公有方法和私有方法
一旦创建了对象,公有方法就可以通过对象名调用,调用形式形如对象名.公有方法(<实参>)
私有方法只能在属于对象的方法中
通过self调用,不能像公有方法一样通过对象名调用。
这里有一个值得注意的地方,就是私有属性还可以在类外访问,但是私有方法只能在类的内部调用
在静态方法中只能访问属于类的成员(也就是类属性),不能访问属于对象的成员(也就是实例属性),而静态方法也只能通过类名调用。
静态方法
静态方法的定义之前需要添加@staticmethod
静态方法定义时,不需要标识访问对象的self参数,形式上与普通函数的定义类似。
静态方法只能访问属于类的成员,不能访问属于对象的成员。
一个对象的所有实例对象共享静态方法。
使用静态方法时,既可以通过“对象名.静态方法名”来访问,也可以通过“类名.静态方法名”来访问。
类方法
类方法定义之前由@classmethod
语句引导,第一个形参通常被命名为cls。
类方法既可以通过类名,也可以通过对象名来调用。
类方法和静态方法
静态方法和类方法都可以通过类名和对象名调用
不能直接访问属于对象的成员,只能访问属于类的成员。
静态方法和类方法不属于任何实例,不会绑定到任何实例,当然也不依赖于任何实例的状态,与实例方法相比能够减少很多开销。
类方法一般以cls作为类方法的第一个参数表示该类自身,在调用类方法时不需要为该参数传递值,静态方法则可以不接收任何参数。
那话说 类方法和静态方法 的区别是什么呢? --不知道了
百度到的一个答案,先放在这里:
所谓实例方法就是第一个参数是self,
所谓类方法就是第一个参数是class,
而静态方法不需要额外的参数,所以必须区分。
类的析构函数
Python中类的析构函数是__del__,用来释放对象占用的资源。
如果用户未提供析构函数,Python将提供一个默认的析构函数。
析构函数在对象就要被垃圾回收之前调用,但发生调用的具体时间是不可知的,所以建议大家尽力避免使用__del__函数。
__name__属性的作用
- 除了可以在开发环境或命令提示符环境中直接运行,任何Python程序文件都可以作为模块导入并使用其中的对象,这也是实现代码复用的重要形式。
- 每个Python脚本在运行时都会有一个__name__属性,通过Python程序的__name__属性可以识别程序的使用方式
如果脚本作为模块被导入,则其__name__属性值被设置成模块名 - 如果脚本作为程序直接运行,则其 __name__属性值被自动设置为字符串“main”
面向过程编程和面向对象编程
一个例子:假设张三有一笔贷款,年利率为5.75%,贷款年限为30年,贷款金额为35万,计算每月还贷数和总还款数。
在这里,这笔贷款与张三相关联,我们用一条语句borrower="张三"说明这笔贷款的主贷人是张三,也就是说一笔贷款与某个贷款人相关联,如果利用面向过程的方法,就是创建不同的变量来存储贷款人的信息,但这种方法并不理想,因为这些值并不是紧耦合的,最理想的方法是将这些值捆绑在对象中,存储于数据域。
通俗来讲,
紧耦合就是模块或系统之间关系太紧密,
当发生变化时,某一部分的调整会随着各种紧耦合的关系引起其他部分甚至整个应用程序的更改。
松耦合就是模块或系统之间仅通过数据接口联系。
当某个服务的内部结构和实现逐渐发生改变时,
不影响其他服务。
大型系统要求系统之间松藕合,
便于实现功能的任意组合和重用。
- 基于对象概念来分析问题和设计解题方法就是面向对象编程,它是将数据和方法一起合并到对象中。
- 面向过程方法的重点在设计函数上,面向对象设计的重点在对象和对象的操作上。
类与对象的动态性、混入机制
在Python中比较特殊的是,可以动态地为自定义类和对象增加或删除成员,这一点是和很多面向对象程序设计语言不同的,也是Python动态类型特点的一种重要体现。
Python类型的动态性使得我们可以动态为自定义类及其对象增加新的属性和行为,俗称混入(mixin)机制,这在大型项目开发中会非常方便和实用。
例如系统中的所有用户分类非常复杂,不同用户组具有不同的行为和权限,并且可能会经常改变。这时候我们可以独立地定义一些行为,然后根据需要来为不同的用户设置相应的行为能力。
实现混入机制的语法是:
import types #需要导入types模块
# 在类外面定义一个函数,注意参数仍需要self,这个外部定义的函数应该与内部定义的函数一样才行
对象名.函数名 = types.MethodTypes(函数名,对象名) # 动态增加一个新的行为
对象名.函数名()
del 对象名.函数名 #删除用户行为
类的重用方法
类的重用技术通过创建新类来复用已有的代码,而不必从头开始编写,可以使用系统标准类库、开源项目中的类库、自定义类等已经调试好的类,从而降低工作量并减少错误的可能性。
两种重用方法:类的继承与类的组合。
- 类的继承是指在现有类的基础上创建新类,在新类中添加代码,以扩展原有类的属性(数据成员)和方法(成员函数)。
- 类的组合是指在新创建的类中包含已有类的对象作为其属性。
类的继承
继承的出发点在于一些类存在相似点,这些相似点可以被提取出来构成一个基类,基类中的代码通过继承可以在其它类中重用。
继承是在一个被作为父类(或称为基类)的基础上扩展新的属性和方法来实现的。
父类定义了公共的属性和方法,继承父类的子类自动具备父类中的非私有属性和非私有方法,不需要重新定义父类中的非私有内容,并且可以增加新的属性和方法。
在Python语言中,object类是所有类的最终父类,所有类最顶层的根都是object类。
在程序中创建一个类时,除非明确指定父类,否则默认从Python的根类object继承。
Python与C++一样支持多继承。也就是说Python中的一个类可以有多个父类,同时从多个父类中继承所有特性。
语法
class ChildClassName(ParentClassName1[,ParentClassName2[,ParentClassName3, …]]):
#类体或pass语句
子类ChildClassName从圆括号中的父类派生,继承父类的非私有属性和非私有方法。
如果圆括号中没有内容,则表示从object类派生。
如果只是给出一个定义,尚没有定义类体时,使用pass语句代替类体。
子类调用父类的构造方法:
有两种方法调用父类的构造方法:
1. 父类名.__init__(self, 其它参数)
2. super(本子类名, self).__init__(其它参数) #注意,这里的其它参数是指构造方法定义时列出的除self外的参数。
父类与子类 ->这名字起的很有意思,子类只能继承这个父类公有的东西,而它所有的子类都继承了父类的特征(属性或方法),和现实非常对应
父类是指被直接或间接继承的类。Python中类object是所有类的直接或间接父类。
在继承关系中,继承者是被继承者的子类。子类继承所有祖先的非私有属性和非私有方法,子类也可以增加新的属性和方法,子类也可以通过重定义来覆盖从父类继承而来的方法。
在继承关系中,子类和父类是一种“is-a”的关系,这种关系可以作为判断继承关系的一个基准。
类Product是一个父类,具备Computer、MobilePhone等类的共同特征。
Computer、MobilePhone都是类Product的子类,它们共同继承了Product的特征。
子类继承父类的属性
- 子类继承父类中的非私有属性,但不能继承父类的私有属性。
- 子类通过父类中的公有方法访问父类中的私有属性。
- 父类与子类如果同时定义了名称相同的属性名称,父类中的属性在子类中将被覆盖
子类继承父类的方法
- 子类继承父类中的非私有方法,不能继承私有方法
- 当子类中定义了与父类中同名的方法时,子类中的方法将覆盖父类中的同名方法,也就是重写了父类中的同名方法。
- 如果需要在子类中调用父类中同名的方法,可以采用如下格式:
super(子类类名, self).方法名称(参数)
继承关系下的构造方法
在Python的继承关系中,如果子类的构造方法没有覆盖父类的构造方法__init__(),则在创建子类对象时,默认执行父类的构造方法。
当子类中的构造方法__init__()覆盖了父类中的构造方法时,创建子类对象时,执行子类中的构造方法,不会自动调用父类中的构造方法。
子类的构造方法可以调用父类的构造方法。如果需要调用父类的构造方法,必须在子类的构造方法中明确写出调用语句。
所谓 一切皆对象 ->object 类是所有类的最终父类
在Python语言中,object类是所有类的最终父类,所有类最顶层的根都是object类。
在程序中创建一个类时,除非明确指定父类,否则默认从Python的根类object继承。
所以在定义类的时候,下面是等价的,所有自定义的类都是object类的一个实例,
class Person(object): # 所有自定义的类其实是来自object类的一个实例
pass
calss Person:
pass
类定义本身也是个实例(继承自object的为type实例,裸的为classobj实例),因此一个“静态方法”其实只不过是类定义对象的成员罢了……
c++的static方法则是正儿八经的静态方法,因为类定义本身不是个实例。
python里,除了极少的几个语句,其余全都是对象实例。一个函数同样是对象实例,因此对python而言其实连方法这一说都不准确…那些个对象方法只不过是指向函数对象的引用,只是属性而已……