Python 3 初探,第 2 部分: 高级主题
Python 3 是 Guido van Rossum 功能强大的通用编程语言的最新版本。它虽然打破了与 2.x 版本的向后兼容性,但却清理了某些语法方面的问题。本文是这个由两部分组成的系列文章中的第二篇,本文构建在此系列 前一期文章 的基础之上,内容涵盖了 Python 更多的新特性和更高深的一些主题,比如在抽象基类、元类和修饰符等方面的变化。
有关 Python 版本 3—,也即 Python 3000 或 Py3K— 的前一篇文章讨论了 Python 内打破向后兼容性的一些基本变化,比如新的 print() 函数、 bytes 数据类型以及 string 类型的变化。本文是该系列文章的第 2 部分,探究了更为高深的一些主题,比如抽象基类(ABC)、元类、函数注释和修饰符(decorator)、整型数(integer literal)支持、数值类型层次结构以及抛出和捕获异常,其中的大多数特性仍然会打破与版本 2x 产品线的向后兼容性。
类修饰符
在 Python 之前的版本中,对方法的转换必须在方法定义之后进行。对于较长的方法,此要求将定义的重要组成部分与 Python Enhancement Proposal (PEP) 318(有关链接,请参见 参考资料)给出的外部接口定义分离。下面的代码片段演示了这一转换要求:
清单 1. Python 3 之前版本中的方法转化
def myMethod(self):
# do something myMethod = transformMethod(myMethod)
为了让此类情景更易于读懂,也为了避免必须多次重用相同的方法名,在 Python 版本 2.4 中引入了方法修饰符。
修饰符 是一些方法,这些方法可以修改其他方法并返回一个方法或另外一个可调用对象。对它们的注释是在修饰符的名称前冠以 “at” 符号(@)— 类似 Java™ 注释的语法。清单 2 显示了实际应用中的修饰符。
清单 2. 一个修饰符方法
@transformMethod
def myMethod(self):
# do something
修饰符是一些纯粹的语法糖(syntactic sugar)— 或者(如 Wikipedia 所言)“对计算机语言语法的补充,这些补充并不影响语言的功能,而是会让语言变得更易于被人使用。”修饰符的一种常见用法是注释静态方法。比如,清单 1 和清单 2 相当,但清单 2 更容易被人读懂。
定义修饰符与定义其他方法无异:
def mod(method):
method.__name__ = "John"
return method @mod
def modMe():
pass print(modMe.__name__)
更棒的是 Python 3 现在不仅支持针对方法的修饰符,并且支持针对类的修饰符,所以,取代如下的用法:
class myClass:
pass myClass = doSomethingOrNotWithClass(myClass)
我们可以这样使用:
@doSomethingOrNotWithClass
class myClass:
pass
元类
元类 是这样一些类,这些类的实例也是类。Python 3 保留了内置的、用来创建其他元类或在运行时动态创建类的 metaclass 类型。如下的语法仍旧有效:
>>>aClass = type('className',
(object,),
{'magicMethod': lambda cls : print("blah blah")})
上述语法接受的参数包括:作为类名的字符串、被继承对象的元组(可以是一个空的元组)和一个包含可以添加的方法的字典(也可以是空的)。当然,也可以从类型继承并创建您自己的元类:
class meta(type):
def __new__(cls, className, baseClasses, dictOfMethods):
return type.__new__(cls, className, baseClasses, dictOfMethods)
只允许关键字的参数
Python 3 已经更改了函数参数分配给 “参数槽(parameter slot)” 的方式,可以在传递进的参数内使用星号(*)以便不接受可变长度的参数。命名的参数必须在 * 之后 — 比如,def meth(*, arg1): pass。更多信息,请参考 Python 文档或 PEP 3102(有关链接,请参见 参考资料)。
注意:如果上面两个例子起不到任何作用,我强烈建议您阅读 David Mertz 和 Michele Simionato 合写的有关元类的系列文章。相关链接,请参见 参考资料。
请注意,现在,在类定义中,关键字参数被允许出现在基类列表之后 — 通常来讲,即 class Foo(*bases, **kwds): pass。使用关键字参数 metaclass 将元类传递给类定义。比如:
>>>class aClass(baseClass1, baseClass2, metaclass = aMetaClass): pass
旧的元类语法是将此元类分配给内置属性 __metaclass__:
class Test(object):
__metaclass__ = type
而且,既然有了新的属性 —__prepare__— 我们就可以使用此属性为新的类名称空间创建字典。在类主体被处理之前,先会调用它,如清单 3 所示。
清单 3. 使用 the __prepare__ attribute 的一个简单元类
def meth():
print("Calling method") class MyMeta(type):
@classmethod
def __prepare__(cls, name, baseClasses):
return {'meth':meth} def __new__(cls, name, baseClasses, classdict):
return type.__new__(cls, name, baseClasses, classdict) class Test(metaclass = MyMeta):
def __init__(self):
pass attr = 'an attribute' t = Test()
print(t.attr)
我们从 PEP 3115 节选了一个更为有趣的例子,如清单 4 所示,这个例子创建了一个具有其方法名称列表的元类,而同时又保持了类方法声明的顺序。
清单 4. 保持了类成员顺序的一个元类
# The custom dictionary
class member_table(dict):
def __init__(self):
self.member_names = [] def __setitem__(self, key, value):
# if the key is not already defined, add to the
# list of keys.
if key not in self:
self.member_names.append(key) # Call superclass
dict.__setitem__(self, key, value) # The metaclass
class OrderedClass(type):
# The prepare function
@classmethod
def __prepare__(metacls, name, bases): # No keywords in this case
return member_table() # The metaclass invocation
def __new__(cls, name, bases, classdict):
# Note that we replace the classdict with a regular
# dict before passing it to the superclass, so that we
# don't continue to record member names after the class
# has been created.
result = type.__new__(cls, name, bases, dict(classdict))
result.member_names = classdict.member_names
return result
在元类内所做的这些改变有几个原因。对象的方法一般存储于一个字典,而这个字 典是没有顺序的。不过,在某些情况下,若能保持所声明的类成员的顺序将非常有用。这可以通过让此元类在信息仍旧可用时,较早地涉入类的创建得以实现 — 这很有用,比如在 C 结构的创建中。借助这种新的机制还能在将来实现其他一些有趣的功能,比如在类构建期间将符号插入到所创建的类名称空间的主体以及对符号的前向引用。PEP 3115 提到更改语法还有美学方面的原因,但是对此尚存在无法用客观标准解决的争论(到 PEP 3115 的链接,请参见 参考资料)。
抽象基类
正如我在 Python 3 初探,第 1 部分:Python 3 的新特性 中提到的,ABC 是一些不能被实例化的类。Java 或 C++ 语言的程序员应该对此概念十分熟悉。Python 3 添加了一个新的框架 —abc— 它提供了对 ABC 的支持。
这个 abc 模块具有一个元类(ABCMeta)和 修饰符(@abstractmethod 和 @abstractproperty)。如果一个 ABC 具有一个 @abstractmethod 或 @abstractproperty,它就不能被实例化,但必须在一个子类内被覆盖。比如,如下代码:
>>>from abc import *
>>>class C(metaclass = ABCMeta): pass
>>>c = C()
这些代码是可以的,但是不能像下面这样编码:
>>>from abc import *
>>>class C(metaclass = ABCMeta):
... @abstractmethod
... def absMethod(self):
... pass
>>>c = C()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class C with abstract methods absMethod
更好的做法是使用如下代码:
>>>class B(C):
... def absMethod(self):
... print("Now a concrete method")
>>>b = B()
>>>b.absMethod()
Now a concrete method
ABCMeta 类覆盖属性 __instancecheck__ 和 __subclasscheck__,借此可以重载内置函数 isinstance() 和 issubclass()。要向 ABC 添加一个虚拟子类,可以使用 ABCMeta 提供的 register() 方法。如下所示的简单示例:
>>>class TestABC(metaclass=ABCMeta): pass
>>>TestABC.register(list)
>>>TestABC.__instancecheck__([])
True
它等同于使用 isinstance(list, TestABC)。您可能已经注意到 Python 3 使用 __instancecheck__,而非 __issubclass__,使用 __subclasscheck__,而非 __issubclass__,这看起来更为自然。若将参数 isinstance(subclass, superclass) 反转成,比如 superclass.__isinstance__(subclass),可能会引起混淆。可见,语法 superclass.__instancecheck__(subclass) 显然更好一点。
在 collections 模块内,可以使用几个 ABC 来测试一个类是否提供了特定的一个接口:
>>>from collections import Iterable
>>>issubclass(list, Iterable)
True
表 1 给出了这个集合框架的 ABC。
表 1. 这个集合框架的 ABC
| ABC | Inherits |
|---|---|
Container |
|
Hashable |
|
Iterable |
|
Iterator |
Iterable |
Sized |
|
Callable |
|
Sequence |
Sized, Iterable, Container |
MutableSequence |
Sequence |
Set |
Sized, Iterable, Container |
MutableSet |
Set |
Mapping |
Sized, Iterable, Container |
MutableMapping |
Mapping |
MappingView |
Sized |
KeysView |
MappingView, Set |
ItemsView |
MappingView, Set |
ValuesView |
MappingView |
集合
集合框架包括容器数据类型、双端队列(即 deque)以及一个默认的字典(即 defaultdict)。一个 deque 支持从前面或后面进行追加和弹出。defaultdict 容器是内置字典的一个子类,它(根据 Python 3 文档)“覆盖一个方法并添加一个可写的实例变量。”除此之外,它还充当一个字典。此外,集合框架还提供了一个数据类型工厂函数 namedtuple()。
ABC 类型层次结构
Python 3 现支持能代表数值类的 ABC 的类型层次结构。这些 ABC 存在于 numbers 模块内并包括 Number、 Complex、Real、 Rational 和 Integral。图 1 显示了这个数值层次结构。可以使用它们来实现您自己的数值类型或其他数值 ABC。
图 1. 数值层次结构

