4.6 标准类型内建函酸
除了这些操作符,我们刚才也看到,Python提供了一些内建函数用于这些基本对象类型:cmp()、repr()、str()、type()和等同于repr()函数的单反引号(``)操作符。
4.6.1 type()
我们现在来正式介绍type()。在Python2.2以前,type()是内建函数。不过从那时起,它变成了一个 “工厂函数“。在本章的后面部分我们会讨论工厂函数,现在你仍然可以将type()仅仅当成一个内建函数来看。type()的用法如下。
type(object)
type()接受一个对象作为参数,并返回它的类型。它的返回值是一个类型对象。
在上面的例子里,我们通过内建函数type()得到了一个整型和一个字符串的类型;为了确认一下类型本身也是类型,我们对type()的返回值再次调用type()。注意type()有趣的输出,它看上去不像一个典型的Python数据类型,比如一个整型或一个字符串,一些东西被一个大于号和一个小号包裹着。这种语法是为了告诉你它是一个对象。每个对象都可以实现一个可打印的字符串表示。不过并不总是这样,对那些不容易显示的对象来说,Python会以一个相对标准的格式表示这个对象,格式通常是这种形式: <object_something_or_another>,以这种形式显示的对象通常会提供对象类别、对象id或位置,或者其他合适的息。
4.6.2 cmp()
内建函数cmp()用于比较两个对象objl和obj2。如果obj1.小于obj2,则返回一个负整型,如果obj1大于obj2则返回一个正整型,如果obj1等于obj2,则返回0。它的行为非常类似于C语言的strcmp()函数。比较是在对象之间进行的,不管是标准类型对象还是用户自定义对象。如果是用户自定义对象,cmp()会调用该类的特殊方0。在第13章会详细介绍类的这些特殊方法。下面是几个使用cmp()内建函数的对数值和字符串对象进行比较的例子。
在后面我们会研究cmp()用于其他对象的比较操作。
4.6.3 str()和 repr()(及``操作符)
内建函数str()和repr()或反引号操作符(``)可以方便地以字符串的方式获取对象的内容、类型、数值属性等信息。str()函数得到的字符串可读性好,而repr()函数得到的字符串通常可以用来重新获得该对象,通常情况下0bj==eval(repr(obj))这个等式是成立的。这两个函数接受一个对象作为其参数,返回适当的字符串。在下面的例子里,我们会随机取一些Python对象来查看他们的字符串表示。
尽管str(),repr()和、、运算在特性和功能方面都非常相似,事实上repr()和“做的是完全一样的事情,它们返回的是一个对象的“官方“字符串表示,也就是说绝大多数情况下可以通过求值运算(使用内建函数eval())重新得到该对象,但str()则有所不同。str()致力于生成一个对象的可读性好的字符串表示,它的返回结果通常无法用于eval()求值,但很适合用于print语句输出。需要再次提醒的是,并不是所有repr()返回的字符串都能够用eval()内建函数得到原来的对象。
也就是说repr()输出对Python比较友好,而str()的输出对用户比较友好。虽然如此,很多情况下这三者的输出仍然都是完全一样的。
核心笔记:为什么我们有了 repr ()还需要``?
在Python学习过程中,你偶尔会遇到某个操作符和某个函数是做同样一件事情。之所以如此是因为某些场合函数会比操作符更适合使用。举个例子,当处理类似函数这样的可执行对象或根据不同的数据项调用不同的函数处理时,函数就比操作符用起来方便。另一个例子就是双星号( )乘方运算和pow()内建函数,xy和pow(x,y)执行的都是X的y次方(译者注:事实上Python社区目前已经不鼓励继续使用**操作符。这是本书成书之后的变化)。
4.6.4 type()和 isinstance()
Python不支持方法或函数重载,因此你必须自己保证调用的就是你想要的函数或对象。幸运的是,我们前面4.3.1小节提到的type()内建函数可以帮助你确认这一点。一个名字里究竟保存的是什么?相当多,尤其是这是一个类型的名字时。确认接收到的类型对象的身份有很多时候都是很有用的。为了达到此目的,Python提供了一个内建函数type()。type()返回任意Python对象的类型,而不局限于标准类型。让我们通过交互式解释器来看几个使用type()内建函数返回多种对象类型的例子。
Python2.2统一了类型和类,如果你使用的是低于Python2.2的解释器,你可能看到不一样的输出结果。
除了内建函数type(),还有一个有用的内建函数叫做isinstance()。我们会在第13章(面向对象编程)正式研究这个函数,不过在这里我们还是要简要介绍一下如何利用它来确认一个对象的类型。
1.举例
在例4.1中我们提供了一段脚本来演示在运行时环境使用isinstance()和type()函数。随后我们讨论 type()的使用,以及怎么将这个例子移植为改用isinstance()。
运行typechk.py,我们会得到以下输出。
例4.1 检查类型(typechk.py)
函数displayNumType()接受一个数值参数,它使用内建函数type()来确认数值的类型(或不是一个数值类型)。
2.例子进阶
(1)原始
这个完成同样功能的函数与本书的第一版中的例子已经大不相同。
由于Python奉行简单但是比较慢的方式,所以我们必须这么做,看一眼我们原来的条件表达式:
(2)减少函数调用的次数
如果我们仔细研究一下我们的代码,会看到我们调用了两次type()。要知道每次调用函数都会付出性能代价,如果我们能减少函数的调用次数,就会提高程序的性能。
利用在本章我们前面提到的types模块,我们还有另一种比较对象类型的方法,那就是将检测得到的类型与一个已知类型进行比较。如果这样,我们就可以直接使用type对象而不用每次计算出这个对象来。那么我们现在修改一下代码,改为只调用一次type()函数。
(3)对象值比较VS对象身份比较
在这一章的前面部分我们讨论了对象的值比较和身份比较,如果你了解其中的关键点,你就会发现我们的代码在性能上还不是最优的。在运行时期,只有一个类型对象来表示整型类型。也就是说, type(0),type(42),type(-100)都是同一个对象<type ‘int’> (types.IntType也是这个对象)。
如果它们是同一个对象,我们为什么还要浪费时间去获得并比较它们的值呢(我们已经知道它们是相同的了)?所以比较对象本身是一个更好地方案。下面是改进后的代码。
这样做有意义吗?我们用对象身份的比较来替代对象值的比较。如果对象是不同的,那意味着原来的变量一定是不同类型的(因为每一个类型只有一个类型对象),我们就没有必要去检查(值)了。一次这样的调用可能无关紧要,不过当很多类似的代码遍布在你的应用程序中的时候,就有影响了。
(4)减少查询次数
这是一个对前一个例子较小的改迸,如果你的程序像我们的例子中做很多次比较的话,程序的性能就会有一些差异。为了得到整型的对象类型,解释器不得不首先查找types这个模块的名字,然后在该模块的字典中查找IntType。通过使用from-import,你可以减少一次查询。
(5)惯例和代码风格
Pythori2.2对类型和类的统一导致isinstance()内建函数的使用率大大增加。我们将在第13章(面向对象编程)正式介绍isinstance(),在这里我们简单浏览一下。
这个布尔函数接受一个或多个对象作为其参数,由于类型和类现在都是一回事,int现在既是一个类型又是一个类。我们可以使用isinstance()函数来让我们的if语句更方便,并具有更好的可读性。
在判断对象类型时也使用isinstance()已经被广为接受,我们上面的typechk.py脚本最终与改成了使用isinstance()函数。值得一提的是,isinstance()接受一个类型对象的元组作为参数,这样我们就不必像使用type()时那样写一堆if-elif-else判断了。
4.6.5 Python类型操作符和内建函数总结
表4.5列出了所有操作符和内建函数,其中操作符顺序是按优先级从高到低排列的。同一种灰度的操作符拥有同样的优先级。注意在operator模块中有这些(和绝大多数Python)操作符相应的同功能的函数可供使用。