Bootstrap

Python面向对象

# 面向对象

在 编程领域中 ,有多种 开发模式/风格 , 比较常见的 有 面向过程 、 面向对象 、 函数式编程 等 手段 。

在 Python 语言中, 支持 面向过程、 面向对象和函数式编程的 。

面向过程

面向过程 目的性很强 、为了完成某个特定的功能 而通过 函数 、判断 、循环 等手段 编写的代码 。

面向对象

面向对象 将一个 任务 进行拆解 、 每一部分的任务 由 特定的 类 /函数 等 完成 、最终 将 每一部分任务的结果 合并到一块 、最终完成整个任务 。

面向对象的三大基石

  • 封装
  • 继承
  • 多态

类 class

类是 自然界中 用来 描述 具有 相同 特征行为 的事物统称。 类是抽象的 。 类 是 对象的 模板 。

对象 是 具象 的 、是 类 的 实例 。Python 中 万物皆为对象。 所以 类 也是一个 对象, 是 type类型 的对象

type 是 Python的元类(万物之始)。

定义 类

class Person:
    pass

定义属性

class Person:
    """人类"""
    def __init__(self, name, sex, birth):
        # 将 传入 的名字 赋值给 人类对象 的 姓名属性
        self.name = name
        # 将 传入的 性别 赋值给 人类对象的 性别属性 。
        self.sex = sex
        # 将传入的出生日期 赋值给 人类对象的 出生日期属性。
        self.birth = birth


if __name__ == '__main__':
    # 根据 类型 创建 该 类型的 对象
    # 在 类型的后面 添加 小括号 负责创建对象,实际上 会 自动调用 类中定义的 __init__() 方法
    person = Person("张三", "男", "1990-10-10")
    # del person.name
    person.name = "张三丰"
    # 获取 对象的 名字
    print(person.name)
    # 获取 对象的 性别
    print(person.sex)
    # 获取 对象的出生日期
    print(person.birth)

    person2 = Person("张三", "男", "1990-10-10")
    print(person, person2)

定义方法

class Person:
    """人类"""

    def __init__(self, *, name=None, sex="保密", birth=None):
        # 将 传入 的名字 赋值给 人类对象 的 姓名属性
        self.name = name
        # 将 传入的 性别 赋值给 人类对象的 性别属性 。
        self.sex = sex
        # 将传入的出生日期 赋值给 人类对象的 出生日期属性。
        self.birth = birth

    def eat(self):
        """吃饭的方法"""
        print(f"{self.name}正在津津有味的吃饭")

    def sum(self, a, b):
        # 调用 eat 方法
        self.eat()
        return a + b


if __name__ == '__main__':
    # 根据 类型 创建 该 类型的 对象
    # 在 类型的后面 添加 小括号 负责创建对象,实际上 会 自动调用 类中定义的 __init__() 方法
    person = Person(name="张三丰", sex="男", birth="1990-10-10")

    # 方法 可以通过 对象调用
    person.eat()
    # 方法 也可以通过 类型用
    Person.eat(person)

    print(person.sum(33, 4))

构造方法(函数)

构造函数的职责: a) 创建对象 、 b) 给属性赋值

def  __init__(self, ...):
    pass

析构方法(函数)

对象在使用 del 进行销毁的时候触发的函数、用来在销毁对象的时候 触发某脚本任务的执行。

def  __del__(self):
    pass

私有属性

类中定义的属性 如果 不希望 别人 使用,那么 可以将属性 私有化

私有化的目的 是为了保证 类中 属性的 安全。

如果 希望 某个 属性 私有化 、只需要 在 定义属性的时候 ,给 属性 添加 前缀 _ 或者 __

如果 以 _ 开头,那么 该属性 是 受保护的, 不希望 别人直接使用的属性。
但无法 限制 使用者

如果 以 __ 开头 ,那么 该属性 是 私有的 , 在 外部无法直接访问 。
但 可以通过 _类名 + 私有属性名 进行访问

class Person:
    def __init__(self, *, name=None, sex=None, birth=None):
        # 定义属性 并给属性赋值
        self.name = name
        self.sex = sex
        self.__birth = birth

    def show(self):
        """显示人的信息、类内部可以直接调用 私有属性 """
        print(f"姓名{self.name}, 性别是:{self.sex}, 出生日期是:{self.__birth}")


if __name__ == '__main__':
    # 创建一个对象
    person = Person(name="张三", sex="男", birth="1990-10-10")

    print(person.name)
    print(person.sex)
    # 无法 直接访问 __birth 私有属性 ,但是 可以通过 _Person__birth 访问
    # print(person.__birth)
    print(person._Person__birth)
    # 做个自我介绍
    person.show()

