【进阶Python】第三讲:类的特殊方法(上篇)

简介: 类,对于Python这类面向对象的编程语言至关重要。而特殊方法,又是Python类中举足轻重的一系列方法。特殊方法,又称为魔术方法,通过特殊方法的使用,能够将类变成字典、字符串,能够实现各种高级、简洁的程序设计模式。本文就来讲解一下Python类的特殊方法,由于特殊方法比较多,篇幅有限,所以分开来讲。

前言

Python是一种面向对象的语言,而特殊方法又是Python类中一个重点,因此学习Python类的特殊方法能够有助于设计出更加简洁、规范的代码架构。

Python类的特殊方法又称为魔术方法,它是以双下划线包裹一个词的形式出现,例如__init__。特殊方法不仅可以可以实现构造和初始化,而且可以实现比较、算数运算,此外,它还可以让类像一个字典、迭代器一样使用,可以设计出一些高级的代码,例如单例模式

面向对象这个词大家应该都不陌生,在C++、Java等面向对象的语言中也经常出现,要想理解面向对象,首先要理解4个概念之间的关系:类、对象、实例、方法。

:类是一种由不同属性、不同数据组成的一个集合。用直白的话来描述,它是由多种对象组成的一个组合,例如人是一个类,那么它包含男人、女人、儿童等对象。例如三角形是一个类,那么它包含等腰三角形、直角三角形、等边三角形等对象。

对象:前面介绍类中已经提到了对象这个词汇,一句话总结:对象具有具体状态和行为。例如直角三角形,它具有特定的状态和属性。

实例:对象就是类的一个实例。也许这有点绕,的确对象与实例之间的概念非常模糊。你可以理解为对象是一个概念性的存在,而实例是采取行为、动作的载体,以一段代码举例,

class Animal(object):
    pass
animal = Animal()

其中Animal是一个类,而animal是一个实例,它可以访问类内的方法,实施“动作”和“行为”

方法:定义在类外部的函数叫做函数,定义在类内部的函数称为方法。

这些概念在Python面向对象编程中非常概念,只有理解这些概念才能在后续学习中更加容易理解,对上述这些概念有一个简单的了解,在后续的讲解中会更加轻松。

完整代码

本讲所涉及的代码已经上传至github,需要的可以可以访问Jackpopc/advance-python完整链接,

https://github.com/Jackpopc/advance-python/blob/master/2-magic-method_1.ipynb

__new__与__init__

之所以把这个放在第一个,因为这个不仅非常常用,而且很容易被误解,甚至很多知名的书籍中都把这个特殊方法弄错。

很多博客和个别书籍中都把__init__当作类似于C++的构造方法,其实这个理解是错误的。

class Animal(object):
    def __new__(cls, *args, **kargs):
        instance = object.__new__(cls, *args, **kargs)
        print("{} in new method.".format(instance))
#         return instance             # 不返回实例
    def __init__(self):
        print("{} in init method.".format(self))
animal = Animal()
# 输出
<__main__.Animal object at 0x000002BB03001CF8> in new method.

以上面为例,我们对基类中的__new__进行重构,不让它返回实例,可以从输出结果可以看出,程序没有进入__init__方法。这是因为__new__是用来构造实例的,而__init__只是用来对返回的实例进行一些属性的初始化,我们在写一个类的时候首先都会写一个__init__方法去初始化变量,却很少使用__new__,因此就容易忽略__new__,其实在我们继承基类object(例如,class Animal(object))时同时就从基类中继承了__new__方法,所以就不需要重新在子类中实现,如果把上述注释取消掉,再看一下,

class Animal(object):
    def __new__(cls, *args, **kargs):
        instance = object.__new__(cls, *args, **kargs)
        print("{} in new method.".format(instance))
        return instance
    def __init__(self):
        print("{} in init method.".format(self))
animal = Animal()
# 输出
<__main__.Animal object at 0x000002BB03001B00> in new method.
<__main__.Animal object at 0x000002BB03001B00> in init method.

可以看出,程序先运行到new中,然后进入init方法。

对于__init__应该都很熟悉,为什么很少使用__new__呢?因为大多数情况下我们是用不到它的。但是存在的即是合理的,它自然有自己的价值。

__new__在哪些场景能够用到呢?

当实现一些高级的软件设计模式可能会用到__new__方法,它主要有以下几点用处,

  • 重构一些不可变方法,例如,int, str, tuple
  • 实现单例模式(Singleton Pattern)

这里着重介绍一下单例模式。单例模式是一种常用的软件设计模式,有时候我们需要严格的限制一个类只有一个实例存在,一个系统只有一个全局对象,这样有利于协调系统的整体行为。先看一下我们常用的写法,

class NewInt(object):
    pass
