抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

面向对象是Python的核心概念,一开始在这些概念问题上一直绕不清,这里做个简单记录。

1. 面向对象编程

使用计算机语言编写代码时,有两种思路分别是面向过程编程和面向对象编程

  1. 面向过程:根据业务逻辑从上到下,直接分析解决问题的步骤,调用函数实现。强调怎么去做
  2. 面向对象:将问题分解成若干“对象”,建立对象是为了描述某个事物在解决问题过程中的行为。强调谁去做

面向过程注重步骤和过程,所有步骤从到到尾逐步实现,将功能独立的代码封装成函数,最后完成代码就是按照顺序地调用不同函数

面向对象注重对象和职责,确认职责,根据职责确定不同对象,对象内部封装不同的方法,最后完成代码是按照顺序让不同对象调用不同方法

python是面向对象编程思想的一门语言,包括做机器学习或者深度学习用的PyTorch、TensorFlow都是面向对象的思想,里面封装了非常多的方法,我们甚至可以不知道方法具体实现的过程和原理,直接调用函数就可以(初学的我就是一开始依葫芦画瓢,程序能跑通但是不能解释实现的原理),对于小白的入门学习确实提供了极大便利(然后一出问题就开始恶补基础了)

2. 概念性名词

先要了解概念性的专业名词,再通过代码的方式加深自己的理解。

面向对象有三个特性,封装性、继承性和多态性(下一篇博客再细说)。

  1. 封装性:把属性和方法放在一个类里面,可以通过访问类的权限属性区分开,不想释放的功能搞成私有机制
  2. 继承性:把实现好的代码和方法通过继承的方法拿过来用,节省代码量
  3. 多态性:同一个方法用不同的方式去实现,体现的多态性

先解释一下上面提到的几个专有名词:

对象(object):python中一切皆对象,对应现实生活中,任何事物都可以称为对象,有自己独特的特征。对象是通过类创建出的真实的个体(对象是类的实例化),对象由属性和方法组成。

类(class):具有同种属性的对象,现实世界中具有共同特征的事物为一类,比如人类,植物类等,描述的是所有对象的共有特征。拥有相似属性和行为的对象都可以抽象出一个类。

属性(attribute):属于对象静态的一面,描述对象的一些静态特征,比如小明的身高、体重、年龄等。

方法(method):属于对象动态的一面,描述对象的动态特征,比如小明会说话,会码代码等。

实例化:对象由一个别名叫“实例”,通过类创建对象的过程为“实例化”。

抽象:由相同特征的对象抽取共同特征的过程为“抽象”。

3. 代码方式理解类和对象

开头的class来创建一个新的类,class之后为类的名称(通常首字母大写)并以冒号结尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 定义类
class People:
# 定义方法
def getPeopleInfo(self):
print('名字:%s, 年龄:%d' %(self.name, self.age))

# 实例化一个对象
Phantom = People()
Phantom.name = 'Phantom' # 使用 . 的方法添加类属性
Phantom.age = 26
Phantom.getPeopleInfo() # 使用 .函数名() 的方法调用类中创建的函数

print(Phantom.age) # 打印实例Phantom的年龄属性

'''
运行结果:
名字:Phantom, 年龄:26
26
'''

在创建的类中定义方法,而类中的方法和普通的函数有一个区别——必须有一个额外的第一个参数名称, 按照惯例是 self。self指的是实例的本身,指向当前创建对象的内存地址。某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以我们只需要传递后面的参数。

python是没有方法的重载的,如果定义了多个重名的方法,只会生效最后一个!

在上面的例子里我给Phantom添加了两个对象属性:name和age,但是如果再实例化一个其他对象,能否在创建时就给予属性而不用重新添加呢?答案是肯定的,这个时候我们可以用__init__()函数来定义属性的默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class People:
# 初始化函数,使对象的属性具有默认值
def __init__(self, sex = 'male', age = 26):
self.sex = sex
self.age = age
# 定义类方法
def getPeopleInfo(self):
print('性别:%s, 年龄:%d' %(self.sex, self.age))

# 创建对象Phantom,不传参,属性使用默认值
Phantom = People()
print(Phantom.sex, Phantom.age)
Phantom.getPeopleInfo()

# 创建第二个对象Aria,传参,新的参数代替默认值
Aria = People('Female', 24)
print(Aria.sex, Aria.age)
Aria.getPeopleInfo()

'''
运行结果:
male 26
性别:male, 年龄:26
Female 24
性别:Female, 年龄:24
'''

可以看到上面创建对象Phantom后,我没有传入参数,python解释器立刻调用了__init__()函数给与了两个属性sex和age,这个时候再调用类内的方法getPeopleInfo(),就会将属性的默认值作为实参传入。

__init__(self)中只有一个默认参数self,如果创建对象传入了两个实参,那么除了self以外还需要两个形参,比如__init__(self, sex, age)这个和自定义创建的类方法不一样,一定要做区分,后面会说到。这里的self是不需要我们传递的,python解释器会自动把当前对象的引用传递进去。

4. 代码方式理解属性和方法

4.1 类属性

