58 面向对象编程

290次阅读
没有评论

共计 5816 个字符,预计需要花费 15 分钟才能阅读完成。

引入

面向对象的由来

58 面向对象编程

一. 面向过程与面向对象

1. 面向过程

  • 面向过程编程的核心就是 过程 二字, 既先做什么, 再做什么, 然后做什么
  • 优点 : 将复杂的问题流程化, 进而简单化
  • 缺点 : 一整个流程只为了解决一个问题, 换另一个问题又需要另一个整套的流程, 牵一发而动全身

2. 面向对象

  • 面向对象编程的核心就是 对象 二字, 对象是 特征与技能的集合体
  • 也可以说对象是一个 容器, 用来盛放 数据(特征) 和 功能(技能)
  • 优点 : 提高程序的解耦合性, 进而提高程序的可扩展性, 对某一个对象单独修改, 会立刻反映到整个体系当中
  • 缺点 : 编程的复杂程度远高于面向过程, 并且不能精准的预测到问题的处理流程和结果, 只能让对象之间进行交互才能准确的知道最终的结果

58 面向对象编程

ps : 一个软件质量因素有很多: 性能, 可维护, 可扩展, 可移植, 成本, 安全......, 而面向对象解决的仅仅是 可扩展性的问题, 所以它也是有局限性的

二. 面向对象的三个特性

1. 继承

2. 封装

3. 多态

三. 类与对象

1. 什么是类

  • 类, 即类别, 种类, 是面向对象设计最重要的概念, 对象是特征与技能的集合体, 而类则是 一系列对象相似特征与技能的集合体
⛅在没有学习类这个概念时,数据与功能是分离的
⛅每次调用都需要重复传入一堆参数, 我们能想到的解决方法是,把这些变量都定义成全局变量
⛅这样我们将会定义一大堆全局变量,这些全局变量并没有做任何区分,即能够被所有功能使用
⛅然而事实上只有某一些对象才使用某一些它们所需要的功能, 并不是全部的功能都需要
⛅言外之意:我们必须找出一种能够将数据与操作数据的方法组合到一起的解决方法,这就是我们说的类了
  • 现实世界与程序中类与对象的关系
⛅" 现实世界中 : 先有对象, 再有类
世界上现实有了各种各样的物种: 猪, 狗, 猫, 人, 然后我们将其归为: 猪类, 犬类, 猫类, 人类

⛅" 在程序中 : 先有类, 再有对象
与函数类似, 先定义再调用, 类也是一样, 先定义类, 再调用类
不同的是函数调用时执行函数体代码, 而类则是实例出一个对

2. 类的定义与实例化

  • 类名尽量使用 驼峰体
🐫制造一个 " 鸭子对象 ", 鸭子有不同品种 " 特征 ", 鸭子会叫, 还会跑这些 " 技能 " (特征与对象的结合体: 就是属性与方法)

⛅先定义一个 " 鸭子类 "
class Duck:
    ⛅特征(属性)
    bread = " 你好鸭 "
    color = "yello"
    ⛅技能(方法), 特殊的函数, 自带 "self"
    def run(self):
        print(" 技能奔跑 ")

⛅定义一个 " 鸭子对象 "
duck1 = Duck()

⛅使用对象
print(duck1.bread)  # 你好鸭
print(duck1.color)  # yello
duck1.run()         # 技能奔跑

3.__dict__ : 类的属性字典

  • 类中可以有任意的 Python 代码, 这些代码在类定义阶段就会执行
  • 因而会产生新的名称空间, 用来存放类的变量名与函数名, 可以通过 [类名].__dict__ 来进行查看储存的属性字典, 其键为属性名,值为属性的值
  • 同理, 对象的属性 也是使用 .__dict__ 来查看
🐫"."(点)专门用来访问属性, 本质上就是在操作 "__dict__"

print(Duck.__dict__)
''' 输出结果
{'__module__': '__main__', 'bread': '你好鸭', 'color': 'yello',\
'run': <function Duck.run at 0x000001FB5771FC18>, '__dict__': <attribute '__dict__' of 'Duck' objects>,\
'__weakref__': <attribute '__weakref__' of 'Duck' objects>, '__doc__': None}
'''“__”开头的可以先不要关注

Duck.bread            # 等于经典类的操作 : Duck.__dict__["bread"]
Duck.color = "black"  # 等于经典类的操作 : Duck.__dict__["color"] = "black"
Duck.new_x = "haha"   # 等于经典类的操作 : Duck.__dict__["new_x"] = "haha"
del Duck.new_x        # 等于经典类的操作 : Duck.__dict__.pop("new_x")