对象的字符串表示形式

  • def __str__(self): : 以 字符串的形式 表示对象, 当 使用 str(x) 函数的时候, , 会调用 x 对象的 __str__ 方法完成对象到字符串的转换
  • def __repr__(self): : 主要给 打印 print 函数提供调用, 当 print(x) 的时候, 会 自动调用 __repr__ 函数。
class Person:
    def __init__(self, *, name=None, sex=None, birth=None):
        # 定义属性 并给属性赋值
        self.name = name
        self.sex = sex
        self.__birth = birth

    def show(self):
        """显示人的信息"""
        print(f"姓名{self.name}, 性别是:{self.sex}, 出生日期是:{self.__birth}")

    def __str__(self):
        """该函数的职责 将对象 以 字符串的形式表示、函数必须返回一个字符串、主要给 str()函数实现的。"""
        return f"( name={self.name}, sex={self.sex}, birth={self.__birth} )"

    def __repr__(self):
        """该函数主要给 print函数提供打印效果"""
        return str(self)

property 类

用来 解决 类中的 私有属性 问题 的\

class Student:
    """学生类"""

    def __init__(self, *, name=None, sex=None, sno=None):
        self.__name = name    # field 属性
        self.__sex = sex
        self.__sno = sno

    def instruction(self):
        """自我介绍"""
        print(f"我叫{self.__name}, 性别是{self.__sex},学号是{self.__sno}")

    def get_sno(self):
        return self.__sno

    def set_sno(self, sno):
        """修改私有属性学号"""
        self.__sno = sno

    def del_sno(self):
        del self.__sno

    sno = property(get_sno, set_sno, del_sno)


if __name__  == "__main__":

   stu = Student(name="张三")
   stu.sno =  "10000"   # 修改学号,自动调用 set_sno 方法

   print(stu.sno)       #  获取学号,自动调用 get_sno 方法

property 装饰器

class Student:
    """学生类"""

    def __init__(self, *, name=None, sex=None, sno=None):
        self.__name = name    # field 属性
        self.__sex = sex
        self.__sno = sno

    def instruction(self):
        """自我介绍"""
        print(f"我叫{self.__name}, 性别是{self.__sex},学号是{self.__sno}")


    @property
    def sno(self):
        return self.__sno

    @sno.setter
    def sno(self, sno):
        """修改私有属性学号"""
        self.__sno = sno

    @sno.deleter
    def sno(self):
        del self.__sno

静态/类属性

成员属性 是 基于 对象的 , 静态属性 是 基于 类 的 属性 , 静态属性 被 类 的 所有对象 共享 。

在 类 中 直接 定义的 属性 是 静态属性 。

静态 属性 可以 通过 类 / 对象 进行调用 (不建议使用对象调用) 。

当使用 对象 调用 属性的时候,默认 会先找 成员属性, 成员属性不存在的时候,再找 静态属性。

class Person:
    """定义一个人类"""
    # 定义一个属性、用来存储 国民生产总值
    gdp = 100000000

    def __init__(self, name):
        self.name = name


if __name__ == '__main__':
    # 创建一个 人类对象
    p = Person("张三")
    #  p.gdp + 10000 中的 gdp 是 静态属性 ,赋值 后的 p.gdp 是 成员属性
    p.gdp = p.gdp + 10000
    # 这里面的两个 gdp 均为成员属性。
    p.gdp = p.gdp - 5000

    print(p.gdp,  Person.gdp)   # 100005000 ,  100000000

类方法

基于 类的方法 。 在 方法上 使用 @classmethod 装饰器 标记一个方法是 类方法 , 此时 类方法的 第一次参数 必须是 cls (名字不是固定的、是约定俗成的) 代表 当前类

class Person:
    """定义一个人类"""
    # 定义一个属性、用来存储 国民生产总值
    gdp = 100000000

    def __init__(self, name):
        self.name = name

    @classmethod
    def print_gdp(cls, cont="中国"):
        """这是一个基于类的方法、方法的第一个参数代表 当前类"""
        print(f"{cont}国民生产总值是{cls.gdp}")


if __name__ == '__main__':
    # 创建一个 人类对象
    Person.print_gdp("日本")

静态方法

基于 类的 方法 。 在 方法上 使用 @staticmethod 装饰器 标记一个方法是 静态方法 、静态方法 不需要 将 类 作为 参数 传入 。

