面向对象编程-进阶
面向对象编程-封装
封装(encapsulation)就是把抽象出的数据[属性]和对数据的造作[方法]封装在一起,数据保护在内部。
程序只有通过被授权的操作,才能对数据进行访问。
优点:隐藏实现细节、可以对数据进行验证(直接访问赋异常值被攻击)、保护数据隐私。
私有成员
默认情况下,类中的变量和方法都是共有的,在类的外部、类的内部都可以正常访问
私有化:将变量或方法以__
开头,只能在本类内部使用,类的外部无法使用
如何访问私有属性/方法:提供公共的方法,用于对私有成员的操作
- Python语言的动态特性,会出现伪私有属性的情况
class Clerk:
name = None
__job = None
__salary = None
def __init__(self, name, job, salary):
self.name = name
self.__job = job
self.__salary =salary
def set_job(self, job):
self.__job = job
def get_job(self):
return self.__job
clerk = Clerk("tiger", "python工程师", 20000)
clerk.__job = "java工程师" 动态创建__job属性
print(clerk.name) #tiger
print(cler.__job) #伪私有属性 python工程师
print(clerk.get_job) #java工程师 debug可以看出实际属性是 _Clerk__job
面向对象编程-继承
继承可以解决代码复用,并让编程更加靠近人类思维。当多个类存在相同的属性(成员变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有子类无需重新定义。父类/基类-子类/派生类。
class DrivedClassName(BaseClassName[, Base2, Base3...]):
statements
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
- Python编程语言中,
object
是所有其它类的基类,通过ctrl+H
可以查看类的继承关系 - Python支持多继承(Java单继承),多重继承中,遵守从左到右的继承优先级
调用父类成员
如果子类和父类出现同名成员,访问父类成员的方式:
父类名.成员变量
父类名.成员方法(self) #需要把自己传过去
super().成员变量
super().成员方法()
- 子类不能直接访问父类的私有成员
- 访问不限于直接父类,而是建立从子类向上级父类的查找关系
- 建议使用
super()
的方式,因为如果使用父类名方式,一旦父类名修改,调用也需修改
class A:
n1 = 100
def run(self):
print("A-run()...")
class B:
n1 = 100
def run(self):
print("B-run()...")
def say(self):
print(f"父类的n1{A.n1} 本类的n1{self.n1}") # self.属性名 先在本类找,若无去父类找
A.run()
self.run() # self.方法名()先在本类找,若无去父类找
def hi(self):
print("父类的n1 {super().n1}")
super().run()
方法重写
重写又称覆盖(override),即子类继承父类的属性和方法后,根据业务需要,再重新定义同名的属性或方法。鼠标停留在重写代码行左边的环形图标可看到提示
类型注解-type hint
忘记方法参数类型,python是解释型语言,只有运行时才能发现问题,因此需要对类型提示的支持。
· 类型提示,防止运行时出现参数类型、返回值类型、变量类型不符合。
· 作为开发文档附加说明,方便使用者调用时传入和返回参数类型。
· 加入后并不会影响程序的运行,不会报正式的错误,只有提醒。
· PyCharm支持类型注解,参数类型错误会给黄色三角感叹号提示。
def f(a: str):
for i in str:
print(i)
变量类型注解
变量: 类型 # 变量类型注解
实例对象: 类型(类名) # 实例对象类型注解,注解父类即可使用子类
my_list: list = [1, 2, 3] #容器类型注解
my_tuple: tuple[str, float] = ["jack", 0.1]
my_set: set[str] = {"jack", "tom"}
my_dict: dict[str, int] = ["no1":1, "no2":2]
# 在注释中使用注解
代码 # type:类型
函数(方法)类型注解
def 函数/方法名(形参名:类型, 形参名:类型...) -> 返回值类型:
函数/方法体
Union联合类型注解
联合类型 Union[type1, type2, ...]
等价于 type1 | type2 | ...
,意味着满足几个类型之一即可。在变量、函数(方法)都可以使用联合类型注解。使用时需先导入from typing import Union
面向对象编程-多态
需求引入:
class Food: # 父类食物
name = None
def __init__(self, name):
self.name = name
class Fish(Food): # 食物子类鱼类
pass
class Animal: #父类动物
name = None
def __init__(self, name)
self.name = name
class Cat(Animal): #动物子类猫
pass
class Master: # 主人类
name = None
def __init__(self, name):
self.name = name
# 问题: 每次扩展需增加动物子类、食物子类、主人喂食方法
def feed_cat(self, cat: Cat, fish: Fish):
print(f"主人{self.name}给动物{cat.name}喂的食物是{fish.name}")
cat = Cat("Bosi")
fish = Fish("liyu")
xiaoming = Master(xiaoming)
xiaoming.feed_cat(cat, fish)
多态:不同的对象调用相同的方法,表现出不同的状态,通常作用在继承关系上
例如:一个父类具有多个子类,不同的子类对象调用相同的方法,执行的时候产生不同的状态(类型)
优点:灵活,无论对象变化,同一种形式调用;扩展性,通过继承创建新类,再调用,无需改原代码(业务执行操作)
特点
· Python中函数/方法的参数是没有类型限制的,所以多态在python中体现并不是很严谨(相对java等强类型语言);
· python并不要求严格的继承体系,关注的不是对象的类型本身,而是它是否具有要调用的方法(行为)
class AA:
def hi(self):
print("AA-hi()...")
class BB:
def hi(self):
print("BB-hi()...")
def test(obj): # 根据传入参数动态决定类型
obj.hi()
aa = AA()
bb = BB()
test(aa) # AA-hi()... #无继承关系,仅关注调用方法
test(bb) # BB-hi()... #不同对象调用同一方法表现不同的状态
需求问题解决
class Master: # 主人类
name = None
def __init__(self, name):
self.name = name
# 解决: 每次扩展仅需增加动物子类、食物子类
def feed(self, animal: Animal, food: Food):
print(f"主人{self.name}给动物{animal.name}喂的食物是{food.name}")
# 不同的主人对象调用同一方法,传入不同类型的参数,返回不同状态类型
cat = Cat("Bosi")
fish = Fish("liyu")
xiaoming = Master(xiaoming)
xiaoming.feed(cat, fish)
isinstance函数
isinstance()
用于判断对象是否为某个类或其子类的函数
isinstance(object, classinfo) # object对象 是不是 classinfo(类名、基本类型或由它们组成的元组) 的(元组中某个)类或子类
num = 9
print(f"num是不是str/int/list: {isinstance(num, (str, int. list))}")
魔术方法
魔术方法:Python中所有以双下划线__
包起来的方法。
魔术方法是一种特殊方法,普通方法需要调用,魔术方法在类或对象的某些事件发生时会自动执行,如果希望根据自己的程序定制特殊功能的类,就需要对这些方法进行重写
python中常用的运算符、for循环、以及类操作等都是运行在魔术方法之上的
常见的魔术方法
__str__
打印对象默认返回:类型名+对象内存地址,默认情况下返回对象的属性信息。
<__main__.Person object at 0x0000019314BC7C90> # 类型Person 当前对象地址(10进制内存地址的16进制形式表达,同hex(id(my_obj)) ), 实际是父类object的__str__方法
实际开发中往往重写__str__
方法,print(对象)或str(对象)时,都会自动调用该对象的__str__
方法。
class Monster:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def __str__(self):
return f"{self.name} {self.age} {self.gender}"
m = Monster("青牛怪", 500, '男')
print(m)
__eq__
== 是一个比较运算符:对象之间进行比较时,比较的是内存地址是否相等,即判断是不是同一个对象
重写 __eq__
方法,可以用于判断对象内容/属性是否相等
class Person:
def __init__(self):
self.name = name
self.age = age
self.sex = sex
# def __eq__(self, other): #p1 == p2 true
#if isinstance(other, Person): #否则不同类属性相同也会判断true
# return self.name == other.name and \
# self.age == other.age and \
# self.sex == other.age
#else:
# return False
p1 = Person("smith", 20, "男")
p2 = Person("smith", 20, "男")
print(f"p1==p2: {p1 == p2}") # false 对象地址不同,属性地址相同
Class对象和静态方法
Class对象
类本身也是对象,即:Class对象(万物皆对象)。可以通过未实例化的类对象访问属性、调用非静态方法className.func(className)。
静态方法
装饰器@staticmethod
将方法转为静态方法,静态方法不会接收隐式的第一个参数self,声明静态方法的语法如下。
class C:
@staticmethod
def f(arg1, arg2...):...
静态方法可以有类调用C.f()
,也可以有实例调用C().f()
(匿名实例):
class Monster
@staticmethod
def ok():
print("ok()...")
Nonster.ok() #不需要实例化,通过类调用静态方法
# 通过实例对象调用静态方法
monster = Monster()
monster.ok()
抽象类
需求:当父类的某些方法需要声明,但又不确定如何实现;不需要实例化父类对象,父类主要用于设计和制订规范,让其它类来继承并实现。
介绍:默认情况下python不提供抽象类,python附带一个为定义抽象类提供基础的模块abc。
- 抽象类需要继承继承ABC类(abc模块的ABC类
from abc import ABC
),并且需要至少一个用@abstractmethod
声明的抽象方法,抽象类不能实例化。 - 用子类继承抽象基类,并实现抽象方法(重写)。
模板设计模式
设计模式:在大量实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
模板设计模式:抽象类作为多个子类的通用模板
e.g:计算工作时间
使用设计模式前:
class AA:
def job(self):
start = time.time()
num = 0
for i in range(1, 800001):
num += 1
end = time.time()
print("计算任务执行时间:", (end - start))
class BB:
def job(self):
start = time.time() #epoch所有平台上都是1970-01-01 00:00:00 (UTC)
num = 0
for i in range(1, 900001):
num -= 1
end = time.time()
print("计算任务执行时间:", (end - start))
if __name__ == '__main__':
aa = AA()
aa.job()
bb = BB()
bb.job
使用设计模式后:
class Template(ABC):
@abstractmethod
def job(self):
pass
def cal_time(self):
start = time.time()
self.job()
end = time.time()
print("计算任务执行时间:", (end - start))
class CC(Template):
def job(self):
num = 0
for i in range(1, 800001):
num += 1
if __name__ == '__main__':
cc = CC()
cc.cal_time() #从父类继承下的方法
顶层环境判断__name__ == '__main__'
:当一个 Python 模块或包被导入时,name 被设为模块的名称——通常为 Python 文件本身的名称去掉 .py 后;而若模块是在顶层代码环境(程序运行入口)中执行的,则其 name 被设为字符串 ‘main’。