1.面向对象
面向过程和面向对象
面向过程:
变量和函数。 “散落” 在文件的各个位置,甚至是不同文件中。
看不出变量与函数的相关性,非常不利于维护,设计模式不清晰。
经常导致程序员,忘记某个变量的相关性,而导致无法检测的错误
面向过程:
相关的变量和函数都“封装” 在对象里,以对象为单位来管理代码。
变量与函数的相关性清晰,利于维护,设计模式清晰。
程序员可以配合“继承” 来提高代码的可重用性,加强合作开发。
区分
面向过程的变成侧重点于编写一系列的步骤来解决问题
面向对象的变成侧重于创建对象和类,这些对象和类封装了数据和处理这些数据的方法
下面我将通过一个简单的例子来展示这两种编程范式的区别。假设我们要编写一个程序,用于计算一个矩形的面积。
面向过程的编程(C语言示例)
在面向过程的编程中,我们通常直接编写函数来执行特定的任务。
#include <stdio.h>
// 定义一个函数来计算矩形的面积
int calculateRectangleArea(int length, int width) {
return length * width;
}
int main() {
int length, width;
// 获取用户输入
printf("请输入矩形的长度: ");
scanf("%d", &length);
printf("请输入矩形的宽度: ");
scanf("%d", &width);
// 调用函数计算面积
int area = calculateRectangleArea(length, width);
// 输出结果
printf("矩形的面积是: %d\n", area);
return 0;
}
在这个C语言的例子中,我们定义了一个函数calculateRectangleArea
来计算面积,然后在main
函数中获取用户输入并调用这个函数。
面向对象的编程(Python示例)
在面向对象的编程中,我们创建类来封装数据和行为。
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def calculate_area(self):
return self.length * self.width
# 创建矩形对象
rectangle = Rectangle(5, 3)
# 调用方法计算面积
area = rectangle.calculate_area()
# 输出结果
print(f"矩形的面积是: {area}")
在这个Python的例子中,我们定义了一个Rectangle
类,它有两个属性(length和width)和一个方法(calculate_area)。我们创建了一个Rectangle
对象,并调用其方法来计算面积。
主要区别
封装:面向对象编程通过类来封装数据和方法,而面向过程编程通常不封装数据和函数。
可重用性:面向对象编程通过继承和多态性提高了代码的可重用性。
维护性:面向对象编程通常更容易维护,因为每个对象都是独立的单元。
抽象:面向对象编程提供了更好的抽象,通过类和对象隐藏了实现细节。
这些例子展示了两种编程范式的基本思想,但在实际应用中,它们可以更加复杂和多样化。
就我总结:python中我们直接调用函数,不会像是c语言那么复杂一个一个进行定义
面向对象的介绍
借鉴函数的封装思维,再次对函数和变量进行封装,来提高代码的复用性。用关键字 class 定义,为了便于区分,把的代码块称为 类。
我们使用类将函数和变量进行封装 ,我们使用class这个关键字进行封装
变量就是属性,函数就是方法
'''
class 类名:
属性\方法
属性:变量()
方法:函数()
'''
#定义一个人类
class Person:
name='小明' #类属性
def work():#类方法
print(f'{Person.name}的工作是厨师')
#这里的name 是类属性我们需要通过类名进行调用
#变量是属性,函数是方法
#上面我们就实现了一个类的创建了
#类是通过对象进行实现后续的操作的
#那么我们就需要创建一个对象
#创建对象:类名()
print(Person)
#<class '__main__.Person'>
p1=Person()
print(p1)
#<__main__.Person object at 0x0196F970>
#这个p就是我们创建的对象
#我们每次创建对象编译器就会为其进行空间的分配
#一个类是可以进行多个对象的创建的
p2=Person()
#我们对于类属性和类方法的访问
#通过对象直接对类中的元素进行访问
print(p1.name)
#p1.work()
#这么写是会报错的
#对象在调用方法时,会将对象信息传递给方法
#所以我们在利用对象调用类方法的时候是传递了一个类似于指针的东西
#但是我们的函数中并没有进行接收的操作
#所以这个是会进行报错的
#所以我们在调用类方法的时候我们是不能直接通过对象进行调用的
#综上所述:
#想要调用类属性的话
#类名.属性名
print(Person.name)
#想要调用类方法的话
#类名.方法名
print(Person.work())
#小明
#小明
#小明的工作是厨师
p1.work()
这么写是会报错的
对象在调用方法时,会将对象信息传递给方法
所以我们在利用对象调用类方法的时候是传递了一个类似于指针的东西
但是我们的函数中并没有进行接收的操作
所以这个是会进行报错的
那么我们如何通过这个对象进行类方法的调用呢?
我们在这个类方法中添加一个参数self用来接收传过来的对象信息
那么我们就能进行调用了
这种在类函数中添加self的操作叫做实例方法
哪个对象调用它,它就接收哪个对象的信息
所以self==对象本身
所以我们在类里面我们用self代表对象,在外面就用对象代表对象
类方法和实例方法的区别在于括号中是否存在self
只有调用示例化的方法我们才用创建对象
我们的方法基本上都是用实例方法
实例方法我们只能通过对象名进行调用
对于没有添加self的类方法我们只能够通过类名进行调用
每次通过类名进行类方法的调用这个里面的值都是固定的
但是我们通过这个实例方法进行调用的话,那么我们这个里面的值就随着对象的改变而改变
class Person:
name='小明' #类属性
def work():#类方法
print(f'{Person.name}的工作是厨师')
def job(self):#实例方法
print(f'self:{self}')
p1=Person()
p1.job()
#<__main__.Person object at 0x03C29AB0> 这个就是对象的地址了
#我们只要将类中的方法实例化我们就能通过对象进行类方法的调用了
#我们在类方法中定义了self用来接受传过来的对象信息
总结下:类方法只能通过类名进行调用,每次调用的结果都是一样的,而且类方法是不能通过对象进行调用的,因为我们在调用类方法的时候通常是会在函数中传递实参的,但是我们的类函数中并没有形参进行接收,那么就会造成报错的现象,但是我们的实例方法中,我们在原本的类函数的基础上添加了self这个参数用来接受对象的信息的,那么在这个实例方法中我们就有形参进行接收调用函数时传过来的对象信息了,我们在外面通过对象进行对象的访问,但是在这个实例方法中我们通过self进行访问对象,因为self指向的是对应的对象
实例方法是可以根据对象的不同而导致传递的值就不痛,就不会像类方法那么固定了
通过实例方法的话我们函数中的参数随着 对象的变化进行变化
实例化对象的介绍
假如我们现在有三个人,我们分别调用这个实例方法,我们都是不会相互影响的,因为三个人的self都是不同的,不会互相影响,这个就是实例方法的好处
如果想使用类方法的话,那么所有的对象都是一样的东西
学到这里我们就理解这个类方法和实例方法的区别了
class Mode:#模型类
#类方法:对于所有实例对象统一的功能
def mode():
print("这是一个苹果15手机壳模型")
#实例方法:应用于对象,对于对象不同,内容不同
def types(self0,color,mat):
print(f"这是一个{color}颜色{mat}材质的手机壳")
#调用类方法
Mode.mode()#类名.类方法进行调用
#这是一个苹果15手机壳模型
#调用示例方法:实例对象.实例方法()
#创建实例对象
m1=Mode()
m1.types("红色","金属")
#这是一个红色颜色金属材质的手机壳
类中除了属性和方法还有个占位符pass:
'''
class 类名:
属性/方法/pass
属性--变量
方法--函数
pass--占位符
我们在定义类的时候我们对于类中需要写什么我们还没有想好,
我们可以通过pass进行占位的操作的
'''
class Pub:
pass#进行占位
#这么就不会出现报错,但是我们如果不加pass的话,并且类中是空的,那么就会报错
如果我们没有想好类里面写什么的话,使用pass进行占位置,这样这个类就不会报错了
类的使用---实例化对象:类名
class Student:
pass
print(Student)
#<class '__main__.Student'>
s1=Student()
print(s1)
#<__main__.Student object at 0x0184F090>
类方法:用于共享的操作,所有对象是一致的
实例方法:用于不同的对象有不同的数据
定义实例属性:在实例方法中通过self.属性名=值
实例属性一但被定义了,那么在实例方法中都能进行调用的
不存在之前函数内变量的局部域和全局域的说法
只要是实例方法的话就能进行实例属性的运用
class Student:
#姓名、年龄、成绩---描述----属性
def fun(self,name,age,grade):#接收传入的数据
self.name=name
self.age=age
self.grade=grade
#睡觉、吃饭、学习---动作、行为---方法 def 方法名()
#self接受对象信息,即seif=对象
def sleep(self):
print(f'{self.name}正在睡觉')
#在类里面进行实例属性的调用
def eat(self):
print(f'{self.name}正在吃东西')
def study(self):
print(f'{self.name}正在学习')
s1=Student()
s1.fun("小明",12,78)
#现在fun函数里面有四个参数,第一个self我们是不用传实参的
#self是可以自动传参的,不需要手动传参的
print(s1.age)
s1.sleep()
#小明正在睡觉
'''
实例属性一但被定义了,那么在实例方法中都能进行调用的
不存在之前函数内变量的局部域和全局域的说法
只要是实例方法的话就能进行实例属性的运用
'''
s1.eat()
#小明正在吃东西
s1.study()
#小明正在学习
'''
s2=Student()
s2.sleep()
这么写是会报错的,因为我们是没有进行对象内属性的初始化的
'''
#生成多个对象,每个对象的值都是不一样的
#我们在调用其他的实例方法的之前我们一定要对对象里面的属性进行初始化
#不然后面的方法是调用不了的
我们直接使用对象名.实例方法名()进行类中的实力方法进行调用
对于上面的代码,如果我们又创建了一个对象s2,但是没有调用fun函数进行初始化,那么我们就是不能进行后面的实力函数的调用的
因为fun函数内的实力属性都没有被定义
我们需要将实例属性定义之后才能进行后续的实力属性的调用操作的
2.魔法方法
魔法方法值方法在某种情况下会自动执行
方法是只有调用下我们才会执行,但是魔法方法的话是在某种特殊的情况下会自动进行执行的
下面的析构函数和构造函数都是魔法方法
构造函数---初始化方法
格式:def init(self): 注意这里是两个下划线
作用:在创建对象的时候就会自动执行
class Student:
#姓名、年龄、成绩---描述----属性
def fun(self,name,age,grade):#接收传入的数据
self.name=name
self.age=age
self.grade=grade
#睡觉、吃饭、学习---动作、行为---方法 def 方法名()
#self接受对象信息,即seif=对象
def sleep(self):
print(f'{self.name}正在睡觉')
#在类里面进行实例属性的调用
def eat(self):
print(f'{self.name}正在吃东西')
def study(self):
print(f'{self.name}正在学习')
class Test:
def __init__(self,name,age):
print("这是一个初始化方法")
t=Test()
#这是一个初始化方法
#我们在创建对象的时候这个init函数会自动执行的,自动进行初始化操作
#如果在__init__中有定义形参,那么创建对象时就要传递实参
#对于对象中都要使用到的通用属性的话,我们就会将这些属性放到__init__函数进行初始化操作
#伴随着对象的创建,那么这个对象的基本属性就初始化好了
#像是上面的fun函数我们必须先调用我们才能实现这个对象属性的初始化操作
#但是现在的话我们是不需要进行调用这个init函数
#我们对象一创建好这个init函数就自动进行调用了
#将一些必备的属性放在init函数中,创建对象就直接初始化好了,
将一些常用的属性放到init函数中,然后我们在创建对象的时候就能进行对象属性的初始化了
析构函数
格式:def del__(self)
在实例对象被销毁的时候执行
在对象的生命周期快到了的时候就将对象所占的空间进行清空的操作
就是释放内存空间
class Test:
#构造函数
def __init__(self,name):
self.name=name#这个self就是指向的对象
print("这是一个初始化方法")
#析构函数--对对象进行销毁的操作
def __del__(self):
print(f'{self.name}进行销毁')
t=Test("张三")
print("程序执行完毕")
del t
'''
这是一个初始化方法
程序执行完毕
张三进行销毁
'''
#我们只有将这个t这个对象进行销毁了,才会进行这个析构函数
#如果这个t没有被回收的话是不会进行析构函数的
#我们这里是不会进行这个析构函数内的打印操作的,但是在paycharm中是会进行的
#手动的删除del执行时间是在程序结束之前进行的
py文件所有代码执行完就会自动执行析构函数
我们在创建对象t的时候在这个类括号中输入了张三
然后张三就别传到了init函数进行属性的初始化操作了
方法是只有调用下我们才会执行,但是魔法方法的话是在某种特殊的情况下会自动进行执行的
除了自动删除操作,我们还能使用手动删除操作del进行删除操作
输出魔法方法
格式: ****str(self):设置print输出对象名时显示的内容
class Test:
#构造函数
def __init__(self,name):
self.name=name#这个self就是指向的对象
print("这是一个初始化方法")
#析构函数--对对象进行销毁的操作
def __del__(self):
print(f'{self.name}进行销毁')
#设置print输出对象名时显示的内容
def __str__(self):
return "这是一个魔法方法__str__设置的"
t1=Test("张三")
print(t1)
#这是一个魔法方法__str__设置的
这个就是可以将我们print函数输出的数据进行改变
我们想print输出啥这个就能改成啥,主要是以return进行返回来实现这个操作的
这个只能设置print输出的内容
如果我们需要改变交互模式输出的话,我们是可以通过这种魔法方法的
__ repr__(self):设置直接输出对象名时的显示内容
class Test:
#构造函数
def __init__(self,name):
self.name=name#这个self就是指向的对象
print("这是一个初始化方法")
#析构函数--对对象进行销毁的操作
def __del__(self):
print(f'{self.name}进行销毁')
#设置print输出对象名时显示的内容
def __str__(self):
return "这是一个魔法方法__str__设置的"
#__repr__(self):设置直接输出对象名时的显示内容
def __repr__(self):
return '这是__repr__设置显示的内容'
t1=Test("张三")
print(t1)
#这是一个魔法方法__str__设置的
'''
交互模式:
print(t1)
这是一个魔法方法__str__设置的
t1
这是__repr__设置显示的内容
'''
这个魔法方法可以直接将交互结果进行改变的
这个输出的话感觉用的很少
回顾:
定义实例方法:def 函数名(self)
调用实例方法:self.方法名或对象名.方法名
对象关系方法--isinstance(对象,类)
#isinstance(对象,类)
#判断对象是不是后面的类实例出的对象
print(isinstance(t1,Test))#这里我们前面创建了,只不过这里没有复制过来
#True
#就是判断我们第一个参数是不是通过第二个参数(类)实例化出来的
#这个就是进行类型的判断
print(isinstance(1,int))
#True
print(isinstance(1.5,int))
#False
判断这个对象是不是这个类实例话出来的
还能判断1是不是整型
返回值都是bool值
3.继承
单继承
举个例子:就是富二代从他爸那里继承了财产,这个就叫继承,富二代什么都不做
继承发生在两个类之间的
继承的关系是双方是父类和子类
继承可以使子类具有父类的所有属性和方法
class Father:#父亲类
def money(self):
print('100万')
#单继承:class子类名(父类名)
class Son(Father):#子类
#重写:在子类中定义一个与父类方法同名的方法就是重写,那么这个子类就不会去执行父亲类了
def money(self):
print('1000万')
#用儿子类创建出来的对象进行父亲类中实例方法的调用
s=Son()
s.money()
#100万
#子类可以通过继承父类,获取父类中的方法、属性
单继承:class子类名(父类名)
重写的操作:如果现在我们不需要使用父亲类中的方法的话,那么我们在子类中再定义一个同名的方法就能进行覆盖的操作
我们可以使用__ mro进行搜索顺序的查询
#__mro__:查询搜索顺序
print(Son.__mro__)
#(<class '__main__.Son'>, <class '__main__.Father'>, <class 'object'>)
#我们在执行这个money的时候我们先看自己类有没有这个方法
#如果自己类有这个方法的话就调用自己的,如果没有的话就看看父亲类有没有
#如果父亲类没有的话就去基类里面找,但是基类的话可能没有,那么就会进行报错的操作
#自己有就用自己的,自己没有就用父亲的
(, , )
从自己开始搜索,如果自己没有就用父亲的,父亲没有的话就会报错
但是我们进行重写操作之后又想用父亲类里面的,该怎么做呢?
#但是我们进行重写操作之后又想用父亲类里面的,该怎么做呢?
#我们可以进行扩展:
#扩展可以执行父类中的同名方法
#方法一:父类名().方法名(self)
class Father:
def money(self):
print('100万')
class Son(Father):
def money(self):
print('1000万')
#扩展可以执行父类中的同名方法
#方法一:父类名.方法名(self)
Father.money(self)
#方法二:super().方法名()#调用下一个类中的方法
#就是我们的搜索顺序是子类到父类到基类
#那么调用下一个类中的方法就是去父类里面找
super().money()
s=Son()
s.money()
#1000万
#100万
#通过方法一:我们也能调用父亲类里面的money方法了
#100万--第三种方法
方法一:父类名.方法名(self)
方法二:super().方法名()#调用下一个类中的方法
对于方法二来说的话,我们的搜索顺序是子类→父类→基类
那么子类的下一个类中的方法就是父类中的方法
通过这两种方法我们也能去调用父类中的方法了
多继承
一个子类同时继承多个父类
class 子类(父类1,父类2,……)
#一个子类同时继承多个父类就是多继承
class Father:
def money(self):
print('100')
class Mother:
def host(self):
print("三室一厅")
class Son(Father,Mother):
pass
s=Son()
s.money()
s.host()
#100
#三室一厅
子类啥都不干就能继承父亲和母亲的
下面的代码介绍了多父类的搜索顺序
class Father:
def money(self):
print('100')
class Mother:
def money(self):
print('1000')
def host(self):
print("三室一厅")
class Son(Father,Mother):
#重写操作
def money(self):
print("10")
#扩展
#扩展一:super().方法名()---这个扩展的是下一个类里面的对应的方法,就是Father里面的方法
super().money()#子类后面的类中第一个有money的类
#扩展二 父类名.方法()
Father.money(self)
Mother.money(self)
s=Son()
s.money()
s.host()
#现在父亲类和母亲类都有money
#那么儿子调用money的时候是调用谁的呢?
#100
#三室一厅
#为什么是这样的呢?
#我们可以试试看看搜索顺序是如何的
print(Son.__mro__)
#(<class '__main__.Son'>, <class '__main__.Father'>, <class '__main__.Mother'>, <class 'object'>)
#先是子类,再是父类,再是母类,最后是基类
#这个顺序依照的就是我们在子类继承多类的时候写在括号内的顺序的
#class Son(Father,Mother):
#如果我们先写的是Mother的话,那么这个搜索顺序肯定是Mother排第二个
搜索的顺序取决于这个我们在继承操作时在括号中写的顺序
如果是mother写在前面那就子类过后就搜索mother中是否存在对应的方法
这个上面的代码还介绍了如何进行扩展操作的,访问父类和子类中的money方法
两种方法
建议使用这个第二种方法: 父类名.方法()
这种直接就指定了拓展哪个类里面的哪个方法