new1 = NewInt()
new2 = NewInt()
print(new1)
print(new2)
# 输出
<__main__.NewInt object at 0x000002BB03001390>
<__main__.NewInt object at 0x000002BB02FF4080>

从输出可以看出,上述两个实例new1、new2地址不同,是两个实例。

然后通过__new__实现单例模式,

class NewInt(object):
    _singleton = None
    def __new__(cls, *args, **kwargs):
        if not cls._singleton:
            cls._singleton = object.__new__(cls, *args, **kwargs)
        return cls._singleton
new1 = NewInt()
new2 = NewInt()
print(new1)
print(new2)
# 输出
<__main__.NewInt object at 0x000002BB02FF6080>
<__main__.NewInt object at 0x000002BB02FF6080>

地址相同,指向同一个对象,所以每次实例化产生的实例都是完全相同的。

__enter__与__exit__

在介绍这两个特殊方法之前我们首先讲一下with语句。with语句主要用于对资源进行访问的场景,例如读取文件。以读取文件为例,我们可以使用open、close的方法,但是使用with语句有着无法比拟的优势。首先就是简洁,你不需要再写file.close的语句去关闭文件。其次,也是最重要的,它能够很好的做到异常处理,当处理过程中发生异常,它能够自动关闭、自动释放资源。以读取文件来对比一下两个功能,如果使用open、close方式需要打开、读取、关闭3个过程,

# file.txt
fp = open("file.txt", "rb")
fp.readline()
fp.close()

而使用with语句只需要打开、读取两个过程,当执行完毕会自动关闭,

with open("file.txt", "rb") as fp:
    fp.readline()

说了这么多with语句的好处,这和__enter__与__exit__有什么关系?

__enter__与__exit__就是实现with的类特殊方法。

以一段代码来解释这两个特殊方法的使用,

class FileReader(object):
    def __init__(self):
        print("in init method")
    def __enter__(self):
        print("int enter method")
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("in exit method")
        del self
    def read(self):
        print("in read")
# with语句调用
with FileReader() as fr:
    fr.read()
# 输出
in init method
int enter method
in read
in exit method

从上面输出可以看出,程序先进去init方法进行初始化,然后进入enter特殊方法,然后通过fr.read调用read()方法,最后退出时调用exit方法。

这就是enter与exit的调用过程,

  • __enter__:初始化后返回实例
  • __exit__:退出时做处理,例如清理内存,关闭文件,删除冗余等

__str__与__repr__

一句话描述这两个特殊方法的功能:把类的实例变为字符串。我们都知道,我们可以用这种方法输出一个字符串,

print("Hello world!")

那我们怎么能够像字符串一样把实例输出出来?

可以通过__str__与__repr__来实现,

lass Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __str__(self):
        return "str: {} now year is {} years old.".format(self.name, self.age)
    def __repr__(self):
        return "repr: {} now year is {} years old.".format(self.name, self.age)
person = Person("Li", 27)
print(person)
# 输出
str: Li now year is 27 years old.

可以看出,当使用print语句打印实例person时,能够像输出字符串那样把实例信息输出出来。但是可以看出,程序进入__str__方法,并没有进入__repr__,这就引出了这两个方法的不同之处,

  • __str__:用于用户调用
  • __repr__:用于开发人员调用

这似乎不太好理解,因为对于写程序的我们无法理解,何为用户?何为开发人员?简单的来说,__str__是用些Python脚本(.py)时使用,用print语句输出字符串信息。__repr__是我们在交互式环境下测试使用,例如cmd下的Python、ipython,例如在交互式环境下调用,

>>> person = Person("li", 27)
>>> person
repr: li now year is 27 years old.

更为简单的理解就是:__str__需要用print语句打印,__repr__只需输入实例名称即可。

__setattr__、__getattr__、__getattribute__与__delattr__

了解上述这4个方法之前,我们先来解释一下什么是属性?

也许很多同学已经清楚,但是我觉得还是有必要介绍一下,因为这是要讲的这3个特殊方法的关键。

class Person(object):
    def __init__(self, name, age, home, work):
        self.name = name
        self.age = age
        self.home = home
        self.work = work
person = Person("Li", 27, "China", "Python")
print(person.name)
print(person.age)
# 输出
Li
27

例如上面我们定义一个Person类,name、age、home、work就是它的属性,当实例化之后我们可以通过点.来访问它的属性。

我可以可以通过传入参数,赋值给self来定义类的属性,但是这样未免太固定了,当实例化之后就不能更改它的属性了,如果我们想获取、添加、删除属性怎么办?这就用到这里要讲的4个特殊方法,__setattr__、__getattr__、__getattribute__与__delattr__,它们的功能分别是,

  • __setattr__:设置属性
  • __getattr__:访问不存的属性时调用,可能会有同学有疑问,访问不存的属性要它干吗?可以用来做异常处理!
  • __getattribute__:访问存在的属性,如果访问属性不存在的时候随后会调用__getattr__
  • __delattr__:删除属性

