对于面向对象编程,我们首先要知道的是什么是面向对象,然后才能运用面向对象去解决实际的问题。而面向对象是一种抽象化的编程思想,在很多编程语言中都有这个概念。在面向对象的思想中,我们强调万物皆对象,即现实世界中的所有事物都可以被抽象为程序中的对象,从而更好地实现程序的设计和开发。对于以往的函数编程,将一个功能设计为一个函数,需要使用该功能的时候调用函数就完成了,这种方法强调将计算看作是对数学函数的求值,注重函数的组合和复用,由于需要避免副作用和状态的变化,该方法倡导使用不可变数据和纯函数,这对于复杂的程序和功能来说较难实现。而面向对象编程以对象为核心,将数据和操作数据的方法封装在对象中,通过对象之间的交互来实现程序的功能,该方法强调数据和行为的封装,以及继承、多态等概念,且状态是可以改变的。
了解了面向对象之后,我们要知道,面向对象有两个核心的概念,即类和对象,那它们究竟是什么。类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用。而对象是由类创建出来的一个具体的存在,可以直接使用。就概念而言类和对象都是比较抽象的,举例说明一下,你可能就会茅塞顿开,比如以“车”来说,我们所见到的实体的车在建造之间需要有一个图纸,而这个图纸就是一个模版,是负责创建对象的,它目前没有条件让车具备行驶的条件,而要想真正意义的开上一辆车,我们就得用这个模版造一辆车出来,而造出来的车就是一个实例对象。这样说来,就得先有图纸(类),再有车(对象),而类定义了对象的属性和方法。
在使用面向对象进行开发时,应该提前分析具体的需求,确定程序中应该包含哪些类!比如在一个植物大战僵尸的demo中,我们会用到下列这些类。
向日葵 | 豌豆射手 | 冰冻射手 | 普通僵尸 | 铁通僵尸 | 跳跃僵尸 |
---|---|---|---|---|---|
生命值 | 生命值 | 生命值 | 生命值 | 生命值/铁通 | 生命值/竹竿 |
生产阳光() | 发射子弹() | 发射冰子弹() | 咬() | 咬() | 咬() |
在上面的这些类中,为什么有些没有’()‘,而有些有’()'呢?这是因为对于每个类别,它们都具有生命值这个属性,而铁通僵尸和跳跃僵尸分别具有铁通和竹竿,这些都是实体对象的属性,而带有括号的都是它们的行为,也就是方法。而在程序开发中,要设计一个类,通常都需要满足三个要素:
面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法。首先我们先定义一个只包含方法的类。具体如下:
class 类名:
def 方法1(self, 参数列表):
pass
def 方法2(self, 参数列表):
pass
在该类中,方法的定义和函数几乎是一样的,区别在于类中的方法第一个参数必须是self
。
当一个类定义完成之后,要使用类来创建对象,语法格式如下:
对象变量 = 类名()
这样就创建了一个实例对象并且用变量来接收。
现在,我们已经知道了面向对象的基础语法以及用简单类来创建对象,接下来,我们通过实际的案例来熟练掌握。
需求:
分析:
Cat |
---|
eat |
drink |
确定需求并且对功能分析完成之后我们可以进行程序的设计了,具体如下:
# 定义猫类
class Cat:
def eat(self):
print('小猫爱吃鱼')
def drink(self):
print('小猫喝水')
# 创建对象
tom = Cat()
tom.eat()
tom.drink()
print(tom)
在上述程序中,我们首先定义了一个猫类(Cat),然后实例化了一个对象tom
,此处的tom
其实是一个变量,它接收了Cat()
对象的相关信息,程序运行结果如下:
小猫爱吃鱼
小猫喝水
<__main__.Cat object at 0x000001E9CFB02B60>
其中,__main__
表示这个类 Cat
是在当前执行的主模块里定义的, Cat
是类的名字, object at 0x000001E9CFB02B60
表示这个对象在内存中的地址(十六进制),每个对象都有一个唯一的内存地址。
现在,我们知道了创建单个对象的程序是怎么样设计的,但是实际情况当中我们需要创建多个对象,而创建对象是比较简单的操作,在上述程序中只需要再实例化一个猫对象即可:
...
tom = Cat()
# 再实例化一个对象tom2
tom2 = Cat()
这样通过同一个类创建出来的对象tom2
和tom
具有相同的方法,但它们并不是同一只猫,它们的地址是不同的。我们打印引用的地址可以看到:
小猫爱吃鱼
小猫喝水
<__main__.Cat object at 0x0000020C166A2B30>
小猫爱吃鱼
小猫喝水
<__main__.Cat object at 0x0000020C166A2B60>
它们具有相同的方法,但是地址是不一样的。
对于上述创建两个猫对象,我们在定义类的时候指定了它的两个方法,而基于该类创建的两个猫对象并没有属性,而实际上猫既有行为,也有属性,现在我们给这些对象添加属性。
要给python对象设置属性非常的容易,只需要在类的外部通过.
的方式添加属性,但是这种方法不推荐,因为对象的属性应该在定义类的时候封装在类的内部。
tom.name = "汤姆"
...
tom2.name = "汤姆2"
若要利用self
在类封装的方法中输出对象的属性,即使用self
在方法内部输出每一只猫的名字,那么由哪一个对象调用的方法,方法内部的self
就是哪一个对象的引用,在类封装的方法内部self
就是当前调用方法的对象自己,在调用方法时,不需要传递self
参数。在方法内部:
self.
访问对象的属性self.
调用其他对象方法在我们上面的Cat类可以进行如下改动:
class Cat:
def eat(self):
print("%s 爱吃鱼" % self.name)
tom = Cat()
tom.name = "tom"
tom.eat()
tom2= Cat()
tom2.name = "tom2"
tom2.eat()
当然,对于改造后的程序依然有很大的问题,我们可以从程序执行的顺序就可以看到给对象设置属性name
是在调用方法之前,若在调用方法之后设置属性:
class Cat:
def eat(self):
print("%s 爱吃鱼" % self.name)
tom = Cat()
tom.eat()
tom.name = "tom"
那么在程序执行到eat
方法时会抛出异常AttributeError: 'Cat' object has no attribute 'name'
。这是因为我们的对象属性还没有设置就进行调用,这种类的外部设置属性是非常不推荐的,应该将属性和方法一起封装到类的内部。
在类的内部封装属性和方法,我们需要一个初始化方法来设置属性。当我们使用类名来创建对象时,会自动执行以下操作:
init
)这个初始化方法就是对象的内置方法__init__
,该方法是专门用来定义一个类具有哪些属性的方法!
现在我们重新定义一个猫类,查看一下该方法有何作用。
class Cat:
def __init__(self):
print('这是一个初始化方法')
tom = Cat()
创建对象后发现程序有如下输出结果:
这是一个初始化方法
这说明初始化方法在创建对象时会被自动调用。既然在创建对象时会自动1调用类里面的初始化方法,那我们就可以将对象的属性封装在初始化方法中,这样就避免了我们在类的外部进行封装而存在的程序隐患。
在初始化方法中设置属性通过self.属性名 = 属性初始值
即可定义属性,在定义属性之后,在使用类创建的对象就会拥有该属性。在我们的猫类中设置名字属性可以进行如下操作:
class Cat:
def __init__(self):
self.name = 'Tom'
def eat(self):
print(f'{self.name}爱吃鱼')
tom = Cat()
tom.eat()
这样在初始化方法中定义了猫的名字属性,这样我们在创建对象的时候该对象就有了名字,在调用eat
方法时就会打印Tom爱吃鱼
。
现在我们成功在类的内部封装了对象的属性,但很明显这样的写法依然有问题,如果我们不止一只猫,而是两只或者多只,那么我们创建的猫对象都是具有名字属性且值为tom
,但是不可能每一只猫的名字都是一样的。所以我们应该对刚才的属性设置方法进行改动,利用初始化方法的参数来设置属性的初始值,后面每个新创建的对象就重新分配该对象的属性即可。
class Cat:
def __init__(self, name):
self.name = name
def eat(self):
print(f'{self.name} 爱吃鱼')
tom = Cat("汤姆")
tom2 = Cat("汤姆2号")
tom.eat()
tom2.eat()
这样我们就可以在初始化对象的时候设置初始值。
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
01 | __del__ |
方法 | 对象被从内存中销毁前,会被自动调用 |
02 | __str__ |
方法 | 返回对象的描述信息,使用print()打印查看 |
__del__
方法在python中,当我们使用类名()
创建对象时,为对象分配完内存空间之后,自动调用__init__
方法,而当一个对象被从内存中销毁前,会自动调用__del__
方法。
应用场景
__init__
改造初始化方法,可以让创建对象更加灵活__del__
如果希望在对象被销毁前,想再做什么,就可以调用该方法生命周期
类名()
创建,生命周期开始__del__
方法一旦被调用,生命周期结束class Cat:
def __init__(self, name):
self.name = name
print(f'{self.name}来了')
def __del__(self):
print(f'{self.name}去了')
tom = Cat("Tom")
print(tom.name)
输出结果如下:
Tom来了
Tom
Tom去了
可以看到,我们没有主动调用__del__
方法,但是却打印了该方法的内容。
__str__
方法前面我们已经说过,在Python中使用print直接打印对象变量,默认会输出该变量引用的对象是由哪一个类创建的对象,以及该对象在内存中的地址(十六进制表示)。
若是希望使用print输出自定义内容,就可以利用__str__
来进行改造。
需要注意的是,该方法必须返回一个字符串。
class Cat:
def __init__(self, name):
self.name = name
print(f'{self.name}来了')
def __del__(self):
print(f'{self.name}去了')
def __str__(self):
return f'{self.name}'
tom = Cat("Tom")
print(tom)
结果为:
Tom来了
Tom
Tom去了