Python学习笔记--面向对象编程基础知识

本文摘自朱雷老师所著《Python工匠》一书第9章内容,因为很多内容,阅读后依然一知半解,特做笔记予以记录而进一步加强认知。

Python是一门面向对象的编程语言,它为面向对象编程提供了非常全面的支持。但和其他编程语言相比,Python中的面向对象有很多细微区别。比如Python并没有严格的私有成员,大多数时候,只是给变量加上下划线_前缀。

和许多静态类型语言不同,Python遵循“鸭子类型”编程风格,极少对变量进行严格的类型检查。“鸭子类型”是一种非常实用的编程风格,但也有缺乏标准、过于隐式的缺点。为了弥补这些确定,可以用抽象类来实现更灵活的子类化检查。

在创建类时,除了可以同时继承多个基类,Mixin模式正是依赖这种技术实现的。但多重继承非常复杂、容易搞砸,使用时无比当心。

继承是面向对象的基本特征之一,但它也很容易被误用。应学会判断何时该使用继承,何时该用组合代替继承。

一、下面是学习第九章知识要点

(1)语言基础知识

  • 类与实例的数据,都保存在一个名为__dict__的字典属性中
  • 灵活利用__dict__属性,能帮助做到常规做法难以完成的一些事情
  • 使用@classmethod可以定义类方法,类方法常用做工厂方法
  • 使用@staticmethod可以定义静态方法,静态方法不依赖实例状态,是一种无状态方法
  • 使用@property可以定义动态属性对象,该属性对象的获取、设置和删除行为都支持自定义

(2)面向对象高级特性

  • Python使用MRO算法来确定多重继承时的方法优先级
  • super()函数获取的并不是当前类的父类,而是当前MRO链条里的下一个类
  • Mixin是一种基于多重继承的有效变成方式,用好Mixin需要精心的设计
  • 元类的功能相当强大,但同时也相当复杂,除非开发一些框架类工具,否则极少需要使用元类
  • 元类有许多更简单的替代品,比如类装饰器、子类化钩子方法等
  • 通过定义__init__subclass__钩子方法,你可以在某个类被继承时执行自定义逻辑

(3)鸭子类型与抽象类

  • “鸭子类型”是Python语言最鲜明特点之一,在该风格下,一般不做任何严格的类型检查
  • 虽然“鸭子类型”非常实用,但是它有两个明显的缺点——缺乏标准和过于隐式
  • 抽象类提供了一种更灵活的子类化机制,我们可以通过定义抽象类来改变isinstance()的行为
  • 通过@abstractmethod装饰器,可以要求抽象类的子类必须实现某个方法

(4)面向对象设计

  • 继承提供了相当强大的代码复用机制,但同时也带来了非常紧密的耦合关系
  • 错误使用继承容易导致代码失控
  • 对事务的行为而不是事务本身建模,更容易孵化出好的面向对象设计
  • 在创建继承关系时应当谨慎。用组合来替代继承有时候是更好的方法

(5)函数与面向对象的配合

  • Python里的面向对象不必特别纯粹,假如用函数打一点儿配合,可以设计出更好的代码
  • 可以像requests模块一样,用函数为自己的面向对象模块实现一些更易用的API
  • 在Python中,极少使用真正的“单例模式”,大多数情况下,一个简单的模块级全局变量对象就够了
  • 使用“预绑定方法模式”,你可以快速为普通实例包装出普通函数的API

(6)代码编写细节

Python的成员私有协议并不严格,如果你想标识某个属性为私有,使用你想标识某个属性私有,使用下划线前缀就可以了。

编写类时,类方法排序应该遵循某种特殊规则,把读者最关心的内容摆在前面

多态是面向对象编程的基本概念,同时也是最强大的思维工具之一

多态可能的介入时机:许多类似的条件分支判断、许多针对类型isinstance()判断

二、知识与技巧

(1)单例模式(singleton pattern)与预绑定方法模式(prebound methon pattern)