以一个例子来说明一下,

class Person(object):
    def __init__(self, name):
        self.name = name
    def __setattr__(self, key, value):
        object.__setattr__(self, key, value)
    def __getattribute__(self, item):
        print("in getattribute")
        return object.__getattribute__(self, item)
    def __getattr__(self, item):
        try:
            print("in getattr")
            return object.__getattribute__(self, item)
        except:
            return "Not find attribute: {}".format(item)
    def __delattr__(self, item):
        object.__delattr__(item)
person = Person("Li")
print(person.name)
print(person.age)
# 输出
in getattribute
Li
in getattribute
in getattr
Not find attribute: age

从上面输出来看一下就可以明白,当获取属性name时,由于已经有了,则进入__getattribute__中,获取对应的属性,当获取属性age时,由于没有这个属性,则先进入__getattribute__,然后进入__getattr__,没有找到属性返回异常信息。

然后再来看一下看一下设置属性和删除属性,

person.age = 27
print(person.age)
delattr(person, "age")
print(person.age)
# 输出
in getattribute
27
in delattr
in getattribute
in getattr
Not find attribute: age

从输出结果可以看出,通过instance.attribute的方式可以设置属性,通过delattr可以删除属性。

相关文章
|
10天前
|
测试技术 开发者 Python
Python单元测试入门:3个核心断言方法,帮你快速定位代码bug
本文介绍Python单元测试基础,详解`unittest`框架中的三大核心断言方法:`assertEqual`验证值相等,`assertTrue`和`assertFalse`判断条件真假。通过实例演示其用法,帮助开发者自动化检测代码逻辑,提升测试效率与可靠性。
99 1
|
2月前
|
机器学习/深度学习 数据采集 数据挖掘
基于 GARCH -LSTM 模型的混合方法进行时间序列预测研究(Python代码实现)
基于 GARCH -LSTM 模型的混合方法进行时间序列预测研究(Python代码实现)
|
2月前
|
调度 Python
微电网两阶段鲁棒优化经济调度方法(Python代码实现)
微电网两阶段鲁棒优化经济调度方法(Python代码实现)
|
2月前
|
传感器 大数据 API
Python数字限制在指定范围内:方法与实践
在Python编程中,限制数字范围是常见需求,如游戏属性控制、金融计算和数据过滤等场景。本文介绍了五种主流方法:基础条件判断、数学运算、装饰器模式、类封装及NumPy数组处理,分别适用于不同复杂度和性能要求的场景。每种方法均有示例代码和适用情况说明,帮助开发者根据实际需求选择最优方案。
78 0
|
2月前
|
Python
Python字符串center()方法详解 - 实现字符串居中对齐的完整指南
Python的`center()`方法用于将字符串居中,并通过指定宽度和填充字符美化输出格式,常用于文本对齐、标题及表格设计。
|
3天前
|
人工智能 数据安全/隐私保护 异构计算
桌面版exe安装和Python命令行安装2种方法详细讲解图片去水印AI源码私有化部署Lama-Cleaner安装使用方法-优雅草卓伊凡
桌面版exe安装和Python命令行安装2种方法详细讲解图片去水印AI源码私有化部署Lama-Cleaner安装使用方法-优雅草卓伊凡
55 8
桌面版exe安装和Python命令行安装2种方法详细讲解图片去水印AI源码私有化部署Lama-Cleaner安装使用方法-优雅草卓伊凡
|
3月前
|
安全 Python
Python语言中常用的文件操作方法探讨
通过上述方法的结合使用,我们可以构筑出强大并且可靠的文件操作逻辑,切实解决日常编程中遇到的文件处理问题。
166 72
|
5天前
|
缓存 供应链 芯片
电子元件类商品 item_get - 商品详情接口深度分析及 Python 实现
电子元件商品接口需精准返回型号参数、规格属性、认证及库存等专业数据,支持供应链管理与采购决策。本文详解其接口特性、数据结构与Python实现方案。
|
15天前
|
算法 调度 决策智能
【两阶段鲁棒优化】利用列-约束生成方法求解两阶段鲁棒优化问题(Python代码实现)
【两阶段鲁棒优化】利用列-约束生成方法求解两阶段鲁棒优化问题(Python代码实现)
|
2月前
|
机器学习/深度学习 数据采集 算法
【CNN-BiLSTM-attention】基于高斯混合模型聚类的风电场短期功率预测方法(Python&matlab代码实现)
【CNN-BiLSTM-attention】基于高斯混合模型聚类的风电场短期功率预测方法(Python&matlab代码实现)
132 4

推荐镜像

更多