面向对象是Python的核心概念,一开始在这些概念问题上一直绕不清,这里做个简单记录。
1. 面向对象编程
使用计算机语言编写代码时,有两种思路分别是面向过程编程和面向对象编程
- 面向过程:根据业务逻辑从上到下,直接分析解决问题的步骤,调用函数实现。强调怎么去做
- 面向对象:将问题分解成若干“对象”,建立对象是为了描述某个事物在解决问题过程中的行为。强调谁去做
面向过程注重步骤和过程,所有步骤从到到尾逐步实现,将功能独立的代码封装成函数,最后完成代码就是按照顺序地调用不同函数。
面向对象注重对象和职责,确认职责,根据职责确定不同对象,对象内部封装不同的方法,最后完成代码是按照顺序让不同对象调用不同方法。
python是面向对象编程思想的一门语言,包括做机器学习或者深度学习用的PyTorch、TensorFlow都是面向对象的思想,里面封装了非常多的方法,我们甚至可以不知道方法具体实现的过程和原理,直接调用函数就可以(初学的我就是一开始依葫芦画瓢,程序能跑通但是不能解释实现的原理),对于小白的入门学习确实提供了极大便利(然后一出问题就开始恶补基础了)。
2. 概念性名词
先要了解概念性的专业名词,再通过代码的方式加深自己的理解。
面向对象有三个特性,封装性、继承性和多态性(下一篇博客再细说)。
- 封装性:把属性和方法放在一个类里面,可以通过访问类的权限属性区分开,不想释放的功能搞成私有机制
- 继承性:把实现好的代码和方法通过继承的方法拿过来用,节省代码量
- 多态性:同一个方法用不同的方式去实现,体现的多态性
先解释一下上面提到的几个专有名词:
对象(object):python中一切皆对象,对应现实生活中,任何事物都可以称为对象,有自己独特的特征。对象是通过类创建出的真实的个体(对象是类的实例化),对象由属性和方法组成。
类(class):具有同种属性的对象,现实世界中具有共同特征的事物为一类,比如人类,植物类等,描述的是所有对象的共有特征。拥有相似属性和行为的对象都可以抽象出一个类。
属性(attribute):属于对象静态的一面,描述对象的一些静态特征,比如小明的身高、体重、年龄等。
方法(method):属于对象动态的一面,描述对象的动态特征,比如小明会说话,会码代码等。
实例化:对象由一个别名叫“实例”,通过类创建对象的过程为“实例化”。
抽象:由相同特征的对象抽取共同特征的过程为“抽象”。
3. 代码方式理解类和对象
开头的class来创建一个新的类,class之后为类的名称(通常首字母大写)并以冒号结尾。
1 | # 定义类 |
在创建的类中定义方法,而类中的方法和普通的函数有一个区别——必须有一个额外的第一个参数名称, 按照惯例是 self。self指的是实例的本身,指向当前创建对象的内存地址。某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以我们只需要传递后面的参数。
python是没有方法的重载的,如果定义了多个重名的方法,只会生效最后一个!
在上面的例子里我给Phantom添加了两个对象属性:name和age,但是如果再实例化一个其他对象,能否在创建时就给予属性而不用重新添加呢?答案是肯定的,这个时候我们可以用__init__()函数来定义属性的默认值。
1 | class People: |
可以看到上面创建对象Phantom后,我没有传入参数,python解释器立刻调用了__init__()函数给与了两个属性sex和age,这个时候再调用类内的方法getPeopleInfo(),就会将属性的默认值作为实参传入。
__init__(self)中只有一个默认参数self,如果创建对象传入了两个实参,那么除了self以外还需要两个形参,比如__init__(self, sex, age)这个和自定义创建的类方法不一样,一定要做区分,后面会说到。这里的self是不需要我们传递的,python解释器会自动把当前对象的引用传递进去。
4. 代码方式理解属性和方法
4.1 类属性
类拥有的属性分为公有属性(public)和私有属性(private),python对于类的属性没有严格的访问控制限制,这与其他面向对象语言有所区别。
_xxx 保护属性,python编辑器不会做任何处理,是给程序员看的,不希望被外部访问
xxx 自己定义的公有属性
__xxx 类中的私有属性,不能从外部直接访问,但是可以通过 实例._类名__私有属性 的方式访问
再次强调,python不存在严格意义上的私有属性。
1 | # 创建类,object是对象,可以省略 |
4.2 实例属性
实例属性是从属于实例对象的属性。
- 实例属性可以在__init__()方法中通过 self.实例属性名 = 初始值 的方式进行定义
- 实例属性可以修改、新增和删除,不会影响到类属性
1 | class People(object): |
可以看到通过一个实例对象去引用修改,只是修改了实例属性而不会影响到类属性。
4.3 特殊属性
Python 对象中包含了很多双下划线开始和结束的属性,这些是特殊属性,有特殊的用法。
特殊方法 | 含义 |
---|---|
obj.__dict__ | 对象的属性字典 |
obj.__class__ | 对象所属的类 |
class.__bases__ | 类的基类元组(多继承) |
class.__base__ | 类的基类 |
class.__mro__ | 类层次结构 |
class.__subclasses__ | 子类列表 |
实际操作运行几个简单的例子:
1 | class People: |
4.4 实例方法和类方法
在类中以def开头定义的方法都是实例方法,实例方法的特点是必须有一个以上的参数(self),用于指定这个方法的实例对象。
类方法也是最少需要一个参数(cls),是类对象有的方法,需要使用装饰器@classmethod来标识其为类方法,关于装饰器的概念我后面再写一篇博客,这里简单按照字面意思理解一下。类方法可以通过实例对象或者类对象去访问,有一个用途就是通过实例调用类方法实现对类属性的修改。
1 | class People(): |
4.5 静态方法
python是动态的语言,我们可以动态地为类添加新的方法,或者动态地修改已有的方法。静态方法可以理解为不变的方法,不依赖于实例对象也不依赖于类对象,因此无论是实例对象还是类对象都可以调用。如果有一个功能实现的方法比较独立,可以考虑用静态方法来实现,静态方法需要使用装饰器@staticmethod来标识。
需要注意的是,静态方法无法使用实例的属性和方法。
1 | class People(): |
4.6 特殊方法
前面的普通方法都是通过 对象名.方法名() 的方式调用,和前面有特殊属性一样,python也有一些特殊方法(或者叫魔术方法),这些特殊方法在符合条件的时候自动触发,不需要调用。
因为特殊方法非常多,这里只简单记录一些常用的。
特殊方法 | 含义 |
---|---|
构造类 | |
__new__(cls, […]) | 对象实例化时调用的第一个方法,第一个参数时类,其他参数传递给__init__(),决定是否使用 |
__init__(self, […]) | 构造器,当一个实例被创建时调用的初始化方法 |
__del__(self) | 构造器,当实例对象被销毁时调用的方法 |
表示类 | |
__str__(self) | 描述类或对象信息,比如打印实例化对象,返回定义内容(给人看) |
__repr__(self) | 描述类或对象信息,比如打印实例化对象,返回定义内容(给解释器看) |
访问控制类 | |
__setattr__(self, key, value) | 定义当一个属性被设置时的行为 |
__getattr__(self, key) | 定义用户试图获取一个不存在的属性时的行为 |
__delattr__(self, key) | 定义当一个属性被删除时的行为 |
__getattribute__(self, key) | 定义当该类属性被访问时的行为(所有属性/方法调用都要经过这里) |
__dir__(self) | 定义当dir()被调用时的行为 |
比较操作类 | |
__eq__(self, other) | 判断两个对象是否相等 |
__ne__(self,other) | 判断两个对象是否不相等 |
__lt__(self, other) | 定义小于号的行为:x < y 调用 x.__lt__(y) |
__gt__(self, other) | 定义大于号的行为:x > y 调用 x.__gt__(y) |
容器类 | |
__setitem__(self, key, value) | 定义设置容器中指定元素的操作,相当于 self[key] = value |
__getitem__(self, key) | 定义获取容器中指定元素的操作 ,相当于 self[key] |
__delitem__(self, key) | 定义删除容器中指定元素的操作 ,相当于 del self[key] |
__len__(self) | 定义当被 len() 调用时的操作,即返回容器中元素个数 |
__iter__(self) | 定义迭代容器中的元素的操作 |
__contains__(self, item) | 定义当使用成员测试运算符(in 或 not in)时的操作 |
__reversed__(self) | 定义当被 reversed() 调用时的操作 |
可调用对象类 | |
__call__(self, [args…]) | 使实例对象以 对象名() 的形式使用 |
这些特殊方法比较常用,看到知道是怎么一回事就好。容器类的特殊方法稍微解释一下,python中常用字典、元组、列表和字符串作为容器,它们都实现了容器协议,可迭代。最后一个调用对象类特殊方法写个代码描述一下
1 | class Calculate(): |
在上面这个例子中,首先初始化了一个Calculate实例a,调用 __init__() 方法,给与了实例属性x和y以及对应的值。但是对于实例对象a又做了调用 a(111, 222) ,实际上调用的是 __call__() 方法,传入自定义参数实现自己的逻辑,这在类实现一个装饰器的场景中比较常见。