假设你在开发一个程序,它的所有配置项都保持在一个特定的文件中。在项目启动时,程序需要从配置文件中读取所有配置项,然后将其加载进内存供其他模块使用。

由于程序执行时只需要一个全局的配置对象,因此你觉得这个场景非常适合使用经典设计模式:单例模式(singleton pattern)。

下面的代码就应用了单例模式的配置类AppConfig:

class AppConfig:"""程序配置类,使用单例模式"""_instance = Nonedef __new__(cls):if cls._instance is None:inst = super().__new__(cls)# 省略,从配置外部配置文件中读取配置#...passcls._instance = instreturn cls._instancedef get_database(self):"""读取数据库配置"""passdef reload(self):"""重新读取配置文件,刷新配置"""passc1 = AppConfig()
c2 = AppConfig()print(c1 is c2)   # 输出结果:True

在Python中,实现单例模式的方式有很多,而上面这种最为常见,它通过重写类的__new__方法来接管实例创建行为。当__new__方法被重新后,类的每次实例化返回的不再是新实例,而是同一个已经初始化的旧实例cls._instance:

>>>c1 = AppConfig()

>>>c2 = AppConfig()

>>> c1 is C2

True

从上代码看,调用AppConfig()总是会返回同一个对象,基于上面设计,如果其他人想读取数据库配置,代码这样写:

from project.config import Appconfigdb_conf = AppConfig().get_database()
# 重新加载配置
AppConfig.reload()

虽然在处理这种全局配置对象时,单例模式是一种行之有效的解决方案,但在Python中,有一种更简单的做法——与绑定方法模式。

与绑定方法模式是一种将对象方法绑定为函数的模式。要实现该模式,第一步就是完全删除AppConfig里的单例设计模式。因为在Python里,实现单例压根儿不用这么麻烦,有一个随手可得的单例对象——模块(module)

当我们在Python中执行import语句导入模块时,无论import执行了多少次,每个被导入的模块在内存中只会存在一份(保存在sys.modules中)。因此要实现单例模式,只需要在模块里创建一个全局对象即可。

class AppConfig:"""程序配置类,使用单例模式"""def __init__(self):# 已省略:从外部配置文件读取配置...def get_database(self):"""读取数据库配置"""...def reload(self):"""重新读取配置文件,刷新配置"""...

上面代码完善删掉了单例模式的相关代码,只实现了__init__方法,_config就是我们的“单例AppConfig对象”,它以下划线开头命名,表面自己是一个私有全局变量,以免其他人直接操作。

下一步,为了给其它模块提供好用的API,我们需要将单例对象_config的共有方法绑定到config模块上:

# file: project/config.py
class AppConfig:"""程序配置类,使用单例模式"""def __init__(self):# 已省略:从外部配置文件读取配置...def get_database(self):"""读取数据库配置"""...def reload(self):"""重新读取配置文件,刷新配置"""...# 私有全局变量
_config = AppConfig()get_database_conf = _config.getdatabase
reload_conf = _config.reload

之后,其它模块就可以像调用普通函数一样操作应用配置对象了:

from project.config import get_database_conf# 载入数据库配置
db_conf = get_database_conf()
# 重新载入配置
reload_conf()

通过使用“预绑定方法模式”,既避免了复杂的单例设计模式,又有了更易使用的函数API。

(2)多态:在分支中寻找多态的应用时机

多态(polymorphism)是面向对象编程的基本概念之一。它表示同一个方法调用,在运行时会因为对象类型的不同,产生不同效果。比如一个animal类有一个方法bark(),在animal类实例化是Cat类型时,bark()方法发出“喵喵”叫,在animal类实例化是Dog类型时则发出“汪汪”叫。

多态很好理解,当我们看到设计合理的多态代码时,很轻松就能明白代码的意图。但面向对象编程的新手有时会处在一种状态:理解多态,但不知道何时该创建多态。

下面的类FancyLogger是一个记录日志的类:

