一、面向对象思维
今天把python的类与对象看完了,简单的复习一下
- 首先是面向对象(OOP):就是关注谁来做,定义好一个个对象,让对象自己来完成任务
2.__init__方法是对象的出厂配置流水线,p = Person()时自动触发调用,不是必须的,其中的self就是刚在内存里被开辟出来的空白对象
3 类属性:就是写在类里,所有对象共享
实例属性:写在__init__里,通过self.绑定,各个对象互不干扰
实例方法:必须带self,权限最大,能访问类属性和实例属性
类方法:由装饰器@classmethod装饰器修饰,第一个参数必须是cls,只能操作公共属性,类名直接调用
静态方法: 由装饰器@staticmethod装饰器修饰,不需要self和cls,只是挂在类里的纯工具函数(如时间转换)
4 python中魔法方法(有没有感觉听起来很好玩)
就是像__init__一样前后都有两个下划线的方法,就像自动感应器,有特定的动作发生,它们就会自动触发
__new__: 它是对象实例化时第一个被调用的方法,真正负责在内存中“捏”出这个对象 。它执行完了,才会轮到__init__ 去做初始化打扮。
__del__: 当对象没用了被垃圾回收机制清理时自动调用。手动用 del xxx 删对象时,如果还有别人在使用它(引用计数不为0),这个方法不会被立刻触发 。
__str__和__repr__: str() 和 repr() 最主要的差别在于⽬标⽤户。 repr() 的作⽤是产⽣机器可读的输出(如Python代码),⽽ str() 则产⽣⼈类可读的输出。
class Cat:
安装“诞生感应器”
def __init__(self):
print("--> 诞生感应器触发:喵!我出生了!")
安装“打印感应器”
def __str__(self):
return "--> 打印感应器触发:我是一只可爱的小猫咪。"
# =======================
动作 1:创造一只猫
注意看:我根本没有写 cat.__init__(),但这行代码运行后,会自动打印第一句话!
my_cat = Cat()
动作 2:打印这只猫
注意看:我根本没有写 cat.__str__(),只是用了最普通的 print,它就自动变成了第二句话!
print(my_cat)
5 在类的外面添加实例方法:将普通函数专门绑定给张三对象,必须使用types.MethodType(方法名,实例对象),而且实例方法必须有self这个参数
import types
class Person:
def __init__(self, name=None):
self.name = name
def eat(self):
print(f"{self.name}在吃饭")
p = Person("张三")
p.eat = types.MethodType(eat, p)
p.eat() # 张三在吃饭
添加类方法/静态方法 : 直接把外面的函数赋值给类名,添加类方法要传入cls
class Person:
home = "earth"
def __init__(self, name=None):
self.name = name
# 定义类⽅法
@classmethod
def come_from(cls):
print(f"来⾃{cls.home}")
# 定义静态⽅法
@staticmethod
def static_function():
print("static function")
# 动态给类添加⽅法
Person.come_from = come_from
Person.come_from() # 输出: 来⾃earth
Person.static_function = static_function
Person.static_function() # 输出: static function
删除属性与方法 : del 对象.属性名 delattr(对象,属性名)
6 __slot__ : 用来限制实例属性和实例方法的,python中每个实例都是有一个字典来存储的,这样子很灵活,但是也很消耗内存,__slot__ = ("name","age","eat"),可以修改底层结构,不用字典,只用一个固定大小的空间,当然类属性、类方法和静态方法还可以添加,对子类也无效。
面向对象三大特性
1.封装
将变量和函数写入类中的操作即为封装,可以把细节隐藏起来,只暴露出必要的接口使调用者调用
私有化:单下划线,只是一个约定,没有强制力,双下划线,原理是改名,使用_类名__x仍然可以访问
属性和方法都可以私有化
class Person:
def __init__(self, name, age):
self.name = name
# 单下划线: 约定当前属性是不想被暴露的
# self._age = age
self.__age = age # 底层使用改名的方式实现私有化
def __cry(self):
print("我在偷偷哭,别人不可以看到")
p1 = Person("xzx",23)
print(p1.name)
# print(p1._age) # 不具备约束性,依然可以拿到
# print(p1.__age) # 会报错,'Person' object has no attribute '__age'
print(p1._Person__age) # 23
p1._Person__cry()
property装饰器 : 把类里的一个方法,伪装成一个属性,让代码既安全又优雅。
传统的面向对象思路: 加双下划线,然后使用get和set方法,好用但是啰嗦
@property解决这个问题
class Person:
def __init__(self, name, age):
self.name = name
self.__age = age
# 1. Getter 获取器:读取 age 时触发
@property
def age(self):
if self.__age > 18:
return 18
return self.__age
# 2. Setter 设置器:给 age 赋值时触发
@age.setter
def age(self, age):
# 只有传入的年龄 <= 18 时,才允许修改底层真实数据
if age <= 18:
self.__age = age
p1 = Person(name="zsh", age=60)
print(p1.age) # 输出: 18
# 直接给属性赋值:
p1.age = 20
但是property装饰器只能够读属性,如果想要修改属性,还需要用到setter装饰器,方法名必须一致(就是把私有化的方法去掉双下划线),Python 会根据它们头顶上的装饰器,自动区分它们是用来获取、设置还是删除的。
2.继承
子类(派生类)可以继承父类(基类)中的属性和方法,实现代码重用,可以新增方法,也可以重写父类的方法
子类不能继承私有方法和属性,但是可以通过改名原则去调用,但是不建议(违背了封装原则)
单继承 : 创建实例时,python先会去当前类找 __init__方法,找不到向上去父类寻找
class Person:
home = "earth" # 公共类属性
def __init__(self, name):
self.name = name # 公共实例属性
def eat(self):
print(f"{self.name} 正在吃饭...") # 公共方法
# 子类继承父类 Person
class YellowRace(Person):
color = "yellow"
class WhiteRace(Person):
color = "white"
class BlackRace(Person):
color = "black"
# 简化后的测试代码:批量实例化并打印
people = [
YellowRace("张三"),
WhiteRace("李四"),
BlackRace("王五")
]
for p in people:
print(f"姓名: {p.name} | 肤色: {p.color} | 故乡: {p.home}")
p.eat()
print("-" * 25)
多继承 : 调用方法时先在子类中查找,若不存在则从左到右依次查找父类中是否包含方法。
class Person:
"""人的类(爷爷辈)"""
home = "earth"
def __init__(self, name):
self.name = name
def eat(self):
print("eating...")
class YellowRace(Person):
"""黄种人(父类2)"""
color = "yellow"
def run(self):
print("running...")
class Student(Person):
"""学生(父类1)"""
def __init__(self, name, grade):
self.name = name
self.grade = grade
def study(self):
print("studying...")
# ================= 核心:多继承 =================
class ChineseStudent(Student, YellowRace):
"""中国学生(同时继承了 Student 和 YellowRace)"""
country = "中国"
# 测试代码
y1 = ChineseStudent("张三", "三年级")
print(f"老家:{y1.home}, 肤色:{y1.color}, 国家:{y1.country}, 姓名:{y1.name}, 年级:{y1.grade}")
y1.eat() # 来自 Person
y1.run() # 来自 YellowRace
y1.study() # 来自 Student
复用父类方法
使用super().方法名(),它会自动根据MRO(方法解析顺序向上去寻找父类中的eat方法)
MRO(方法 :ChineseStudent -> Student (左爸爸) -> YellowRace (右爸爸) -> Person (爷爷) -> object (所有类的老祖宗,Python 自动加的)。
def study(self):
print("先吃再学")
super().eat() # 魔法就在这行
print("studying...")
父类名.方法名(self):直接找到具体的类名,然后调用里面的方法
def study(self):
print("先吃再学")
Person.eat(self) # 指名道姓让 Person 执行
print("studying...")
方法重写(override) : 在子类里定义一个和父类一模一样名字的方法。
重写__init
-
父类 Person 的 init 负责给对象发“身份证”(绑定 self.name = name)。
-
子类 Chinese 为了增加一个“籍贯”属性,重写了 init(self, name, area)。
-
灾难发生: 因为子类重写了 init,Python 执行子类的初始化后就下班了。父类的 init 没有被执行!结果就是:这个 Chinese 对象成功拥有了籍贯(area),但失去了名字(self.name 没有被绑定)。
-
解决办法:在子类的方法内部,使用super()调用一下父类的init方法
多态
本质 : “同一个指令,发给不同的对象,会产生完全不同的反应。”
好处:下面代码中,如果直接用duck.speak()也行,但是如果新增一个小兔子,那么很多地方都要改代码,但是如果使用let_it_speak(),只需要新建一个小兔类,重写一下speak方法就行。