Python抽象基类:ABC谢谢你,因为有你,温暖了四季!
Python抽象基类:ABC谢谢你,因为有你,温暖了四季!
最近阅读了《Python Tricks: The Book》的第四章“Classes & OOP”,这一章节介绍了Python对面向对象编程的支持,内容包括“is”和“==”的区别、特殊方法“__str__”和“__repr__”的作用、自定义异常类、浅拷贝深拷贝、抽象基类以及实例方法和类方法的区别等。本文记录自己学习这些知识点的心得体会,重点讨论其中的实例方法、类方法和静态方法的应用场景、抽象类以及具名元组等内容。
实例方法、类方法和静态方法
实例方法、类方法和静态方法是三个不同的概念:
实例方法用于访问和修改类实例的属性,它的第一个参数是“self”。当通过类名调用实例方法时,需要提供类的实例作为参数(传一个对象作为参数);当通过类的实例调用实例方法时,Python会自动把实例方法绑定到调用方,实例方法的“self”参数就是调用它的类实例(可以类比functools.partial?):
class Pizza:
def __init__(self, size: int):
self.size = size def get_size(self) -> int:
return self.size Pizza.get_size() # TypeError, missing argument: 'self'
Pizza.get_size(Pizza(25)) # 25
Pizza(21).get_size() # 21,不用显式指定实例方法的“self”参数啦
类方法常被用作工厂方法,用于创建类的实例(可以类比Java中类的构造方法),它的第一个参数是“cls”。类的实例可能有多种构造方案,比如时间类,既可以通过指定年月日时分秒构造实例,也可以通过解析字符串“%Y-%m-%d %H:%M:%S”进行构造,而Python规定了一个类只能定义一个初始化方法“__init__”,这限制了根据类创建实例的灵活性。类方法是缓解该矛盾的有效方案:
from typing import List class Pizza:
def __init__(self, ingredients: List[str]):
self.ingredients = ingredients def __repr__(self): # 定义把对象转化为字符串的方法,!r表示调用变量的__repr__方法
return f'Pizza({self.ingredients!r})' @classmethod # 类方法使用@classmethod装饰器修饰
def margherita(cls):
""""玛格丽塔,一种披萨的名称"""
return cls(['mozzarella', 'tomatoes']) @classmethod
def prosciutto(cls):
"""火腿披萨"""
return cls(['mozzarella', 'tomatoes', 'ham']) Pizza(['mozzarella', 'tomatoes'])
Pizza.margherita() # Pizza(['mozzarella', 'tomatoes'])
Pizza.prosciutto() # Pizza(['mozzarella', 'tomatoes', 'ham'])
静态方法跟定义在相同模块中的函数没有明显的区别,它不依赖于类变量或实例对象的状态,需要使用“@staticmethod”装饰器修饰:
from typing import List
import math class Pizza:
def __init__(self, ingredients: List[str]):
self.ingredients = ingredients @staticmethod
def circle_area(radius: float) -> float:
return radius ** 2 * math.pi pizza = Pizza(['mozzarella', 'tomatoes'])
pizza.circle_area(4) # 50.27
Pizza.circle_area(4) # 50.27
抽象类
抽象类是指声明了抽象方法的类,它不能被实例化,但可以被继承。当抽象类被继承时,子类往往需要实现父类的所有抽象方法(如果只实现部分抽象方法,那子类也是抽象类)。抽象类常用于类型检查,即判断给定的类是否由某个基类派生(issubclass())或给定的对象是否为某个基类的实例(isinstance())。
import collections
issubclass(list, collections.abc.Iterable) # True
issubclass(list, collections.abc.Hashable) # False
a = [1, 0, 2, 4]
isinstance(a, collections.abc.Sequence) # True
isinstance(a, collections.abc.Mapping) # False
此外,抽象类的使用能够让类的层次关系变得清晰,方便代码的开发和维护;抽象类声明接口,子类给出具体实现,子类实例的行为变得可以预期。举例来说,抽象类“collections.abc.Sized”声明了抽象方法“__len__”,Python的内置类型list、tuple、set、dict等都继承自该抽象类,因此可以通过“len()”函数获取这些类型的实例的大小。
早期Python通过在方法的定义体中抛出“NotImplementedError”异常的方式来声明抽象方法,抽象基类(Abstract Base Classes,ABC)出现以后,有了更好的方案。
通过抛出“NotImplementedError”异常定义抽象类
class Base:
"""基于NotImplementedError异常的抽象类"""
def foo(self):
raise NotImplementedError() b = Base() # 虽然名为抽象类,但还是可以被实例化
通过继承“abc.ABC”定义抽象类
import abc # 导入抽象基类模块 class Base(abc.ABC):
""""通过继承abc.ABC定义抽象类""" @abc.abstractmethod
def foo(self):
""""抽象方法使用@abc.abstractmethod装饰器标记""" def bar(self):
pass # 抽象类可以包含具体方法 class Concrete(Base):
def foo(self):
print('听我说谢谢你,因为有你,温暖了四季~') b = Base() # TypeError: Can't instantiate abstract class Base with abstract methods foo
c = Concrete()
c.foo() # 听我说谢谢你,因为有你,温暖了四季~
具名元组
元组是不可变的列表,常被用于表示数据的记录(类比Java的Record类型?关系型数据库也把表中的一行数据称为元组)。元组中的元素只能通过索引进行访问,而整型的索引难以表示元素在数据记录中的语义,最终导致代码的可读性变差。为了解决该问题,Python提出了具名元组(Named Tuple),允许通过可读性强的标识符访问元组中的元素。有两种定义具名元组的方式:(1)利用“collections.namedtuple”工厂函数定义具名元组;(2)通过继承“typing.NamedTuple”定义具名元组。
利用“collections.namedtuple”工厂函数定义具名元组
namedtuple工厂函数接收一个标识符和字段列表作为参数,返回以该标识符命名的类(是内置类型tuple的子类)。
from collections import namedtuple Car = namedtuple('Car' , ['color', 'mileage'])
tesla = Car('black', mileage=376.5) # 创建实例时,提供位置参数、关键字参数均可
tesla.color # 'black',现在可以通过标识符访问元组的元素啦
tesla._asdict() # {'color': 'black', 'mileage': 376.5} tesla._replace(color='white') # Car(color='white', mileage=376.5),通过替换元素构造新的元组
Car._make(['white', 376.5]) # Car(color='white', mileage=376.5),通过类方法构造新的元组
Car._fields # ('color', 'mileage')
通过继承“typing.NamedTuple”定义具名元组
import typing class Car(typing.NamedTuple): # 继承typing.NamedTuple
color: str
mileage: float tesla = Car('black', mileage=376.5)
tesla.color # 'black'
tesla._asdict() # {'color': 'black', 'mileage': 376.5} Car._fields # ('color', 'mileage')
Car._field_types # {'color': str, 'mileage': float},相比于利用工厂函数定义的方式多了类型信息
具名元组占用的空间与内置的元组类型是相同的,这是我不能理解的地方。尽管字段名列表、类型信息可以绑定到类上面,但是诸如“_asdict()”、“_replace()”这样的实例方法需要绑定到具名元组的实例上,这为什么没有带来空间开销呢?还是说通过“sys.getsizeof()”获得的对象占用内存空间的大小跟想的是不太一样?
import sys
a = ('black', 376.5)
sys.getsizeof(a) # 56,普通元组占用56个字节的空间
b = Car('black', 376.5)
sys.getsizeof(b) # 56,具名元组同样占用56个字节的空间
除了具名元组,定义数据类的另一种方式是使用“dataclasses”模块,由于篇幅限制,这里就不再展开介绍了。
参考资料
- Python Tricks: The Book
- 《流畅的Python》,第十一章“接口:从协议到抽象基类”
- The definitive guide on how to use static, class or abstract methods in Python
- 正确理解Python中的@staticmethod@classmethod方法
- Python中的abc模块
Python抽象基类:ABC谢谢你,因为有你,温暖了四季!的更多相关文章
- Python抽象基类之声明协议
抽象基类之--声明协议 上回讲了Python中抽象基类的大概,相信大家对abcmeta以及什么是抽象基类已经有所了解.传送门 现在我们来讲讲抽象基类的另一个常用用法--声明协议 所谓声明协议,有点像J ...
- python抽象基类
抽象基类 抽象基类提了一种方式,用以组织对象的层次结构,做出关于所需方法的断言,以及实现其他一些功能 要定义抽象基类,需要使用abc模块,该模块定义了一个元类(ABCMeta) 和一组装饰器(@abs ...
- 抽象基类(ABC),纯虚函数
#ifndef _ACCTABC_H_ #define _ACCTABC_H_ //(* #include <iostream> #include <string> //*) ...
- guxh的python笔记七:抽象基类
1,鸭子类型和白鹅类型 1.1,白鹅类型 白鹅类型对接口有明确定义,比如不可变序列(Sequence),需要实现__contains__,__iter__,__len__,__getitem__,__ ...
- Python高级主题:Python ABC(抽象基类)
#抽象类实例 作用统一规范接口,降低使用复杂度.import abcclass Animal(metaclass = abc.ABCMeta): ##只能被继承,不能实例化,实例化会报错 @abc.a ...
- python面对对象编程---------6:抽象基类
抽象基本类的几大特点: 1:要定义但是并不完整的实现所有方法 2:基本的意思是作为父类 3:父类需要明确表示出那些方法的特征,这样在写子类时更加简单明白 用抽象基本类的地方: 1:用作父类 2:用作检 ...
- 流畅python学习笔记:第十一章:抽象基类
__getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...
- Python 接口:从协议到抽象基类
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 抽象基类的常见用途:实现接口时作为超类使用.然后,说明抽象基类如何检查 ...
- python之抽象基类
抽象基类特点 1.不能够实例化 2.在这个基础的类中设定一些抽象的方法,所有继承这个抽象基类的类必须覆盖这个抽象基类里面的方法 思考 既然python中有鸭子类型,为什么还要使用抽象基类? 一是我们在 ...
随机推荐
- BUAA 2021-2022毛概复习资料
2021-2022年毛概期末主观题复习范围,参考2022版教材和课程组官方PPT,原文太过敏感,所以贴出代码大家自己run #include <stdio.h> unsigned arti ...
- 用ssh无密码登录远程linux
登录linux常用的方式是:用户名+密码,多次输入密码非常不方便,所以推荐使用密钥登录,安全又方便,下面我说下怎么使用密钥登录. 生成密钥 使用密钥登录首先需要本地有ssh密钥 如果本地没有密钥,那么 ...
- 为什么JVM要用到压缩指针?Java对象要求8字节的整数倍?
前言 前两天在一个帖子中看到一道面试题: 堆内存超过32G时,为什么压缩指针失效? 之前没有了解过这方面的知识,于是开始google起来,但当我翻看了不下一页的帖子,我都仍然没有搞懂,因为好多答案给我 ...
- k8s集群搭建过程详解
准备工作 安装CentOS7虚拟机 略 安装Docker 略 关闭CentOS7自带的防火墙服务 systemctl disable firewalld systemctl stop firewall ...
- CyclicBarrier和CountDownLatch区别
这两天写多线程时,用到了CyclicBarrier,下意识的认为CyclicBarrier和CountDownLatch作用很像,就翻阅资料查了一下,说一下他们的区别吧 CyclicBarrier和C ...
- 什么是B+树??
上一篇中,我们了解了B树,辣么..B+树又是什么呢?? 一:定义:B+树是基于B树的,是B树的变形,也是一种多路搜索树.查询性能更加出色. 1.每个父节点元素出现在子节点中,是子节点的最大或最小元素. ...
- Less使用@import进行Mixins
Import 指令 从其他样式表导入样式 在标准CSS中,@ import at-rules必须在所有其他类型的规则之前.但Less.js并不关心你放置@import语句的位置 Example: .f ...
- BootstrapBlazor 智能生成神器(一)AutoGenerateColumnAttribute 特性介绍
原文连接:https://www.cnblogs.com/ysmc/p/16074645.html BootstrapBlazor 官网地址:https://www.blazor.zone 介绍 Bo ...
- Rust 中的数据布局--非正常大小的类型
非正常大小的类型 大多数的时候,我们期望类型在编译时能够有一个静态已知的非零大小,但这并不总是 Rust 的常态. Dynamically Sized Types (DSTs) Rust 支持动态大小 ...
- 微信小程序——gulp处理文件
懒癌直接贴代码,想写在写因为最近搞了一下小程序,直接使用微信的开发者工具搞感觉有点不习惯,并且看了几篇给小程序瘦身的博客,决定给自己的项目做一套配置文件,使用gulp来支持sass scss文件编译以 ...