通过内置对象理解 Python(四)
2021-11-03
str, bytes, int, bool, float and complex:五个基本类型
Python有6个基本的数据类型(分明是5个,随后会解释)。 其中4个是数字,另外2个基于文本。
先看基于文本的数据类型,因为简单。
str 是 Python 中最常见的数据类型之一,使用 input 函数接受用户输入会得到字符串,Python 中的其他所有数据类型都可以转换成字符串。 这是必要的,因为所有计算机输入/输出都是文本形式的,无论是用户 I/O 还是文件 I/O ,这可能是字符串无处不在的原因。
bytes 字节类型实际上是计算中所有 I/O 的基础。 如果你了解计算机,可能会知道所有的数据都是以位和字节的形式存储和处理的——这也是终端真正的工作方式。
如果想看一下位于 input 和 print 之下的字节,需要查看 sys 模块中的 I/O 缓存: sys.stdout.buffer 和 sys.stdin.buffer :
1 | import sys |
buffer 对象接收字节,把它们直接写入输出缓存,并返回字节数。
为了证明下面的一切都是字节,让我们看看另一个使用字节打印表情的例子:
1 | import sys |
int 是另一种广泛使用的基本数据类型。 它也是另外两种数据类型 float 和 complex 的最小单元, complex 是 float 的超类,而 float 又是 int 的超类。
这意味着所有的 int 也可以视作 float 或 complex ,但反过来说就不成立了。 类似地,所有的 float 可以作为 complex 。例如:
1 | x = 5 |
刚才提到 Python 中实际上只有5个基本数据类型,而不是6个。 这是因为,bool 实际上不是一个基本数据类型——它实际上是 int 的子类!
你可以通过查看这些类的 mro 属性来查验上述说法。
mro 意思是“方法解析顺序”,它定义了在类中所有方法的搜索顺序。 调用某个方法,首先在类本身中查找,如果找不到,则会在父类中搜索,然后是再上一级的父类,一直到顶端的 object 类。 Python中的一切都继承自 object 。 是的,Python中几乎所有东西都是对象。
看一看下面的代码:
1 | int.mro() |
所有类都有共同的“祖先” object 。 bool是居然继承了 int 类。
或许对 bool 是 int 的子类感觉奇怪。这主要是历史原因造成的。在历史上,曾经用 0 和 1 分别表示逻辑假和真,后来,在 Python 2.2 才引入了 True 和 False 这两个 bool 类型的值,为了能兼容,把它们设计成整数的包装器。于是这个事实就被延续至今。
基于这个事实,就能在本需要整数地方,用 bool 类型替代了。
1 | import json |
这里的 indent=True 被视为 indent=1 ,所以它是有效的,但我确信没有人会想要缩进1个空格。
object :基类
object 是整个类层次结构的基类,每个类都继承了 object 。
object 类中通过特殊方法,定义了 Python 中的一些最基本的函数,比如,通过 __hash__() 可以定义函数 hash() ,等等。
1 | dir(object) |
使用 obj.x 形式访问属性会调用 __getattr__() 方法。 类似地,设置新属性和删除属性分别调用 __setattr__() 和 __delattr__() 。 对象的哈希值由 __hash__() 方法生成,对象的字符串表示形式来自 __repr__()。
1 | object() # This creates an object with no properties |
实际上,关于 Python 的特殊方法还有很多内容,推荐参考《Python大学实用教程》或《Python 完全自学教程》,参阅:www.itdifferc.om 的说明。
type :类工厂
如果 object 是所有对象之父,那么 type 就是所有“类”之父。即所有的对象继承 object ,所有的类来自于 type 。
type 可以用来动态地创建新类,它有两种用途:
如果给定一个参数,它返回该参数的“类型”,即:用于创建该对象的类:
1
2
3
4
5
6
7x = 5
type(x)
<class 'int'>
>>> type(x) is int
True
>>> type(x)(42.0) # Same as int(42.0)
42用三个参数,可以创建一个新类,这三个参数是
name、bases和dict。name定义类的名称bases定义所继承的类dict定义了所有的类属性和方法。
如果要定义这样的类:
1
2
3class MyClass(MySuperClass):
def x(self):
print('x')用
type可以定义同样的类:1
2
3
4def x_function(self):
print('x')
MyClass = type('MyClass', (MySuperClass), {'x': x_function})这也是实现
collections.namedtuple类的一种方法,例如,它以类的名字和元组作为参数。
hash and id :判断相等的基础
Python的内置函数 hash 和 id 是用于判断对象相等的依据。
Python 对象默认情况下是不具有可比性的,除非它们是完全相同的。 如果你尝试创建两个 object() 对象并检查它们是否相等…
1 | x = object() |
结果总是 False 。 这是因为:事实上,object 以同一性来比较自己,即它们只是与自己相等,而不是与别的对象相等。
补充知识:哨兵
通常很少用类 object 直接创建实例,在编程中,有一种情景可以使用 object实例,并且称此实例为“哨兵”,这是什么意思呢?
看下面的例子。假设有一个函数 what_was_passed ,用它能够实现下面所演示的功能:
1 | what_was_passed(42) |
也就是,只要输入一个非空的值,就能对应相应的输出;如果输入的是空值,则提示 Nothing was passed 。或许觉得这个函数的代码很容易编写,比如:
1 | def what_was_passed(value=None): |
但是,如果提供的参数就是 None ——注意,None 在有的情况下,并不表示“什么也没有”,也可能具有某种意义。
1 | what_was_passed(None) |
显然这样做,并不总能实现期望。或者以省略号 ... 为参数(详见《Python 中的省略号》),也不能通过测试。
这时就需要一个“哨兵”了:
1 | __my_sentinel = object() |
现在,不论给函数提供任何职,都能看做对象,只有在不提供值时才为空。
1 | what_was_passed(42) |
(补充知识完毕)
要理解为什么对象只与自己比较,我们必须理解关键词 is 。
Python 的 is 用于检查两个名称是否在内存中引用了完全相同的对象。 我们可以把 Python 对象想象成在空间中漂浮的盒子,把变量、数组索引等想象成指向这些对象的箭头。
举个简单的例子:
1 | x = object() |
在上面的代码中,有两个独立的对象,三个标签 x ,y 和 z 指向这两个对象:x 指向第一个对象,y 和 z 都指向另一个对象。
1 | del x |
这个操作将删除箭头 x 。 对象本身不受赋值或删除的影响,只有箭头受影响。 但现在没有指向第一个对象的箭头,这个对象的存在也就没有意义了。 所以 Python 的“垃圾回收器”将其清除。 现在我们只剩下一个 object 了。
1 | y = 5 |
现在 y 箭头被改为指向一个整数对象 5 。 z 仍然指向第二个 object ,所以该对象仍然存在。
1 | z = y * 2 |
现在 z 指向另一个新对象 10 ,这个新对象存储在内存中的某个地方。 现在也没有箭头指向第二个object了,因此该对象随后被作为垃圾收走。
为了能够验证所有上述说法,我们可以使用 id 内置函数。 id 表示对象在内存中的确切位置,用数字表示。
1 | x = object() |
相同的对象,相同的 id() 返回值,否则不同。
With objects, == and is behaves the same way:
对于 object 类的示例对象,== 和 is 具有相同的效果:
1 | x = object() |
这是因为,在 object 类中,专门定义了针对 == 的方法 __eq__ ,像下面这样:
1 | class object: |
当然,实际上 object 的实现是用 C 语言编写的。
另一方面,如果容器类型可以相互替换,则它们是相等的。典型例子是具有相同索引的相同项的列表,或者包含完全相同值的集合。
1 | x = [1, 2, 3] |
这些可以这样定义:
1 | class list: |
类似地,集合是无序的,所以成员的位置无关紧要,重要的是它们的“存在”:
1 | class list: |
现在,开始讨论“等价”的概念。Python 有哈希(或:散列)的概念。 任何数据块的“散列”指的都是一个看起来非常随机的预计算值,但它在某种程度上可以用于标识该数据块。
哈希有两个特定的属性:
相同的数据块总是有相同的哈希值。
即使只是稍微改变数据,也会返回一个完全不同的哈希值。
这意味着,如果两个数据块具有相同的哈希值,那么它们也很可能具有相同的值。
比较哈希值是检查“是否存在”的一种非常快速的方法。 这就是字典和集合用来快速查找内部的值的方法:
1 | import timeit |
注意:集合解决方案的运行速度比列表解决方案快数百倍!!这是因为它们使用哈希值作为“索引”的替代,如果相同哈希的值已经存储在集合或字典中,Python 可以快速检查它是否是同一项。 这个过程可以非常即时地检查哈希值是否存在。
补充知识:关于哈希
在 Python中,所有相等的数值都具有相同的哈希值,这一点往往鲜为人知。
1 | hash(42) == hash(42.0) == hash(42+0j) |
另一个事实是不可变对象,如字符串、元组和不可变集合,通过组合各项的哈希来生成它们自己的哈希。 这使得你只需通过编写 hash 函数就可以为类创建自定义哈希函数:
1 | class Car: |
(补充知识结束)
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏
关注微信公众号,读文章、听课程,提升技能