# 面向对象
在 编程领域中 ,有多种 开发模式/风格 , 比较常见的 有 面向过程 、 面向对象 、 函数式编程 等 手段 。
在 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