# -*- coding: utf-8 -*-
from enum import Enum, autoclass OutputType(int, Enum):"""输出类型:枚举类型使用enum模块中的auto()方法,自动为枚举常量分配连续的整数。"""FILE = auto()  REDIS = auto()ES = auto()class FancyLogger:"""日志类:支持向文件、Redis、ES等服务输出日志"""_redis_max_length = 1024def __init__(self, ouput_type=OutputType.FILE):self.ouput_type = ouput_type...def log(self,message):"""打印日志"""if self.ouput_type == OutputType.FILE:...elif self.ouput_type == OutputType.REDIS:...elif self.ouput_type == OutputType.ES:...else:raise TypeError('非法的输出类型')def pre_process(self,message):"""预处理日志"""# Redis对日志最大长度有限制,需要进行裁剪if self.ouput_type == OutputType.REDIS:return message[:self._redis_max_length]   # 切片,裁剪message信息

FancyLogger类接收一个实例化参数:output_type,代表当前的日志输出类型。当输出类型不同时,log()和pre_process()方法会做不同的事情。

上面的FancyLogger类代码就是一个典型的应该使用多态的例子。

FancyLogger类在日志输出类型不同时,需要有不同的行为。因此,我们完全可以为“输出日志”行为建模一个新的类型:LogWriter,然后把每个类型的不同逻辑封装到各自的Writer类中。

对上面的3种输出类型,创建下面的3个Writer类,并对FancyLogger进行了简化,代码如下:

class FileWriter:def write(self, message):...class RedisWriter:max_length = 1024def _pre_process(self, message):# REDIS 对日志最大长度有限制,需要进行裁剪return message[: self.max_length]def write(self, message):message = self._pre_process(message)...class EsWriter:def write(self, message):...class FancyLogger:"""日志类:支持往文件、Redis、ES 等服务输出日志"""def __init__(self, output_writer=None):self._writer = output_writer or FileWriter()  # 默认输出类型:FileWriter...def log(self, message):self._writer.write(message)

上面代码,FancyLogger类使用多态特性,完全消除了原来的条件判断语句。最大的意义在于,利用多态的新代码扩展性更好。

假如想增加一种新的输出类型,在原来实现的FancyLogger类的代码中,需要修改其中Log()和pre_process()等方法,在代码中增加新的类型的判断逻辑。而在新的代码中,只需要增加一个新的Writer类即可,调用FancyLogger类,根据传入的output_type不同,多态会调用相关的Writer做匹配的动作。

注意:增加新的输出类型及相关功能,主类FancyLogger代码不用做任何修改,只需要增加新的Writer类,实现新Writer的实现逻辑即可。

另外,上面这些Writer类都没有继承任何基类,这是因为在Python中多态并不需要使用继承。如果你感觉不好,也可以选择创建一个LogWriter抽象基类。

深入思考多态时,会发现它是一种思维的杠杆,是一种“以少胜多”的过程。

比起把所有的分支和可能行,一股脑地塞进程序员的脑子里,多态思想驱使我们更积极地寻找有效的抽象,以此隔离各个模块,让它们之间通过规范的接口来通信。模块因此变得更容易扩展,代码也更容易理解。

找到使用多态的时机,当你发现自己的代码出现以下特征时:

  • 有许多if/else判断,并且这些判断语句的条件都非常类似;
  • 有许多针对类型的isinstance()判断逻辑。

问自己一个问题:代码是不是缺少了某种抽象?如果增加这个抽象,这些分布在各处的条件分支,是不是可以用多态来表现?如果答案是肯定的,就去找到那个抽象吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/657396.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

酒店|酒店管理小程序|基于微信小程序的酒店管理系统设计与实现(源码+数据库+文档)

酒店管理小程序目录 目录 基于微信小程序的酒店管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员模块的实现 (1) 用户信息管理 (2) 酒店管理员管理 (3) 房间信息管理 2、小程序序会员模块的实现 (1)系统首页 &#xff0…

