本篇文章记录的是Python面向对象的知识。
一、什么是面向对象
面向对象编程是一种编程思想 , 一种编程范式 , 不是只有python才有 , 而是所有的编程语言都可以用面向对象的思想去写代码 , 那么面向对象到底是什么 ?
面向对象
的核心是对象
二字,而对象的精髓在于整合
,什么意思?
所有的程序(脚本) 都是由 “数据” ( 变量 ) 与 “功能”(函数) 组成,因而编写程序的本质就是定义出一系列的数据,然后再定义出一系列的功能来对数据进行操作。
示例
一个游戏中一个用户 , 每天的打卡代码
name = "小王"
age = 21
addr = "信思智学"
def da_ka():
print(f"{age}的{name},每天都要去{addr}上班,打卡")
print("打卡成功")
da_ka()
上述代码是不是有数据 有功能 , 整个程序抽象成网络游戏中的一个用户每天必须要做的事情 , 那如果有一个新注册的用户小李 , 是不是每天也要做这样的事情 , 程序背后的代码是不是还是这几行 ? 只不过数据的值可能要变
name = "小李"
age = 22
addr = "信思智学"
def da_ka():
print(f"{age}的{name},每天都要去{addr}上班,打卡")
print("打卡成功")
da_ka()
二、为什么要有面向对象
如果当前这个游戏 背后所有用户的打卡代码都这样 , 是不是非常的冗余 , 既然都是用户 , 那么都会有三个变量要创建对吧 , 我们把这三个变量称之为属性 , 以及一个打卡函数 , 我们称之为功能 , 我们能不能把这些属性和功能封装到一个类似像函数那样的代码中 , 我们只需要传入不同的值就可以实现这样的打卡功能了
可以的 , 面向对象编程就可以实现 , 把公有的一些属性和方法 , 封装到一个类中 , 然后通过类实例化出不同的对象这个不同取决于我们在实例化对象时候传入的值是不同的 , 所以实例化出的对象也是不同的 , 然后通过对象去调用打卡功能 , 那在python中如何编写这样的代码呢?
三、面向对象的代码如何编写
1、类的定义
语法
class 类名:
类中的代码
# 关于类名的定义规范和变量很相似 , 只不过它的首字母要大写 , 多个单词组成的话 , 推荐使用驼峰法
其中解释器运行到类定义代码的时候 , 就会被执行 , 不像函数 , 函数里的代码只有当调用时才会被执行
示例
class A:
print("111")
2、实例化对象
关于类中的代码 , 如何设计 , 怎么写 , 我们以上面的那个例子为例
第一版
class User:
# 相同的属性
addr = "信思智学"
# 公有的方法
def da_ka(self, age, name, addr):
print(f"{age}的{name},每天都要去{addr}上班,打卡")
print("打卡成功")
先看如何通过类实例化出来对象 实例化对象
# 语法
# 类名()
obj1 = User()
obj2 = User()
obj3 = User()
3、属性的访问
# 语法
# 对象.属性
print(obj1.addr)
print(obj2.addr)
print(obj3.addr)
4、新增属性
即为对象定制自己独有的属性
# 语法
对象.属性 = 值
obj1.name = "小王"
obj1.age = 21
obj2.name = "小李"
obj2.age = 22
print(obj1.name)
print(obj2.name)
# 补充
如果属性存在会修改当前对象的属性值 , 其他对象还是原来的属性值
5、方法的访问
# 语法
# 对象.方法()
obj1.da_ka(obj1.name,obj1.age,obj1.addr)
6、初始化方法
如果我的程序 , 现在要再创建3个对象 , 并且还要新增自己独有的属性值 , 那么这样的代码是不是还要写3遍
obj2.name = "小强"
obj2.age = 23
.......
代码是不是很重复 冗余 , 既然重复 冗余我们能不能封装成一个函数 , 每次想新增属性 , 只需要调用函数 , 然后传递不同的参数即可
第二版
class User:
# 相同的属性
addr = "信思智学"
# 公有的方法
def da_ka(self, age, name, addr):
print(f"{age}的{name},每天都要去{addr}上班,打卡")
print("打卡成功")
def init(obj,name,age):
obj.name = name
obj.age = age
obj3 = User()
obj4 = User()
obj5 = User()
init(obj3,"小强",23)
init(obj4,"小刚",24)
init(obj5,"小呆呆",25)
print(obj3.name)
既然这个init函数 , 是所有对象都要调用的函数 , 我们能不能把它想da_ka这样的功能一下 , 是公有的 , 我们写的类里面呢 ? 是可以的
第三版
class User:
# 相同的属性
addr = "信思智学"
def __init__(self, name, age):
self.name = name
self.age = age
# 公有的方法
def da_ka(self, age, name, addr):
print(f"{age}的{name},每天都要去{addr}上班,打卡")
print("打卡成功")
obj3 = User("小强", 23)
obj4 = User("小刚", 24)
obj5 = User("小呆呆", 25)
print(obj3.name)
print(obj4.name)
print(obj5.name)
实际上 python 提供的有专门的方法 , 就是 __init__
, 它称之为 初始化方法 , 当实例化对象的时候会自动的调用 ,该方法是有着固定的作用 , 就是用来封装对象之间不同的一些属性值 , 如 人对象 , 每个人都有名字这个属性, 但是名字的值都是不同的
补充
在这类以__开始和__结束的方法 , 有的从php转来的程序员 也叫他魔术方法/魔法函数 等等
7、self
self是什么呢 ? 它实际上是当前属性/方法的调用者 , 即当前对象 , 我们通过print ,打印他们的内存地址 , 即可发现
示例
class User:
# 相同的属性
addr = "信思智学"
def __init__(self, name, age):
self.name = name
self.age = age
# 公有的方法
def da_ka(self, age, name, addr):
print(self)
print(f"{age}的{name},每天都要去{addr}上班,打卡")
print("打卡成功")
obj3 = User("小强", 23)
obj4 = User("小刚", 24)
obj5 = User("小呆呆", 25)
obj3.da_ka(obj3.age,obj3.name,obj3.addr)
print(obj3)
"""
<__main__.User object at 0x000001F5583A27F0>
23的小强,每天都要去信思智学上班,打卡
打卡成功
<__main__.User object at 0x000001F5583A27F0>
"""
既然self是当前调用的对象 , 当前对象中又封装的有da_ka函数需要使用到的变量 , 那么 da_ka 中代码我可以这样修改
第四版
class User:
# 相同的属性
addr = "信思智学"
def __init__(self, name, age):
self.name = name
self.age = age
# 公有的方法
def da_ka(self):
print(f"{self.age}的{self.name},每天都要去{self.addr}上班,打卡")
print("打卡成功")
obj3 = User("小强", 23)
obj4 = User("小刚", 24)
obj5 = User("小呆呆", 25)
obj3.da_ka()
四、面向对象的三大特征
封装 : 找到变化并且把它封装起来 , 你就可以在不影响其它部分的情况下修改或扩展被封装的变化部分 , 这是所有设计模式的基础 , 就是封装变化 , 因此封装的作用 , 就解决了程序的可扩展性。
继承 : 子类继承父类 , 可以继承父类的方法及属性 , 实现了多态以及代码的重用 , 因此也解决了系统的重用性和扩展性
多态 : 接口的多种不同的实现方式即为多态。接口是对行为的抽象 , 刚才在封装提到 , 找到变化部分并封装起来,但是封装起来后 , 怎么适应接下来的变化?这正是接口的作用 , 接口的主要目的是为不相关的类提供通用的处理服务,我们可以想象一下。比如鸟会飞 , 但是超人也会飞 , 通过飞这个接口 , 我们可以让鸟和
超人 , 都实现这个接口 , 这就实现了系统的可维护性,可扩展性。
因此面向对象能实现人们追求的系统可维护性 , 可扩展性 , 可重用性。面向对象是一种编程思想 , 起初,“面向对象”是专指在程序设计中采用封装、继承、多态等设计方法,但面向对象的思想已经涉及到软件开发的各个方面,比如现在细分为了面向对象的分析(OOA),面向对象的设计(OOD),面向对象的编程实现(OOP)
1、封装
还是针对上面的代码 , 比如我还想 封装 一个每天晚上要去查人数的功能 , 我就可以这样
class User:
# 相同的属性
addr = "信思智学"
def __init__(self, name, age):
self.name = name
self.age = age
# 公有的方法
def da_ka(self):
print(f"{self.age}的{self.name},每天都要去{self.addr}上班,打卡")
print("打卡成功")
def kao_qin(self):
print(f"亲爱的{self.name}同学,每天晚上19.00都要去查考勤")
print("查考勤结束")
obj3 = User("小强", 23)
obj4 = User("小刚", 24)
obj5 = User("小呆呆", 25)
obj3.da_ka()
obj3.kao_qin()
说简单点 , 就是把函数写到类里面 , 当然关于封装不仅仅只是这一点点而已 , 还有其他的封装
2、继承
什么是继承
继承是一种创建新类的方式,在Python中,新建的类可以继承一个或多个父类,新建的类可称为子类或派生类,父类又可称为基类或超类
继承语法
class 类名(父类1,父类2):
pas
示例
class Parent1:
x = 11
class Parent2:
pass
class Sub1(Parent1):
pass
class Sub2(Parent1, Parent2):
pass
为什么要继承以及继承的意义
为什么要用继承
解决类与类之间代码冗余问题
继承的意义
子类会遗传父类的属性 , 解决类与类之间代码冗余问题
继承下的属性查找顺序
class Foo:
def f1(self):
print("foo.f1")
def f2(self):
print("foo.f2")
self.f1()
class Bar(Foo):
def f1(self):
print("bar.f1")
b = Bar()
b.f2()
# 只要涉及到继承找属性,一定要先从当前对象查找,没有,再到类中查找,如果在没有再到父类中查找
补充 : 父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的
class Foo:
def __f1(self): # 变形为_Foo__fa
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.__f1() # 变形为self._Foo__fa,因而只会调用自己所在的类中的方法
class Bar(Foo):
def __f1(self): # 变形为_Bar__f1
print('Foo.f1')
b=Bar()
b.f2() #在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法
Foo.f2
Foo.f1
关于继承 , 要求 : 能看懂
3、多态
关于多态 , 是在面向对象中最抽象和最难理解的一个特征 , 同时在python这门编程语言中对多态的实现在语法层面上 , 又是那么的不明显 , 导致很多人不理解这个多态
是什么多态
多态指的是一类事物有多种形态,比如动物这类事物有多种形态:猫、狗、猪 , 水有水蒸气 , 液体水 , 冰这些形态
看代码
# Persion类
class Persion:
def __init__(self, name):
self.name = name
def eat(self): # 吃饭
pass
class Boy(Persion):
def __init__(self, name, gender="男"):
# 在子类中 , 调用父类的 __init__ 方法 , 简单理解: super()返回父类
super().__init__(name)
self.gender = gender
def eat(self): # 男孩喜欢吃肉
super().eat() # 调用父类的eat方法
print(f"{self.name} like eat meat !!!")
class Girl(Persion):
def __init__(self, name, gender="女"):
super().__init__(name)
self.gender = gender
def eat(self): # 女孩喜欢吃米饭
super().eat() # 在子类中 , 调用父类的eat方法 , 简单理解: super()返回父类
print(f"{self.name} like eat rice !!!")
# 多态的体现,既可以是boy,也可以是个girl
# 只要实现了eat方法 , 我不管你是什么形态
def eat(persion):
persion.eat()
boy = Boy("alex")
girl = Girl("lili")
# 接收的类型 可以是女孩/男孩 只要实现了eat方法即可
eat(boy)
eat(girl)
五、面向对象和面向过程的区别
面向对象是基于面向过程的编程思想
面向过程:强调的是实现每一个功能的步骤
面向对象:强调的是对象,然后由对象去调用功能
六、总结
知道语法 , 知道属性是如何调用的 , 方法如何调用的
编写程序 , 不使用面向对象仍然可以完成需求
大家对面向对象的迷惑点是 , 什么情况下使用面向对象 , 面向对象中的属性和方法应该怎么定义
我写的代码符不符合面向对象标准
面向对象编程 , 不需要必须实现继承和多态 我单独的一个封装也是面向对象编程
对新手朋友来说最难的实际上是面向对象的设计 , 当你拿到一个需求的时候 , 你不知道该怎么设计 , 即怎么的封装
属性和方法怎么定义 , 进而你会觉得面向对象难
有一个方法 , 你先把你想要实现的程序用到的数据和功能 , 拆分出来 , 然后再编写程序 , 因为面向对象一定是对属性(数据)和方法(功能的封装) , 然后再编写代码