在C++面向过程与面向对象的章节里,我们介绍过面向过程关注的是做一件事情的需要的步骤有哪些,通过一系列函数之间的调用配合来实现解决问题,面向对象关注的是解决这一个问题参与的对象,依靠对象之间的交互来完成问题的解决
对于Python或者说很多的编程语言来说,面向对象已经是不可或缺的一部分了
对于Python来说,这个对象实际上指的是类(class),类中可以包含成员变量,成员方法,这都是属于同一个类的内容
我们可以用类去封装一系列的变量和函数,基于类去构造出对象来使用
类的定义语法
class 类名称:
成员变量
成员方法
这里的成员方法其实就是函数,但是在类的内部,我们统称为方法
类的创建(实例化)语法
对象名称 = 类名称()
在类定义的时候,实际上是不消耗内存空间的,因为并没有构建类的对象,他就像是一个设计图纸,并未造出实体,一旦我们创建类,或者是实例化一个类,就是产生了一个类的对象,就是消耗了内存的
例如,我们构建一个简单的学生类,定义一个方法让他做自我介绍
class student:
name = None
age = None
gender = None
def greeting(self):
print(f"我是{self.name},我是{self.gender}生,我今年{self.age}了")
这里出现了self关键字,稍后我们会介绍
对于这个类的使用,示例如下
stu = student()
stu.name = '莫蒂'
stu.age = 14
stu.gender = '男'
stu.greeting()
类似于对模块中的变量和方法的使用,我们也可以使用其中的方法
def 方法名(self,形参,...):
方法体
这里的self实际上是用来表示对象自身的意思,如果我们想在方法内调用类中的成员,就必须要通过self调用,在调用方法传参的时候,self是可以忽略的
在上面我们可以看到,每次初始化成员变量的时候就要调用一个赋值语句,不够优雅,于是我们引入构造方法
例如
class student:
name = None
age = None
gender = None
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def greeting(self):
print(f"我是{self.name},我是{self.gender}生,我今年{self.age}了")
这样创建完构造方法之后,实际上在上面的变量声明就可以省略了,而这个构造方法会在构造对象时自动调用,自动传参
stu = student('莫蒂', 14, '男')
这样对类进行构造就十分方便了
上文学习的__init__ 构造方法,是Python类内置的方法之一
这些内置的类方法,各自有各自特殊的功能,这些内置方法我们称之为:魔术方法
这个方法实际上是一种对类转换成字符串的方法,一种主要的用途就是可以自定义输出的内容,直接调用str就会出现内存地址
例如
class student:
name = None
age = None
gender = None
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
stu = student('莫蒂', 14, '男')
print(stu)
print(str(stu))
class student:
name = None
age = None
gender = None
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def __str__(self):
return f"name={self.name} gender={self.gender} age={self.age}"
stu = student('莫蒂', 14, '男')
print(stu)
print(str(stu))
直接对两个类的比较大小是会出错的,我们可以构建这个魔术方法对类的大小进行定义,而且这个方法可以同时实现大于符号和小于符号的两种比较,类似于C++中的运算符重载
例如我们可以按照年龄大小进行比较
class student:
name = None
age = None
gender = None
def __lt__(self,other):
return self.age < other.age
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def __str__(self):
return f"name={self.name} gender={self.gender} age={self.age}"
这里和小于符合几乎一样,不过多赘述了
面向对象的简单理解就是基于模板(类)去创建实体(对象),再通过对象进行功能的开发
对于面向对象来说,他有三大主要的特性,即为封装,继承,多态
封装的概念我们没有细讲,但是其实已经使用到了
我们将各种属性(成员变量),行为(成员方法)封装到一个类中,形成了一个类似于包裹的东西,可以调用其中的部分变量和方法
在实际中,我们构建出一个类,有些内容是不希望使用者(用户)能够访问到的,这也引申出了一个概念,私有成员
我们可以用特定的形式来约定私有成员变量和私有成员方法
例如
class student:
name = None
age = None
gender = None
__id = None # 私有成员变量
def __func():
私有成员变量:变量名以__开头(2个下划线)
私有成员方法:方法名以__开头(2个下划线)
这样我们在类外就无法调用和使用私有成员了,但在成员方法内部依然可以使用
之前的类的定义和使用似乎已经完美了,但是如果我们想要对类进行更新维护,添加新的功能,又不能失去原有的功能,似乎只有两种方法,一是复制粘贴重新写一个类,二是基于原来的类进行修改,但是又难以保证新写出的代码一定是完美的,尤其是当代码量及其庞大的时候,为了解决这个问题我们引入了继承这个概念
继承从字面意思来理解,就是从原有的类中,添加新的功能,那我们也称这个原有的类为父类或者基类
语法:
class 类名(父类名):
类内容
例如我们想构建一个手机类,再对这个手机类进行更新维护
class MyPhone_1:
ID = None
Producer = None
def CallBy4G(self):
print('4G')
class MyPhone_2(MyPhone_1):
FaceID = True
def CallBy5G:
print('5G')
这样我们就从MyPhone_1继承出了MyPhone_2
继承也分为单继承和多继承,也就是说,可以从不止一个父类那里继承来他的成员变量和成员方法,需要注意的是,私有成员不会被继承,在多继承中,如果声明有相同的变量名称,那么按照从左到右的顺序继承,先继承的保留,后继承的则不会保留
我们说子类是对父类的更新,因此如果我们对父类的成员不满意,则可以对其进行复写,例如
class MyPhone_1:
ID = None
Producer = None
def CallBy4G(self):
print('4G')
class MyPhone_2(MyPhone_1):
FaceID = True
ID = 999
def CallBy4G:
print('4G+')
def CallBy5G:
print('5G')
一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员
如果需要使用被复写的父类的成员,需要特殊的调用方式
父类名.成员
class MyPhone_1:
ID = None
Producer = None
def CallBy4G(self):
print('4G')
class MyPhone_2(MyPhone_1):
FaceID = True
ID = 999
def CallByOld4G:
MyPhone_1.CallBy4G()
def CallBy4G:
print('4G+')
def CallBy5G:
print('5G')
super().成员
class MyPhone_1:
ID = None
Producer = None
def CallBy4G(self):
print('4G')
class MyPhone_2(MyPhone_1):
FaceID = True
ID = 999
def CallByOld4G:
super().CallBy4G()
def CallBy4G:
print('4G+')
def CallBy5G:
print('5G')
需要注意的是,只能在子类内调用父类的同名成员,如果使用对象调用则会直接调用复写的成员
当我们在使用诸如PyCharm的工具中他会自动弹出代码补全,而我们我们自己写的类或函数则不会,这是为什么,实际上是因为我们自己写的变量,PyCharm不确定这个对象是什么类型,因此不会弹出代码补全
在Python3.5之后,都支持类型注解,可以提供类型的显示说明,方面代码提示
他可以作用于变量的类型注解,函数(方法)形参、返回值的类型注解
语法如下 变量: 类型
age: int = 14
name: str = '莫蒂' # 变量的类型注解
class student:
pass
stu: student = student() # 类对象类型注解
list1: list = [1,2,3] # 容器类型注解
list2: list[int] = [1,2,3] # 容器详细注解
tuple1: tuple[str,int,bool] = ('a',1,True) # 元组需要将每一个元素都标记出来
dict1: dict[str,int] = {'a':1,'b':2} # 字典需要两个类型,一个代表key,一个代表val
在注释中进行类型注解也是符合语法的 # type: 类型
例如
age = 14 # type: int
需要注意的是,类型注解是提示性信息,并不是决定性的,即使错误也并不会进行报错
例如
age: int = 'aa'
语法
def func(形参名: 类型, 形参名: 类型) -> 返回值类型:
pass
例如
def add(x: int, y: int) -> int:
return x+y
这个实际上是用于定义联合类型注解的,例如
from typing import Union
dict2: dict[str, Union[str,int]] = {'a':1,'b':'b'}
这里表示的就是可能出现的类型,也很简单,在变量,函数注解中都可以使用
多态指的是多种状态,即完成某个行为(调用某个函数),使用不同的对象会得到不同的状态(结果)
可以理解为,一个高级的ifelse语句
例如
class Animal:
def speak(self):
pass
class Dog(Animal): # 继承
def speak(self):
print('wang wang')
class Cat(Animal): # 继承
def speak(self):
print('miao miao')
def MakeNoise(anm: Animal): # 类型注解
anm.speak()
d = Dog()
c = Cat()
MakeNoise(d)
MakeNoise(c)
这个过程中Animal是父类,并没有实际的定义实际的内容,只是声明,他有两个子类Dog和Cat,他们是对功能进行实际的定义,最后通过第三方来调用,以达到传入不同的类能产生不同的结果
在上面的不少代码中,我们都使用到了pass关键字,这里表示空实现,也可以对比理解为变量的None,这种写法就叫做抽象类(接口),与之对应的还有抽象方法
由此我们就可以只定义各类方法的标准,具体实现交给别的类,例如我们规定动物(父类)可以走,跑,跳,叫,吃(方法),但是不规定他们以何种形式,以何种工具实现这种功能,直到具体到某动物(子类)的时候,我们才能知道他的实现方式,例如猫是四条腿走路,鸵鸟是两条腿走路
我们的Python类和对象就到此为止了,感谢各位的支持,如果你发现文章中有任何不严谨或者需要补充的部分,欢迎在评论区指出