class Person:
    """定义一个人类"""
    # 定义一个属性、用来存储 国民生产总值
    gdp = 100000000

    def __init__(self, name):
        self.name = name

    @staticmethod
    def print_gdp(cont="中国"):
        print(f"{cont}国民生产总值是{Person.gdp}")


if __name__ == '__main__':
    # 创建一个 人类对象
    Person.print_gdp("日本")

静态/类 方法 中 不能调用 和 对象相关的 属性 和 方法 、因为 类 属性 类方法 和 类有关 、产生的时间要早于对象。

对象 获取 类型 可以通过 __class__ 来获取 , 也可以 通过 type 函数 获取 。

反射函数

  • hasattr(obj, attr) : 判断 obj 对象中是否有 attr(字符串) 属性

  • setattr(obj, attr, value) : 给 obj 对象动态添加 attr (字符串) 属性、 且值 为 value

  • getattr(obj, attr) : 获取 obj 对象中的 attr (字符串)属性的 值

  • delattr(obj, attr) : 删除 Obj 对象 的 attr(字符串) 属性

    魔术属性

  • __name__ : 可以应用与 模块 (模块名) 、 函数 (函数名) 、 类 (类名) 、变量 (变量名)

  • __class__ : 获取 对象的 类型

  • __module__ : 获取模块信息