类拥有的属性分为公有属性(public)和私有属性(private),python对于类的属性没有严格的访问控制限制,这与其他面向对象语言有所区别。

  1. _xxx 保护属性,python编辑器不会做任何处理,是给程序员看的,不希望被外部访问

  2. xxx 自己定义的公有属性

  3. __xxx 类中的私有属性,不能从外部直接访问,但是可以通过 实例._类名__私有属性 的方式访问

    再次强调,python不存在严格意义上的私有属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建类,object是对象,可以省略
class People(object):
name = 'Phantom' # 公有的类属性
__age = 26 # 私有的类属性
_sex = 'male' # 保护的类属性
def __init__(self):
pass # 空语句,占位用,不会执行任何操作
p = People()

print(p.name) # 通过实例对象访问公有类属性
print(p._People__age) # 通过实例访问私有类属性
print(p._sex) # 访问保护的类属性(可以访问但是不推荐)

'''
运行结果:
Phantom
26
male
'''

4.2 实例属性

实例属性是从属于实例对象的属性。

  1. 实例属性可以在__init__()方法中通过 self.实例属性名 = 初始值 的方式进行定义
  2. 实例属性可以修改、新增和删除,不会影响到类属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class People(object):
name = 'Phantom'

p = People()
print(p.name, People.name) # 通过实例查看实例属性,通过类对象查看类属性
p.name = 'Aria' # 修改实例属性
print(p.name, People.name)
People.name = 'Aria' # 修改类属性
print(p.name, People.name)

'''
Phantom Phantom
Aria Phantom
Aria Aria
'''

可以看到通过一个实例对象去引用修改,只是修改了实例属性而不会影响到类属性。

4.3 特殊属性

Python 对象中包含了很多双下划线开始和结束的属性,这些是特殊属性,有特殊的用法。

特殊方法 含义
obj.__dict__ 对象的属性字典
obj.__class__ 对象所属的类
class.__bases__ 类的基类元组(多继承)
class.__base__ 类的基类
class.__mro__ 类层次结构
class.__subclasses__ 子类列表

实际操作运行几个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class People:
name = 'Phantom' # 类属性

def __init__(self, sex, age): # 实例属性
self.sex = sex
self.age = age

p = People('male', 26)

# dict 生成类属性信息的字典和实例对象属性信息的字典
print('People类属性为:' + str(People.__dict__))
print('实例对象p属性为:' + str(p.__dict__))

# class 对实例对象查询所属类信息
print('实例对象p所属类信息:' + str(p.__class__))

'''
运行结果:
People类属性为:{'__module__': '__main__', 'name': 'Phantom', '__init__': <function People.__init__ at 0x000001A7D212AB00>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
实例对象p属性为:{'sex': 'male', 'age': 26}
实例对象p所属类信息:<class '__main__.People'>
'''

4.4 实例方法和类方法

在类中以def开头定义的方法都是实例方法,实例方法的特点是必须有一个以上的参数(self),用于指定这个方法的实例对象。

类方法也是最少需要一个参数(cls),是类对象有的方法,需要使用装饰器@classmethod来标识其为类方法,关于装饰器的概念我后面再写一篇博客,这里简单按照字面意思理解一下。类方法可以通过实例对象或者类对象去访问,有一个用途就是通过实例调用类方法实现对类属性的修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class People():
name = 'Phantm'

@classmethod
def getName(cls):
return cls.name
@classmethod
def setName(cls, name):
cls.name = name

p = People()
print(p.getName(), People.getName()) # 通过实例和类对象调用类方法,先查看一下类属性
p.setName('Aria') # 通过实例调用类方法改变类属性
print(p.getName(), People.getName())

'''
运行结果:
Phantm Phantm
Aria Aria
'''

4.5 静态方法

python是动态的语言,我们可以动态地为类添加新的方法,或者动态地修改已有的方法。静态方法可以理解为不变的方法,不依赖于实例对象也不依赖于类对象,因此无论是实例对象还是类对象都可以调用。如果有一个功能实现的方法比较独立,可以考虑用静态方法来实现,静态方法需要使用装饰器@staticmethod来标识

需要注意的是,静态方法无法使用实例的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class People():
name = 'Phantom'
def __init__(self, name = 'Aria'):
self.name = name

@staticmethod
def getName():
print('静态方法调用类属性', People.name)
#print(self.name) #不能调用实例的属性,会报错

p = People()
p.getName()

'''
运行结果:
静态方法调用类属性 Phantom
'''

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Calculate():
def __init__(self, x, y):
self.x = x
self.y = y
def __call__(self, m, n):
self.m = m
self.n = n
SUM = m + n
return SUM

a = Calculate(100, 200)

print(a(111, 222)) # __call__() 将实例化对象a当作一个方法来执行
print(a.x, a.y) # 实例属性并没有改变

'''
运行结果:
333
100 200
'''

在上面这个例子中,首先初始化了一个Calculate实例a,调用 __init__() 方法,给与了实例属性x和y以及对应的值。但是对于实例对象a又做了调用 a(111, 222) ,实际上调用的是 __call__() 方法,传入自定义参数实现自己的逻辑,这在类实现一个装饰器的场景中比较常见。

欢迎小伙伴们留言评论~