kettle通过severice_name连接oracle数据源踩坑

最近在研究kettle做数据抽取核对,按照官网安装kettle后无法连接oracle 坑1:kettle 连接oracle的数据库名指的是sidname 而非severicename,前期一直使用severicename 如下始终报错 注意区分下: SID:一个数据库可以有多个实例&…

力扣hot100 组合总和 回溯 剪枝 组合

Problem: 39. 组合总和 文章目录 思路复杂度&#x1f496; Code 思路 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) &#x1f496; Code class Solution{List<List<Integer>> res new ArrayList<>();int x;// 全局targetin…

[GN] DP学习笔记板子

文章目录 Bitset滚动数组多重背包区间DP树形dp状压dp模拟退火 Bitset 使用bitset需要引用<bitset>头文件。 其声明方法为: std::bitset<N>s; (N为s长度)常用函数&#xff1a; b.any() 判断b中是否存在值为1的二进制位 b.none() 判断b中是否不存在值为1的二…

webassembly003 TTS BARK.CPP-02-bark_tokenize_input(ctx, text);

bark_tokenize_input函数 bark是没有语言控制选项的&#xff0c;但是官方的版本无法运行中文bark_tokenize_input会调用bert_tokenize函数&#xff0c;bark_tokenize_input函数对中文分词失效&#xff0c;也就是导致不支持中文的原因。 void bark_tokenize_input(struct bark_…

IP 层转发分组的过程

目录 IP 层转发分组的过程 1.1 基于终点的转发 1.2 最长前缀匹配 转发表中的 2 种特殊的路由 主机路由 (host route) 默认路由 (default route) 路由器分组转发算法 1.3 使用二叉线索查找转发表 IP 层转发分组的过程 1.1 基于终点的转发 分组在互联网中是逐跳转发的。…

Walrus 实用教程|Walrus + Gitlab,打通CI/CD 自动化交付!

Walrus file 是 Walrus 0.5 版本推出的新功能&#xff0c;用户可以通过一个非常简洁的 YAML 描述应用或基础设施资源的部署配置&#xff0c;然后通过 Walrus CLI 执行 walrus apply或在 Walrus UI 上进行import&#xff0c;将 Walrus file 提交给 Walrus server&#xff0c;由 …

AP5191 DC-DC宽电压LED降压恒流驱动器 摩托电动汽车驱动芯片

产品描述 AP5191是一款PWM工作模式,高效率、外围简 单、内置功率MOS管&#xff0c;适用于4.5-150V输入的高 精度降压LED恒流驱动芯片。输出功率150W&#xff0c; 电流6A。AP5191可实现线性调光和PWM调光&#xff0c;线性调 光脚有效电压范围0.55-2.6V. AP5191 工作频率可以通过…

visual studio2022专业版安装步骤

目录 一、Visual studio下载二、创建C#项目——Hello World三、专业版秘钥激活 一、Visual studio下载 首先进入下载官网 先下载2022专业版&#xff0c;等等后面还需要选环境 我勾选了以下几个和c#开发有关的&#xff0c;后面缺什么还可以再安装所有以少勾了问题也不大 然后…

c学习:sqlite3数据库操作

目录 获取sqlite3源码 c调用步骤 常用接口函数说明 例子 打开数据库&#xff0c;新建表&#xff0c;插入数据&#xff0c;查询数据&#xff0c;关闭数据库 查询数据需要在回调函数中获取 获取sqlite3源码 先下载c的sqlite3源码&#xff0c;https://www.sqlite.org/inde…

植物病害检测YOLOV8,OPENCV调用

【免费】植物病害检测&#xff0c;10种类型&#xff0c;YOLOV8训练&#xff0c;转换成ONNX&#xff0c;OPENCV调用资源-CSDN文库 植物病害检测&#xff0c;YOLOV8NANO&#xff0c;训练得到PT模型&#xff0c;然后转换成ONNX&#xff0c;OPENCV的DNN调用&#xff0c;支持C,PYTH…

