`
liuxinglanyue
  • 浏览: 564739 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Python 3 初探,第 2 部分: 高级主题

阅读更多

转自:Cesar Otero

简介: 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 SizedIterableContainer
MutableSequence Sequence
Set SizedIterableContainer
MutableSet Set
Mapping SizedIterableContainer
MutableMapping Mapping
MappingView Sized
KeysView MappingViewSet
ItemsView MappingViewSet
ValuesView MappingView

集合

集合框架包括容器数据类型、双端队列(即 deque)以及一个默认的字典(即 defaultdict )。一个 deque 支持从前面或后面进行追加和弹出。defaultdict 容器是内置字典的一个子类,它(根据 Python 3 文档)“覆盖一个方法并添加一个可写的实例变量。”除此之外,它还充当一个字典。此外,集合框架还提供了一个数据类型工厂函数namedtuple()

ABC 类型层次结构

Python 3 现支持能代表数值类的 ABC 的类型层次结构。这些 ABC 存在于 numbers 模块内并包括 Number、 ComplexReal、 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:
    ...

  


  
分享到:
评论

相关推荐

    Beginning Python Using Python2.6 and Python3.1

    ##### 第一部分:初探Python - **第1章:编程基础与字符串**:介绍Python的基础知识,包括编程环境的搭建、基本的数据类型、变量以及字符串的操作方法。 - **第2章:数字与运算符**:详细介绍Python中的数值类型和...

    python免费视频教程(初高中级)+基础教程.docx

    #### Python 第25课:初探list - **知识点介绍**:介绍列表的基本概念和操作方法。 - **实操要点**:创建和操作列表,实现基本的数据存储功能。 #### Python 第26课:操作list - **知识点介绍**:进一步探讨列表的...

    基于计算机等级考试的Python教学方法初探.zip

    在计算机等级考试中,Python部分考察的是学生的编程基础、逻辑思维能力和问题解决能力。因此,教学过程中应注重培养学生的这些核心素养,而不仅仅是语言语法。 二、教学内容与考试大纲对接 教学内容应紧密围绕...

    python基础教程至60课(基础).docx

    #### Python第25课:初探list - **知识点概述**:介绍列表数据类型的基础知识。 - **详细内容**:列表是一种有序的、可变的数据集合。可以通过方括号`[]`创建列表。列表支持多种操作,如索引、切片等。 #### Python...

    Python基础教程60课

    #### 五、高级主题 - **【Python第55课】正则表达式(1)** 正则表达式是一种强大的文本处理工具。本课将介绍正则表达式的基础知识,包括元字符、字符类等。 - **【Python第56课】正则表达式(2)** 进一步...

    Python程序设计课程中的课堂思政元素初探.zip

    "Python程序设计课程中的课堂思政元素初探"这个主题,旨在探讨如何在教授Python编程语言的同时,有效地引入思政教育,让学生在学习技术的同时,也能提升道德品质和社会责任感。 首先,Python是一种广泛使用的高级...

    OpenCV学习资料文档

    ### OpenCV 学习资料文档知识点汇总 ...这部分内容涉及了直方图的相关概念和技术,以及如何利用直方图进行图像匹配等高级主题。由于提供的文档部分内容较短,无法全面覆盖该章节所有细节,但以上总结涵盖了关键知识点。

    Microsoft-DAT208x-Introduction-to-Python-for-Data-Science

    《Python for Data Science初探——基于Microsoft DAT208x课程》 在现代数据分析领域,Python语言已经成为不可或缺的工具,尤其在数据科学方面,它的强大功能和易用性深受广大用户的喜爱。Microsoft DAT208x课程...

    IADS-cw:爱丁堡大学算法和数据结构入门课程

    《IADS-cw:爱丁堡大学算法与数据结构初探》 爱丁堡大学的IADS(Introduction to Algorithms and Data ...通过这些练习,学生可以掌握Python的高级特性和面向对象编程概念,为未来的学习和职业发展打下坚实基础。

Global site tag (gtag.js) - Google Analytics