目 录
面向对象编程 OOP 实现了数据和动作的融合,将现实世界的问题抽象出来。类提高了对象的定义,而实例就是这些定义的实现。OOP 在很多编程语言中都存在,Java 就是非常典型的。在 Python 中也是非常重要的部分。本章先介绍一下 Python 中 OOP 的一些常见术语和特性。
1. 常见术语
1.1 抽象和实现
抽象是指对现实世界问题和实体的本质表现、行为和特征建立模型,建立相关的集合,可以用于描述程序结构,并且实现这种模型,包括了对数据的抽象,还包括了这些数据的接口。
1.2 封装/接口
封装是指对数据/信息进行隐藏,除非程序员运行,否则用户不能直接面对数据并且进行操作。程序会提供接口给用户,用来安全的读取、更新、操作数据。
1.3 合成
合成扩充了类,使得多个不同的类合并成一个大类,来解决现实问题。合成会组成一个复杂的系统,比如一个类是由其他类组成,其中的组件可能是其他的类。这些较小的组件,要么是通过联合关系组在一起,要么是聚合在一起,封装好的组件只能通过定义好的接口来访问。
1.4 派生/继承/继承结构
派生和继承是创建子类时候的两种行为。继承描述了子类属性、方法从父类属性、方法继承而来的这种方式,而派生则是运行修改这些属性、方法的自定义操作。
一个子类,可能有多个父类,也可能父类上面还有祖类,不同父类或者祖类乃至更早的祖先中可能有名字相同的但是定义不同的属性、方法,这时候就要看它的继承结构了,即 MRO,后面会有单独一章讲。
1.5 泛化/特化
泛化表示所有子类与其父类以及祖先类有一样的特点,所以子类和祖先类的关系可以是(is-a)的关系,比如一个工人是一个人,一个农民是一个人,工人、农民都是人这个类的子类。
1.6 多态
之前提到过类的合成、派生、继承等概念,由此可见不同的类或者对象,可能有相同名字的方法。而多态有两种情况。
第一种指的是相同的操作或者函数,过程可用于多种类型对象上并获得不同的结果,例如厨师这个类下面又有子类-面点师、子类-冷盘师傅,他们都有方法名叫“工作”,但是面点师是做面点,冷盘师傅是做冷盘。
第二种指不同的对象,收到统一消息可以产生不同的结果。
1.7 自省/反射
自省表示给与程序员某种能力在代码运行期间获得自身信息、增加、修改、删除相关属性和方法。自省/反射是非常强大而有效的功能,后面会有一章单独说它。
2. 类和实例
2.1 类的创建、定义和声明
类的创建、定义和声明是一起的。
# 用 class 关键字来声明后面是一个类的定义过程
# 类名首字母一般要大写
# 类名后面的 () 可以是空的(经典类)
# 类名后面的 () 也可以是object(新式类)
# 类名后面的 () 还可以是父类名称
可以参看下面的一段代码,我先创建了 Man 类,然后实例化了 allen。
参考代码 1:
# 用 class 关键字来声明后面是一个类的定义过程
# 类名首字母一般要大写
# 类名后面的 () 可以是空的(经典类)
# 类名后面的 () 也可以是object(新式类)
# 类名后面的 () 还可以是父类名称
class Man(object):
'''
This class is used to describle human!
'''
def __init__(self,name,birthYear):
self.name=name
self.birthYear=birthYear
def age(self,currentYear):
self.age=currentYear-self.birthYear
return(self.age)
# 打印出 Man 类的信息
print(Man)
print(type(Man))
# 实例化 Man
allen=Man("allen",1998)
print(type(allen))
# 给类增加一个属性 a
Man.a=10
# 给实例增加一个属性 a
allen.a=20
# 打印 Man 类和 实例 allen 里面的属性
print(Man.a)
print(allen.a)
2.2 实例的创建、初始化、解构
参看 “参考代码 1:”,实例的创建很简单。从代码上我们只能看到一个特殊方法 __init__,看起来是它构造的。实际上它起的作用是初始化,而真正的构造这个实例的是看不见的 __new__ 方法。如果子类没有明确的定义 __new__ 则使用父类中的__new__ ,__init__ 方法也是如此。子类中的 __init__ 还可以被显示修改。
创建实例时候,要将这个实例保存到一个变量中,否则它即使刚刚得到内存,还是马上被释放。创建了一个类,就获得了一个名称空间,就像一个容器对象一样。
其实类还有一个特殊的解构器方法 __del__。由于 Python 具有垃圾对象回收机制(使用引用计数),这个方法要到该实例对象所有的引用都被清除掉才会被执行。这个 __del__ 是在实例释放前提供特殊处理功能的方法,它们通常不会被实现,因为实例很少被显示释放(del 语句 是减少引用次数,不代表调用 __del__)。
该解构器 __del__ 只能被调用一次,并且没有必要不要使用。
2.3 数据属性
2.3.1 类的数据属性
参看 “参考代码 1:”,Man.a 是我在代码运行时候加上的,和实例的 allen.a 虽然名字相同,但是数值是不一样。可以用 “类名.数据名” 方式调用的数据类型,叫做类的数据类型,又叫静态变量或者静态数据,只和类绑定,和任何实例没有关系,即使没有任何实例也可以通过 “类名.数据名” 方式调用。
类的数据属性可以在创建类时候添加,也可以在代码运行后添加。一旦添加后,无论是在添加前就有的子类或者实例,还是后面有的实例都立刻拥有此属性,而不需要额外添加。删除操作也是这么方便快捷。
2.3.2 实例的属性
参看 “参考代码 1:”,实例可以在实例化时候获得类的数据属性,也可以实例化之后自己添加属于自己的实例属性。前者是和类绑定的,后者属于实例,增删改查都不会应该类的数据属性。但是大家容易弄混淆一些情况,我罗列了一些常见情况,供大家参看。
参考代码 2:对于某一个实例而言,如果实例化之前类就有类数据属性了(可以是创建类时候有的,也可以是创建后添加的),那么当这个实例创建时候,就天然的获得这个类的同名数据属性,两者值是相等的。但是对于实例.数据属性进行修改,不会影响类.数据属性的值,两者是独立的。
class Man(object):
'''
This class is used to describle human!
'''
def __init__(self,name,birthYear):
self.name=name
self.birthYear=birthYear
def age(self,currentYear):
self.age=currentYear-self.birthYear
return(self.age)
# 给类增加一个属性 a
Man.a=10
#读类 Man 中是否有 a
try:
print(Man.a)
except AttributeError:
#print(Argument)
print("1. Man do not have a")
else:
print("1. now Man have a")
finally:
print()
# 实例化 Man
allen=Man("allen",1998)
#先读实例 allen 中是否有 a
try:
print(allen.a)
except AttributeError:
print("2. allen do not have a")
else:
print("2. allen have a")
finally:
print()
# 给实例修改一个属性 a
print("修改 allen.a=100 ")
allen.a=20
print()
# 打印 Man 类和 实例 allen 里面的属性
print("Now Man.a is ==>",Man.a)
print("Now allen.a is ==>",allen.a)
# 给类修改属性 a
print("修改 Man.a=100 ")
Man.a=100
print()
# 打印 Man 类和 实例 allen 里面的属性
print("Now Man.a is ==>",Man.a)
print("Now allen.a is ==>",allen.a)
参考代码 3:如果一个实例运行时候添加了一个数据属性,那么和类数据属性没啥关系,即使再给类添加一个同名的数据属性。反过来,代码运行时候给类添加一个数据属性,那么在此之前实例化或者之后实例化的实例,都会天然的得到这个类数据属性。
class Man(object):
'''
This class is used to describle human!
'''
def __init__(self,name,birthYear):
self.name=name
self.birthYear=birthYear
def age(self,currentYear):
self.age=currentYear-self.birthYear
return(self.age)
# 实例化 Man
allen=Man("allen",1998)
#先读实例 allen 中是否有 a
try:
print(allen.a)
except AttributeError:
print("1. allen do not have a")
else:
print("1. allen have a")
finally:
print()
# 给实例增加一个属性 a
print("给实例增加一个属性 a=20")
allen.a=20
#再读实例 allen 中是否有 a
try:
print(allen.a)
except AttributeError:
print("2. allen do not have a")
else:
print("2. now allen have a")
finally:
print()
#再读类 Man 中是否有 a
try:
print(Man.a)
except AttributeError:
#print(Argument)
print("3. Man do not have a")
else:
print("3. now Man have a")
finally:
print()
# 给类增加一个属性 a
print("给类增加一个属性 a=10")
Man.a=10
print("给类增加一个属性 c=99")
Man.c=99
# 打印 Man 类和 实例 allen 里面的属性
print("Now Man.a is ==>",Man.a)
print("Now allen.a is ==>",allen.a)
# 打印 Man 类和 实例 allen 里面的属性
print("Now Man.c is ==>",Man.c)
print("Now allen.c is ==>",allen.c)
2.3.3 特殊的属性
- __name__ 类的名字(字符串)
- __doc__ 类的文档(字符串)
- __bases__ 类的所有父类构成的元组,只有类能看,实例不能看
- __dict__ 类的所有属性
- __module__ 类定义所在的模块
- __class__ 实例对应的类
- __repr__ 显示属性
2.3.4 如何查看类和修改类或者实例的属性
- 查看一个类有那些属性,可以用dir() 函数,或者是访问类的 __dict__。
- Ptyhon 支持动态的修改类属性,因为它是典型的动态语言。编程语言分为静态和动态。静态语言的变量是在内存中的有类型的且不可变化的,除非强制转换它的类型;动态语言的变量是指向内存中的标签或者名称,其类型在代码运行过程中会根据实际的值而定。
- 参看 给Python的类和对象动态增加属性和方法
2.4 方法
2.4.1 类方法
含义和类数据属性类似,只不过换成了方法。常见的类方法,有使用 classmethd 、staticmethod 修饰的方法。请参考 Python 装饰器-中
2.4.2 实例方法
在前面的参考代码1、2、3 三中,都是普通的实例方法,它们的第一个参数是 self ,即自己,一般是用户定义的,和普通的函数写法并无不同,唯一有区别的就是调用方式,即 实例.实例方法名。
2.4.2 特殊的方法
- __delattr__ 清除属性数据、方法
- __dir__ 查看
- __eq__ 是否相等
- __format__ 格式化输出
- __ge__ 是否大于等于
- __getattribute__ 获得属性、方法
- __gt__ 是否大于
- __hash__
- __init__ 初始化方法
- __init_subclass__
- __le__ 是否小于等于
- __lt__ 是否小于
- __ne__
- __new__
- __reduce__
- __reduce_ex__
- __setattr__
- __sizeof__
- __str__
################### 求点赞,求转发,求评论,求收藏!#####################