【译】在 Mac 上加速 PyTorch 训练

写在前面 为什么突然深度介入大模型领域了 因为最近在评估大模型用于行业应用&#xff0c;通过 OpenCompass 排行榜了解到了很多大模型&#xff0c;像文心一言是自己深度试用过的&#xff0c;趁着这次评估&#xff0c;也体验或者通过其他团队的介绍了解了通义千问、清华智谱、…

React16源码: React中NewContext的源码实现

NewContext 1 &#xff09;概述 新的 context API 是一个组件化的使用方式 它就跟写其他的组件一样&#xff0c;像写jsx&#xff0c;通过标签的这种方式来赋值一些props还有去给子节点去拿到这个 conntext 的属性 context的提供方和订阅方都是独立的 在什么地方想要用到这个 c…

《Numpy 简易速速上手小册》第5章:Numpy高效计算与广播(2024 最新版)

文章目录 5.1 向量化计算5.1.1 基础知识5.1.2 完整案例&#xff1a;股票数据分析5.1.3 拓展案例 1&#xff1a;多维数组运算5.1.4 拓展案例 2&#xff1a;复杂函数的向量化应用 5.2 广播机制5.2.1 基础知识5.2.2 完整案例&#xff1a;二维数据与一维数据运算5.2.3 拓展案例 1&a…

vxe-table表格合并行和虚拟滚动冲突

项目一直用的vxe-table 2.0版本&#xff0c;支持表格的虚拟滚动&#xff0c;最近要做表格合并行功能&#xff0c;虚拟滚动便失效了&#xff0c;强行虚拟滚动&#xff0c;合并行会有错行现象。 vxe-table2.0给出的解释是&#xff1a;合并行不能和虚拟滚动一起使用。 目前找到两种…

华为VRP系统简介

因为现在国内主流是华为、华三、锐捷的设备趋势&#xff0c;然后考的证书也是相关的&#xff0c;对于华为设备的一个了解也是需要的。 一、VRP概述 华为的VRP(通用路由平台)是华为公司数据通信产品的通用操作系统平台&#xff0c;作为华为公司从低端到核心的全系列路由器、以太…

个人建站前端篇(一)项目准备初始化以及远程仓库连接

云风的知识库 云风网前端重构&#xff0c;采用vue3.0vite antd框架&#xff0c;实现前后端分离&#xff0c;实现网站的SEO优化&#xff0c;实现网站的性能优化 vite创建vue项目以及前期准备 Vite 需要 Node.js 版本 18&#xff0c;20。然而&#xff0c;有些模板需要依赖更高…

java生成dll,并利用c语言使用libcurl调用http接口

本文可能需要使用的环境和工具&#xff1a; c/ c和GCC编译器 (Windows) Cygwin或MinGW 本文运行环境为windows10&#xff0c;使用MinGW-W64-builds-4.2.0 curl-8.5.0 libcurl 可以在官网 http://curl.haxx.se/ 获得。 配置MinGW 将mingw.rar解压到D:&#xff0c;修改系统…

Java面试题之 IO(四)

Java面试题之 IO&#xff08;四&#xff09; 文章目录 Java面试题之 IO&#xff08;四&#xff09;随机访问流 文章来自Java Guide 用于学习如有侵权&#xff0c;立即删除 随机访问流 这里要介绍的随机访问流指的是支持随意跳转到文件的任意位置进行读写的 RandomAccessFile 。…

Java抽取Hive、HDFS元数据信息

文章目录 一、技术二、构建SpringBoot工程2.1 创建maven工程并配置 pom.xml文件2.2 编写配置文件 application.yml2.3 编写配置文件 application.propertites2.4 开发主启动类2.5 开发配置类 三、测试抽取Hive、HDFS元数据四、将抽取的元数据存储到MySQL4.1 引入依赖4.2 配置ap…