【进阶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可以删除属性。

相关文章
|
3天前
|
Python
空间管理大师已上线!(2),Python高级工程师进阶学习】
空间管理大师已上线!(2),Python高级工程师进阶学习】
|
5天前
|
Python
【Python进阶(五)】——模块搜索及工作目录
【Python进阶(五)】——模块搜索及工作目录
|
5天前
|
C++ 开发者 Python
实现Python日志点击跳转到代码位置的方法
本文介绍了如何在Python日志中实现点击跳转到代码位置的功能,以提升调试效率。通过结合`logging`模块的`findCaller()`方法记录代码位置信息,并使用支持点击跳转的日志查看工具(如VS Code、PyCharm),开发者可以从日志直接点击链接定位到出错代码,加快问题排查。
14 2
|
5天前
|
索引 Python
Python 中寻找列表最大值位置的方法
本文介绍了Python中找列表最大值及其位置的三种方法:1) 使用内置`max()`和`index()`函数;2) 通过循环遍历;3) 利用`enumerate()`函数和生成器表达式。每种方法均附有示例代码,其中`enumerate()`方法在保证效率的同时代码更简洁。
29 2
|
5天前
|
JSON 数据处理 数据格式
Python中批量提取[]括号内第一个元素的四种方法
Python中批量提取[]括号内第一个元素的四种方法
23 1
|
5天前
|
SQL 关系型数据库 数据库连接
使用 Python 访问数据库的基本方法
【5月更文挑战第12天】在Python中操作数据库涉及安装数据库驱动(如mysql-connector-python, psycopg2, pymongo)、连接数据库、执行查询/更新、处理结果集及关闭连接。使用ORM(如SQLAlchemy)可简化操作。通过上下文管理器(with语句)能更好地管理资源和错误。注意根据实际需求处理事务、错误和安全性,例如使用SSL连接。
23 2
|
5天前
|
Python
【Python进阶(三)】——面向对象编程
【Python进阶(三)】——面向对象编程
|
5天前
|
Python
【Python进阶(一)】——异常与错误
【Python进阶(一)】——异常与错误
|
5天前
|
Python
Python-类视图和蓝图
Python-类视图和蓝图
11 2
|
5天前
|
存储 Java 数据安全/隐私保护
Python----类对象和实例对象
Python----类对象和实例对象
9 2