数值塔(numerical tower)
Python 的数值层次结构的灵感来自于 Scheme 语言的数值塔。
新模块 fractions 可实现这个数值 ABC Rational。此模块提供对有理数算法的支持。若使用 dir(fractions.Fraction),就会注意到它具有一些属性,比如 imag、 real 和 __complex__。根据数值塔的原理分析,其原因在于 Rationals 继承自 Reals,而 Reals 继承自 Complex。
抛出和捕获异常
在 Python 3 内,except 语句已经被修改为处理语法不清的问题。之前,在 Python version 2.5 内,try . . . except 结构,比如:
>>>try:
... x = float('not a number')
... except (ValueError, NameError):
... print "can't convert type"
可能会被不正确地写为:
>>> try:
... x = float('not a number')
... except ValueError, NameError:
... print "can't convert type"
后一种格式的问题在于 ValueError 异常将永远捕获不到,因为解释器将会捕获 ValueError 并将此异常对象绑定到名称 NameError。这一点可以从下面的示例中看出:
>>> try:
... x = float('blah')
... except ValueError, NameError:
... print "NameError is ", NameError
...
NameError is invalid literal for float(): not a number
所以,为了处理语法不清的问题,在想要将此异常对象与另一个名称绑定时,逗号(,)会被替换成关键字 as。如果想要捕获多个异常,必须使用括号(())。清单 5 中的代码展示了 Python 3 内的两个合乎语法的示例。
清单 5. Python 3 内的异常处理
# bind ValueError object to local name ex
try:
x = float('blah')
except ValueError as ex:
print("value exception occurred ", ex) # catch two different exceptions simultaneously
try:
x = float('blah')
except (ValueError, NameError):
print("caught both types of exceptions")
异常处理的另一个改变是异常链— 隐式或显式。清单 6 给出了隐式异常链的一个示例。
清单 6. Python 3 内的隐式异常链
def divide(a, b):
try:
print(a/b)
except Exception as exc:
def log(exc):
fid = open('logfile.txt') # missing 'w'
print(exc, file=fid)
fid.close() log(exc) divide(1,0)
divide() 方法试图执行除数为零的除法,因而引发了一个异常:ZeroDivisionError。但是,在异常语句的嵌套 log() 方法内,print(exc, file=fid) 试图向一个尚未打开的文件进行写操作。Python 3 抛出异常,如清单 7 所示。
清单 7. 隐式异常链示例的跟踪
Traceback (most recent call last):
File "chainExceptionExample1.py", line 3, in divide
print(a/b)
ZeroDivisionError: int division or modulo by zero During handling of the above exception, another exception occurred: Traceback (most recent call last):
File "chainExceptionExample1.py", line 12, in <module>
divide(1,0)
File "chainExceptionExample1.py", line 10, in divide
log(exc)
File "chainExceptionExample1.py", line 7, in log
print(exc, file=fid)
File "/opt/python3.0/lib/python3.0/io.py", line 1492, in write
self.buffer.write(b)
File "/opt/python3.0/lib/python3.0/io.py", line 696, in write
self._unsupported("write")
File "/opt/python3.0/lib/python3.0/io.py", line 322, in _unsupported
(self.__class__.__name__, name))
io.UnsupportedOperation: BufferedReader.write() not supported
请注意,这两个异常都被处理。在 Python 的早期版本中,ZeroDivisionError 将会丢失,得不到处理。那么这是如何实现的呢?__context__ 属性,比如 ZeroDivisionError,现在是所有异常对象的一部分。在本例中,被抛出的 IOError 的 __context__ 属性 “仍为” __context__ 属性内的 ZeroDivisionError。
除 __context__ 属性外,异常对象还有一个 __cause__ 属性,它通常被初始化为 None。这个属性的作用是以一种显式方法记录异常的原因。__cause__ 属性通过如下语法设置:
>>> raise EXCEPTION from CAUSE
它与下列代码相同:
>>>exception = EXCEPTION
>>>exception.__cause__ = CAUSE
>>>raise exception
但更为优雅。清单 8 中所示的示例展示了显式异常链。
清单 8. Python 3 中的显式异常链
class CustomError(Exception):
pass try:
fid = open("aFile.txt") # missing 'w' again
print("blah blah blah", file=fid)
except IOError as exc:
raise CustomError('something went wrong') from exc
正如之前的例子所示,print() 函数抛出了一个异常,原因是文件尚未打开以供写入。清单 9 给出了相应的跟踪。
清单 9. 异常跟踪
Traceback (most recent call last):
File "chainExceptionExample2.py", line 5, in <module>
fid = open("aFile.txt")
File "/opt/python3.0/lib/python3.0/io.py", line 278, in __new__
return open(*args, **kwargs)
File "/opt/python3.0/lib/python3.0/io.py", line 222, in open
closefd)
File "/opt/python3.0/lib/python3.0/io.py", line 615, in __init__
_fileio._FileIO.__init__(self, name, mode, closefd)
IOError: [Errno 2] No such file or directory: 'aFile.txt' The above exception was the direct cause of the following exception: Traceback (most recent call last):
File "chainExceptionExample2.py", line 8, in <modulei>
raise CustomError('something went wrong') from exc
__main__.CustomError: something went wrong
请注意,在异常跟踪中的一行 “The above exception was the direct cause of the following exception,” 之后的是另一个对导致 CustomError “something went wrong” 异常的跟踪。
添加给异常对象的另一个属性是 __traceback__。如果被捕获的异常不具备其 __traceback__ 属性,那么新的跟踪就会被设置。如下是一个简单的例子:
from traceback import format_tb try:
1/0
except Exception as exc:
print(format_tb(exc.__traceback__)[0])
请注意,format_tb 返回的是一个列表,而且此列表中只有一个异常。
with 语句
从 Python 版本 2.6 开始,with 已经成为了一个关键字,而且这一属性无需再通过 __future__ 导入。
整数支持和语法
Python 支持不同进制的整型字符串文本 — 八进制、十进制(最明显的!)和十六进制 — 而现在还加入了二进制。八进制数的表示已经改变:八进制数现在均以一个前缀 0o 或 0O(即,数字零后跟一个大写或小写的字母 o)开头。比如,八进制的 13 或十进制的 11 分别如下表示:
>>>0o13
11
新的二进制数则以前缀 0b 或 0B(即,数字零后跟一个大写或小写的字母 b)开头。十进制数 21 用二进制表示应为:
>>>0b010101
21
oct() 和 hex() 方法已被删除。
函数注释
函数注释 会在编译时将表述与函数的某些部分(比如参数)相关联。就其本身而言,函数注释是无意义的 — 即,除非第三方库对之进行处理,否则它们不会被处理。函数注释的作用是为了标准化函数参数或返回值被注释的方式。函数注释语法为:
def methodName(param1: expression1, ..., paramN: expressionN)->ExpressionForReturnType:
...
例如,如下所示的是针对某函数的参数的注释:
def execute(program:"name of program to be executed", error:"if something goes wrong"):
...
如下的示例则注释了某函数的返回值。这对于检查某函数的返回类型非常有用:
def getName() -> "isString":
...
函数注释的完整语法可以在 PEP 3107(相关链接,请参见 参考资料)内找到。
结束语
意外收获
在我看来,为 Python 3 添加的最棒的一个模块是 antigravity。启动 Python,然后在命令行键入 import antigravity。您一定不会失望。
Python 3 最终的发布版在 2008 年 12 月份初就已经发布。自那之后,我曾经查阅过一些博客,试图了解人们对向后不兼容性问题的反应。虽然,我不能断言社区在某些程度上已经达成了官方的共识,但 我阅读过的博客所呈现的观点呈两极分化。Linux® 开发社区似乎不太喜欢转变到版本 3,因为需要移植大量代码。相比之下,因为新版本对 unicode 支持方面的改进,很多 Web 开发人员则欢迎转变。
我想要说明的一点是,在您做决定是否要移植到新版本之前,应该仔细阅 读相关的 PEP 和开发邮件列表。这些 PEP 解释了各项更改的初衷、带来的益处及其实现。不难看出,这些更改的做出经过了深思熟虑和激烈讨论。本系列所展示的这些主题旨在让普通的 Python 程序员无需遍阅所有的 PEP 就能立即对这些更改有大致的概念。
Python 3 初探,第 2 部分: 高级主题的更多相关文章
- Python:高级主题之(属性取值和赋值过程、属性描述符、装饰器)
Python:高级主题之(属性取值和赋值过程.属性描述符.装饰器) 背景 学习了Javascript才知道原来属性的取值和赋值操作访问的“位置”可能不同.还有词法作用域这个东西,这也是我学习任何一门语 ...
- Python——类的高级主题
介绍关于类的一些高级主题,这些是可选的,在Python应用程序中,不会常常遇到. =========================================================== ...
- 进击的Python【第一章】:Python背景初探与Python基础(一)
Python背景初探 一.Python起源 Python的创始人为Guido van Rossum.1989年圣诞节期间,在阿姆斯特丹,Guido为了打发圣诞节的无趣,决心开发一个新的脚本解释程序,做 ...
- 简学Python第七章__class面向对象高级用法与反射
Python第七章__class面向对象高级用法与反射 欢迎加入Linux_Python学习群 群号:478616847 目录: Python中关于oop的常用术语 类的特殊方法 元类 反射 一.P ...
- Mego开发文档 - 建模高级主题
建模高级主题 在建模过程中我们还有许多其他情况,这里列出本框架中的有用特性来用于解决此类问题. 函数映射 我们可以将指定的CLR函数映射到数据库中的系统函数或自定义函数,该特性用于补充框架中未提供的数 ...
- Redis高级主题
Redis高级主题 持久化 Redis 支持持久化, 其持久化数据有两种方式. 两种可以同时使用. 如果同时使用, Reids 在重启时将使用 AOF 方式来还原数据. RDB 按照一定策略定时同 ...
- python学习9—文件基本操作与高级操作
python学习9—文件基本操作与高级操作 1. 文件基本操作 打开文件,获得文件句柄:f = open('filename',encoding='utf-8'),open会查询操作系统的编码方式,并 ...
- makefile实验四 编译本地的源文件 + 变量的高级主题一
<一>编译本地的源文件 + 变量的模式替换 实验代码 root@ubuntu:~/Makefile_Test/5make_test# vim makefile target := t ...
- Python高级主题:Python ABC(抽象基类)
#抽象类实例 作用统一规范接口,降低使用复杂度.import abcclass Animal(metaclass = abc.ABCMeta): ##只能被继承,不能实例化,实例化会报错 @abc.a ...
随机推荐
- hdu 1932(spfa)
XYZZY Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 3694 Accepted: 1059 Description ...
- Nginx网站根目录更改及导致403 forbidden的问题解决
最近因为工作需要,要将Nginx网站根目录更改下,通过网上的一些教程更改后,但发现测试的时候一直提示403 forbidden错误,后台通过一个朋友的提示也解决了,所以现在将详细的步骤分享给大家,有需 ...
- 本地配置环境打开项目出现404/本地wampserver配置伪静态以及php.ini配置
本地wamp/phpstudy实现虚拟主机后,出现了500错误看日志看到.htaccess: Invalid command ‘RewriteEngine’, perhaps misspelled o ...
- mysql TIMESTAMPDIFF
在MySQL应用时,经常要使用这两个函数TIMESTAMPDIFF和TIMESTAMPADD. 一,TIMESTAMPDIFF 语法: TIMESTAMPDIFF(interval,datetime_ ...
- selenium Select用法笔记
select_by value:参数为option中的value属性,例如:HTML代码中 <ignore_js_op> ,应该是value="volvo"这个值,所以 ...
- Retrying Operations using Spring's RetryTemplate
If your application is using Spring then it is easier to use the Spring Framework's RetryTemplate. T ...
- [haoi2009]巧克力
鉴于河南是oi弱省,所以想来这道题也没什么人会翻出来写吧,而且这还是haoi2009的一道简单题目,所以估计也没几个人会写博客的,那么看在我这么弱的份上,我觉得是应该写一篇出来的. 这道题我是按照贪心 ...
- Codeforces Round #394 (Div. 2) E. Dasha and Puzzle(分形)
E. Dasha and Puzzle time limit per test 2 seconds memory limit per test 256 megabytes input standard ...
- 【树上莫队】【带修莫队】【权值分块】bzoj1146 [CTSC2008]网络管理Network
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using ...
- python3-开发进阶Django-debug-toolbar的配置和Django logging的配置
阅读目录 django-debug-toolbar的配置 Django logging的配置 一.django-debug-toolbar的配置 1.介绍 django-debug-toolbar 是 ...
内容