韩顺平 | 零基础快速学Python(13) OOP进阶

本文详细介绍了面向对象编程中的封装、私有成员、继承、多态、类型注解、魔术方法和抽象类等概念,以及Python中如何实现和应用这些特性。特别强调了Python的动态性和灵活性,以及模板设计模式在代码组织中的作用。
摘要由CSDN通过智能技术生成

面向对象编程-进阶

面向对象编程-封装

封装(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’。

  • 32
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值