4. 定制对象独有的特征

  • 调用类, 或称为实例化, 得到对象
class Duck:
    bread = " 你好鸭 "
    color = "yello"
    "self"

    def run(self):
        print(" 技能奔跑 ")

duck1 = Duck()

duck1.name = " 哎鸭 "     # 定制独有的品种
duck1.color = "black"   # 定制独有的颜色 
duck1.weight = 20       # 定制独有的重量

print(duck1.name)       # 哎鸭
print(duck1.color)      # black
print(duck1.weight)     # 20

5.__init__方法

  • 该方法是为对象初始化自己独有的特征
  • 该方法内可以有任意的 python 代码, 一定不能有返回值
🐫上面我们介绍了一种给对象定制独有特征的方法, 下面我们使用 "__init__" 方法直接进行初始化赋值

⛅新方案, 只要是类实例化得到对象, 就会自动触发 "__init__" 的执行
class Duck:
    def __init__(self):
        self.bread = " 你好鸭 "
        self.color = "yello"

    def run(self):
        print(f"{self.bread}有技能奔跑 ")

duck1 = Duck()

print(duck1.bread)  # 你好鸭
print(duck1.color)  # yello
duck1.run()         # 你好鸭有技能奔跑

⛅"__init__" 的升级使用, 触发 "__init__" 的执行, 并传入参数(传参原则与函数传参相同)
class Duck:
    def __init__(self,bread,color):
        self.bread = bread
        self.color = color

    def run(self):
        print(f"{self.bread}有技能奔跑 ")

duck1 = Duck(" 快跑鸭 ","black")
duck2 = Duck(" 可以鸭 ","blue")
duck3 = Duck(" 牛鸭 ","white")

print(duck1.bread)  # 快跑鸭
print(duck1.color)  # black
duck1.run()         # 快跑鸭有技能奔跑

print(duck2.bread)  # 可以鸭
print(duck2.color)  # blue
duck2.run()         # 可以鸭有技能奔跑

print(duck3.bread)  # 牛鸭
print(duck3.color)  # white
duck3.run()         # 牛鸭有技能奔跑

四. 对象属性查找

1. 类的两种属性

  • 类的数据属性是所有对象共享的
  • 类的函数属性是绑定给对象用的

2. 数据属性共享

  • 数据 ID 都一样

ps : id 是 python 的实现机制, 并不能真实反映内存地址, 如果有内存地址, 还是以内存地址为准

class Duck:
    master = "shawn"
    def __init__(self,bread,size):
        self.bread = bread
        self.size = size

duck1 = Duck(" 嘿鸭 ",100)
duck2 = Duck(" 好的鸭 ",80)

print(id(duck1.master))  # 1905145581488
print(id(duck2.master))  # 1905145581488

3. 函数属性绑定给对象

  • obj.method 称为绑定方法, 内存地址都不一样
class Duck:
    master = "shawn"
    def __init__(self,bread,size):
        self.bread = bread
        self.size = size
    def run(self):
        print(f"{self.bread}在跑 ")

duck1 = Duck(" 嘿鸭 ",100)
duck2 = Duck(" 好的鸭 ",80)
duck3 = Duck(" 别鸭 ",80)

print(Duck.run)  # <function Duck.run at 0x000001F46C919168>
print(duck1.run) # <bound method Duck.run of <__main__.Duck object at 0x000001F46C693E08>>
print(duck2.run) # <bound method Duck.run of <__main__.Duck object at 0x000001F46C8F3E48>>
print(duck3.run) # <bound method Duck.run of <__main__.Duck object at 0x000001F46C8F3E88>>

4. 对象的查找

  • 对象寻找属性, 比如 name, 它会先到 自己的名称空间 里面去找, 找不到再去 类里面 去找, 类中找不到再去 父类 , 父类中找不到就 抛出异常

58 面向对象编程

  • 类中定义的属性, 对象只能使用, 不能修改, 对象如果修改, 改的是自己的名称空间
⛅对象更改属性, 改的只是自己的
class Duck:
    master = "shawn"
    def __init__(self,bread,size):
        self.bread = bread
        self.size = size
    def run(self):
        print(f"{self.bread}在跑 ")

duck1 = Duck(" 不要鸭 ",80)
duck1.master = "song"  # 更改属性