魔术方法

  • __init__ :构造方法 、 用来创建对象 和 做 属性 初始化

  • __del__ : 析构方法、 用来销毁对象 前 执行的逻辑

  • __str__ : 将 对象 以 字符串的形式表示 、该方法必须 字符串 , 当 调用 str 函数 会自动执行 该 魔术 方法

  • __repr__ : 将 对象 以 字符串的 原意 形式表示 、一般可以通过 eval 函数 将字符串 还原成 对象。

  • __len__ : 获取 长度 、 当调用 len函数的会自动 执行 该魔术方法

  • __iter__ : 获取一个对象的 可迭代表示形式。(可以让对象变成可迭代的)

    每次 使用 for … in 遍历 对象的时候, 都会 执行 __iter__ 函数。 如果 __iter__ 函数返回一个迭代器,则直接遍历迭代器中的数据、如果返回一个对象,则会调用 该对象中的 __next__ 完成数据的迭代。

    def __iter__(self):
      """告诉 Python 解析器, 当前对象是一个 迭代器"""
      ret = []  # 定义一个空列表
      node = self.__first  # 2个值
      # 使用 while 遍历链表
      while node is not None:
          ret.append(node.value)
          node = node.next
    
      return iter(ret)
    
  • __next__ : 获取 迭代器中 下一个数据 , 在自己写的类中,一般 重写了 __iter__(标记是一个迭代器) 、建议 也提供 __next__ (负责使用next函数取数据)

    def __iter__(self):
        """告诉 Python 解析器, 当前对象是一个 迭代器"""
        self.__next_index = 0
        self.__next_node = None
        return self
    
    def __next__(self):
        """负责迭代数据"""
        if self.__next_index == 0:
            self.__next_node = self.__first
    
        if self.__next_node is None:
            raise StopIteration()
        val = self.__next_node.value
        self.__next_index += 1
        self.__next_node = self.__next_node.next
        return val
    
  • 算术运算符重载

    • __add__+ 符号的重载 、可以实现 2个对象 相加
    • __sub__- 符号的重载 、可以实现2个对象 相减
    • __mul__ : * 符号的重载 、可以实现 乘法 运算
    • __divmod__ : 当调用 divmod 函数的时候,会执行该函数 、该函数返回一个 元组、第一个值代表 商、 第二个值代表余数
    • __mod__ : % 符号的重载 、可以实现 求余数
    • __truediv__ : / 符号的重载, 可以实现 除法运算。
    • __floordiv__ : // 整除符号的重载、可以实现 整除运算。
    • __pow__** 幂运算符的重载
  • 关系运算符的重载

    任何 自定义的 类 都默认提供了 __eq____ne__ 这两个魔术方式

    • __eq__ : == 符号的重载 、可以用来比较 2个对象的内容是否相等

      在 python 中 == 可以比较2个对象的内容是否相等、但如果是 自定义的类、 == 默认比较的是地址 。

      如果 一个对象 希望 能够 存储到 集合中 、并实现 自动去重, 那么 必须 提供 __eq____hash__

      集合的去重原理: 当一个元素 向 集合中添加的时候, 会首先 调用 hash 函数 获取对象的 hash值, 如果 hash值不同, 则 被认为是 不同的对象(不会去重)

      如果 hash值 相同 、且 == 比较内容 也相同, 则 认为是 相同对象,会进行 自动去重。

      hash 算法: 内容相同的 对象 hash值 必须 相同 , 内容不同的对象 尽可能地 hash 值 不同 。

      def __eq__(self, other):
          """
           1.  如果 other 是 None , 直接返回 False
           2.  如果 other 和 self 是同一个对象, 直接返回 True
           3.  判断 other 是否 和 self 是 相同类型 。如果不是 ,直接返回 False
           4.  如果 是 同种类型、 则 逐个 比较 属性 值 是否 相等
          """
          if other is None:
              return False
          if other is self:
              return True
          if type(other) != self.__class__:
              return False
          # 逐个比较 2个对象中的属性是否相等
          if len(self) != len(other):
              return False
      
          index = 0
          node_a, node_b = self.__first, other.__first
          while index < self.__size:
              if node_a.value != node_b.value:
                  return False
              else:
                  node_a = node_a.next
                  node_b = node_b.next
              index += 1
          return True
      
    • __ne__ : != 符号的重载 、可以用来比较2个对象内容是否不相等

    • __gt__> 符号的重载 , 可比较 2个对象的大小

    • __ge__ : >= 符号的重载

    • __lt__ : < 符号的重载

    • __le__ : <= 符号的重载

  • 切片

    • __getitem__ : 根据索引 获取数据、 或者 切片 获取数据

      def __getitem__(self, item):
         """切片 / 索引 提取数据"""
         if isinstance(item, int):
             # 如果 item 是 int 类型, 代表 从索引中 获取数据
             return self.get(item)
         # 如果不是 int , 那么一定是 slice 对象 , 获取 start, stop, step
         start = item.start or 0  # 如果 start 是None, 则 取 0
         stop = item.stop or self.__size  # 如果 stop 是 None ,则取长度
         step = item.step or 1  # 如果 step 是 None , 则 取 1
         # 创建一个新的链表
         # 遍历 链表中所有的数据
         _lst = [x for x in self][start:stop:step]
         # 将列表中的数据 存储到 链表中
         return self.__class__.of(*_lst)
      
    • __setitem__: 根据索引 修改数据、 或者 切片 修改数据

      def __setitem__(self, key, value):
         if isinstance(key, int):
             self.set(key, value)
         else:
             # 如果不是 int , 那么一定是 slice 对象 , 获取 start, stop, step
             start = key.start or 0  # 如果 start 是None, 则 取 0
             stop = key.stop or self.__size  # 如果 stop 是 None ,则取长度
             step = key.step or 1  # 如果 step 是 None , 则 取 1
      
             # 判断 step 是否是 1 或者 -1 , 如果是,说明是一个连续 切片,直接将 value 替换掉 切片对应的区间数据
             if step in [1, -1]:
                 # 遍历 start, stop ,删除 里面的所有元素
                 for index in range(start, stop, step):
                     self.remove(start)
      
                 # 删除所有数据后、在将整个 value 添加进去
                 for index in range(len(value), 0, -1):
                     self.add(value[index - 1], start)
             else:
                 # 使用 for range 遍历数字、替换不连续的 数据
                 _i = 0
                 for index in range(start, stop, step):
                     self.set(index, value[_i])
                     _i += 1
                 else:
                     if _i != len(value):
                         raise IndexError
      
    • __delitem__

  • __new__ : 创建对象 、该方法 执行 时间 早于 __init__

    是在 创建对象前 执行的代码 , 它的参数代表 创建对象的时候 init需要的参数, 该函数 必须返回一个 创建的对象 。super().__new__(cls) 创建 cls 类型的对象(没有任何属性)

    __new__ 可以控制 对象创建的 行为 和 方式

    • 生成私有属性和 property 属性

      def outter_fget(obj, attr):
        return lambda obj: getattr(obj, f"__{attr}")
      

    def outter_fset(obj, attr):

    return lambda obj, value: setattr(obj, f"__{attr}", value)
    

    class Cat:

    __attr_list__ = ("name", "age")
    
    def __new__(cls, *args, **kwargs):
        # 创建一个 没有任何属性的 对象
        instance = super().__new__(cls)
        # 1. 给 创建的对象 自动添加 __name , __age 私有属性
        # 遍历 __attr_list__ 属性,获取要 动态追加的 私有属性
        for field in cls.__attr_list__:
            setattr(instance, f"__{field}", kwargs.get(field))
            # 2. 并 给 对象 提供 对应的 property 属性 (需要使用闭包解决 field属性名问题)
            fget = outter_fget(instance, field)
            fset = outter_fset(instance, field)
            # 将它作为 property 属性
            setattr(cls, field, property(fget, fset))
        return instance
    

    if name == ‘main’:

    cat = Cat(name="波斯猫")
    cat.name = "加菲猫"
    print(cat.name)
    cat.age = 20
    print(cat.age)
    
