python随笔-3

本文最后更新于:2022年5月23日 凌晨

前言

最近挺忙的(指打游戏)所以导致摸了很久(包括学习),现在痛定思痛,于是乎毅然决然写(水)一篇博文来规避内疚感(不是)
其实还是有看(一点)的,这篇随笔要介绍的都是和类与面向对象有关的,那么不(想)多说了直接进入正文了。

正文

is 和 ==

在python中,==比较的是相等性,而is比较的是相同性,当我们用==时,比较的是值,当我们用is时,比较的是标识符。这么说可能非常难懂,所以直接在python中写点代码会好很多。

1
2
3
4
5
6
7
8
9
10
11
12
>>> a = [1,2,3]
>>> b = [1,2,3]

>>> a == b
True

>>> a is b
False

>>>b = a
>>>a is b
True

这里我们可以看到,由于 a 和 b 的值是相等的,所以a == b为True,但是很明显 a 和 b 的标识符是不同的,所以结果输出了False。而在执行了b = a后,由于标识符相同了,所以现在a is b的结果是True了。

来概括一下吧,用两条定义区分is和==的区别:[1]

当两个变量指向同一个对象时,is表达式的结果为True;
当各变量指向的对象含有相同内容时,==表达式的结果为True。

字符串转换

有的时候我们想把我们实例化类的对象的内容(信息)打印出来,我们希望最好是这么做:print(a_object),但是理所当然的不会像你想的那样输出你想要的结果。
如果直接打印的话,我们的得到的是一个包含类名和实例对象的标识符。

1
2
3
4
5
6
7
8
9
10
class Pet:
def __init__(self, name, specie):
self.name = name
self.specie = specie

>>> my_pet = Pet('white', 'cat')
>>> print(my_pet)
<__main__.Pet object at 0x0000019E5BDFCF40>
>>> my_pet
<__main__.Pet object at 0x0000019E5BDFCF40>

为了解决这个问题,我们可以自己在类里构建一个方法来对打印的实例对象进行字符串转换,但是我们不需要这么做(不要构建自己的字符串转换机制),因为Python提供了两个双下划线方法__str____repr__在不同的情况下来将对象转换成字符串。
现在我们在上边的类中添加一个__str__方法,再用print输出,这时候就可以转换成我们想要的字符串了。

1
2
3
4
5
6
7
8
9
10
11
class Pet:
def __init__(self, name, specie):
self.name = name
self.specie = specie

def __str__(self):
return f'The {self.specie}\'s name is {self.name}.'

>>> my_pet = Pet('white', 'cat')
>>> print(my_pet)
The cat's name is white.

“在Python解释器会话中查看对象得到的是对象的__repr__结果。
像列表和字典这样的容器总是使用__repr__的结果来表示所包含的对象,哪怕对容器本身调用str()也是如此。”

我们也可以手动选择两种方法中的一种,用str()或是repr()即可,最好不要用对象名.__str__或是对象名.__repr__
那他们的具体差别在哪里呢?这里我觉得书中的例子非常好,所以摘录书中内容来介绍。

对于这样的问题,可以看看Python标准库是怎么做的。现在再设计一个实验,创建一个datetime.date对象,看这个对象如何使用__repr__和__str__来控制字符串转换:

1
2
>>> import datetime
>>> today = datetime.date.today()

在date对象上,__str__函数的结果侧重于可读性,旨在为人们返回一个简洁的文本表示,以便放心地向用户展示。因此在date对象上调用str()时,得到的是一些看起来像ISO日期格式的东西:

1
2
>>> str(today)
'2017-02-02'

__repr__侧重的则是得到无歧义的结果,生成的字符串更多的是帮助开发人员调试程序。为此需要尽可能明确地说明这个对象是什么,因此在对象上调用repr()会得到相对更复杂的结果,其中甚至包括完整的模块和类名称:

1
2
>>> repr()
'datetime.date(2017, 2, 2)'

我的经验法则是只要让__repr__生成的字符串对开发人员清晰且有帮助就可以了,并不需要能从中恢复对象的完整状态。

如果没有设置__str__方法的话,那么在需要调用该方法时会转为调用__repr__方法。

abc——抽象基类

使用抽象基类可以避免我们在之后编写代码的时候不小心实例化了基类,并且可以确保派生类实现了我们想在其中实现的基类方法。
先来看看普通的基类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Pet:
def infor(self):
raise NotImplementedError()

def behavior(self):
raise NotImplementedError()

class Cat(Pet):
def infor(self, name, specie):
return f"A {specie} called {name}."

>>> b = Pet()
>>> b.infor()
NotImplementedError

>>> my_cat = Cat()
>>> my_cat.infor('white', 'cat')
A cat called white.
>>> my_cat.behavior()
NotImplementedError

在上面的代码里,不仅基类被实例化了,而且只有在调用未实现的方法时才能发现编写了一个实现不完整的子类。
为了避免出现这种状况,就可以用到我们的abc模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from abc import ABCMeta, abstractmethod

class Pet(metaclass=ABCMeta):
@abstractmethod
def infor(self):
pass

@abstractmethod
def behavior(self):
pass

class Cat(Pet):
def infor(self, name, specie):
return f"A {specie} called {name}."

>>> b = Pet()
TypeError: Can't instantiate abstract class Pet with abstract methods behavior, infor

>>> my_cat = Cat()
TypeError: Can't instantiate abstract class Cat with abstract method behavior

如果我们尝试实例化基类,或者实例化一个不完整的子类,那么就会引发TypeError,并且会指出子类中缺少了哪些方法(未实现哪些抽象方法)。

在用多个装饰器修饰同一个方法时,abstractmethod()应当被应用为最内层的装饰器。

不同于Java 抽象方法,这些抽象方法可能具有一个实现。这个实现可在重载它的类上通过super()机制来调用。这在使用协作多重继承的框架中可以被用作超调用的一个端点。

如果想进一步了解abc模块,可以阅读官方文档:点这里跳转


  1. 《Python Tricks 深入理解python特性》 [德]达恩·巴德尔 人民邮电出版社

这里有一只爱丽丝

希望本文章能够帮到您~


python随笔-3
https://map1e-g.github.io/2022/05/22/python-essay-3/
作者
MaP1e-G
发布于
2022年5月22日
许可协议