print(Duck.master)     # shawn
print(duck1.master)    # song

⛅类修改
class Duck:
    master = "shawn"
    def __init__(self,bread,size):
        self.bread = bread
        self.size = size
    def run(self):
        print(f"{self.bread}在跑 ")

duck1 = Duck(" 酱紫鸭 ",80)
Duck.master = "xing" # 类修改属性

print(Duck.master)   # xing
print(duck1.master)  # xing

五. 对象的绑定方法

1. 什么是对象的绑定方法

  • 定义在 类内部的函数 (且没有被任何装饰器装饰的) 就是对象的绑定方法, 对象来调用, 调用时会自动传入对象本身 self
  • 对象的绑定方法, 类也可以调用 , 当类调用的时候, 调的就是一个 普通的函数, 有几个参数就要传几个参数
  • 可以通过 self 来区分到底是哪个对象调用了自己的绑定方法

ps : self 可以是任意名字,但是约定俗成地写成 self

2. 什么叫函数, 什么叫方法?

  • 函数就是 def 定义的函数, 有几个参数就要传几个参数
  • 方法是 绑定给对象使用的 , 由对象来调用, 特殊之处就是会把对象 自身传入 , 与方法 __init__ 是一样的道理

3. 对象调用绑定方法演示

  • 对象调用, 将对象自身传入
class Duck:
    master = "shawn"
    def __init__(self,bread,size):
        self.bread = bread
        self.size = size
    def run(self):
        print(f"{self.bread}在跑 ")

    def eat(self):
        print(f"{self.bread}在吃东西 ")

    def sleep(self):
        print(f"{self.bread}在睡觉 ")

duck1 = Duck(" 可恶鸭 ",80)
duck2 = Duck(" 厉害鸭 ",50)
duck3 = Duck(" 好惨鸭 ",90)

duck1.run()    # 可恶鸭在跑
duck2.eat()    # 厉害鸭在吃东西
duck3.sleep()  # 好惨鸭在睡觉

4. 类调用方法演示

  • 其实类中的函数主要是给对象来使用的, 这里只做演示
  • 类调用时要遵循函数的参数规则, 有几个参数就要传几个参数
Duck.run(duck1)   # 可恶鸭在跑
Duck.eat(duck2)   # 厉害鸭在吃东西
Duck.sleep(duck3) # 好惨鸭在睡觉

六.Python 中一切皆对象

1."类" 与 "类型"

  • 在 Python3 中,"类""类型" 同一个概念, 我们其实早就使用了类这个概念
  • 常用的数据类型 : list, dict, int, float, tuple, set, bool 等等
list1 = list([1,2,3])
dict1  = dict({"name":"shawn"})
int1 = int("12311321")
float1 = float("11.3")
tuple1 = tuple((1,2,3))
set1 = set(list1)
bool1 = True

print(type(list1))   # <class 'list'>
print(type(dict1))   # <class 'dict'>
print(type(int1))    # <class 'int'>
print(type(float1))  # <class 'float'>
print(type(tuple1))  # <class 'tuple'>
print(type(set1))    # <class 'set'>
print(type(bool1))   # <class 'bool'>

2.list 类演示

⛅使用 "list" 类实例化出一个对象
list1 = list([1,2,3,4])
list2 = list([5,6,6,8])

⛅每个对象都可以使用 "list" 类的公共属性和方法, 我们称之为绑定方法, 并且绑定给不同的对象, 内存地址也不相同
print(list1.append)  # <built-in method append of list object at 0x000001A950EAF108>
print(list2.append)  # <built-in method append of list object at 0x000001A950EAF188>

⛅实例化出来的每个对象都是相互独立的个体, 相互不影响
list1.append(1)
list2.insert(0,9)

print(list1)  # [1, 2, 3, 4, 1]
print(list2)  # [9, 5, 6, 6, 8]

七. 小结

  • 在上述介绍类与对象的使用过程中,我们更多的是站在底层原理的角度去介绍类与对象之间的关联关系

  • 如果只是站在使用的角度,我们无需考虑语法“对象. 属性 " 中”属性“到底源自于哪里,只需要知道是通过对象获取到的就可以了

  • 所以说,对象是一个高度整合的产物,有了对象,我们只需要使用”对象.xxx“的语法就可以得到跟这个对象相关的所有数据与功能,十分方便且解耦合程度极高

正文完
 
shawn
版权声明:本站原创文章,由 shawn 2023-06-16发表,共计5816字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)