- 单例模式 : 保证一个类 永远只能产生一个对象
```python
class A:

    def __new__(cls, *args, **kwargs):
        # 如果 当前类的 对象 不存在 , 创建对象 , 如果存在, 直接返回已存在的对象。
        # 判断 cls 中 有没有一个 属性 叫 __instance (名字随意)
        if not hasattr(cls, f"_{cls.__name__}__instance"):
            cls.__instance = super().__new__(cls)

        return cls.__instance

    def __init__(self):
        print("正在执行 init ")


if __name__ == '__main__':
    a = A()
    a2 = A()

    print(a is a2)
  • __call__ : 对象可调用, 可以配合 __init__ 让 类 拥有 闭包的效果, 从而让类可以作为装饰器应用。

    class Timer:
    
        def __init__(self, func):
            self.__func = func
    
        """装饰器、计算函数的执行时间"""
    
        def __call__(self, num):
            # 获取当前执行函数前的 时间戳
            start_time = time.time()
            # 调用目标函数
            ret = self.__func(num)
            # 获取目标函数的执行时间
            end_time = time.time()
            # 打印 目标函数的执行时长
            print(f"函数{self.__func.__name__}执行的时间是{end_time - start_time}、参数是{num}, 返回的结果是{ret}")
            return ret
    

    @Timer
    def abc(x):

    print("abc", f"x={x}")
    

    if name == ‘main’:

    x = abc("123")
    print(x)
    
<br/>

## 继承

>  描述的是 类 与 类 之间的 关系 、 如果 一个类 和 另一个类 拥有 包含 和 被包含的 关系, 那么 在 编程 中 就可以 将这两个 描述为 父和子的关系,  一个类 A 包含 一个类 B ,那么 A 就是 父类,  B 是 子类 。
> 
> 那么 B 此时就 继承了 A。  在 Python 中 一个类 可以 继承 多个 类 。 Python 采用 多继承 , 多继承形成 网状结构,关系相对复杂, 而 Java 语言 采用 单继承、单继承 形成 树状结构、关系相对简单 。

```python
class Animal:
  """定义所有子类的共性"""

  def __init__(self, state=True):
      # 生存状态
      self.state = state

  def eat(self):
      print("动物在吃饭")


class Dog(Animal):

  def __init__(self, skin, tail, state=True):
      # 初始化 父类中定义的属性。。。
      super().__init__(state)
      # 皮肤(毛发)
      self.skin = skin
      # 尾巴
      self.tail = tail


if __name__ == '__main__':
  # 创建一个 dog 对象
  dog = Dog("黄色", "长尾巴")

  # print(dog)
  print(dog.state)
  dog.eat()

子类可以在定义的时候使用 小括号 的方式 定义继承的父类 、 父类中定义的方法 子类可以直接继承下来不需要再定义、 父类中定义的属性 需要子类在进行初始化的时候 进行 初始化!

继承特点

  • 多继承
  • 可以继承 父类中定义的 公开属性、受保护属性 (在 子类中 必须 完成 对 父类的初始化操作 )
  • 可以 继承 父类中定义的 公开方法、受保护方法
  • 不允许 继承 父类中定义的 私有 方法、 私有属性 。

方法的重写(覆盖)

当 父类 中 提供的 方法 不满足 子类的 需求的时候 ,那么 子类 可以 重写 父类 中 定义的方法 。

抽象类

是一个特殊的类、特殊在 抽象类中有抽象方法, 抽象类 必须继承 abc.ABC 类

抽象方法: 一个方法不需要做任何代码时候,只负责定义规则,如果一个方法希望是抽象方法,只需要添加一个 abc.abstractmethod 装饰器即可。 抽象方法 是一个半成品 方法,需要子类重写 才能够实现!

如果一个类是抽象类, 且抽象类中有 抽象 方法, 那么 该抽象类 不允许创建对象。

import math
from abc import abstractmethod, ABC


class Shape(ABC):
    """图形类"""

    @abstractmethod
    def area(self):
        """计算面积"""
        pass

    @abstractmethod
    def perimeter(self):
        """计算周长"""
        pass


class Circle(Shape):
    """圆形"""

    def __init__(self, radius):
        """定义一个半径"""
        self.radius = radius

    def area(self):
        """求圆的面积"""
        return math.pi * self.radius * self.radius

    def perimeter(self):
        """求圆的周长"""
        return 2 * math.pi * self.radius
;