`
liuxinglanyue
  • 浏览: 546647 次
  • 性别: 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:
    ...

  


  
分享到:
评论

相关推荐

    python-base.py: 千行代码入门Python python-visual.py: 15张图入门Matplotlib

    python_lda.py: 玩点高级的--带你入门Topic模型LDA(小改进+附源码) python_sqlalchemy.py: 作为一个Pythoner, 不会SQLAlchemy都不好意思跟同行打招呼! python_oneline.py: 几个小例子告诉你, 一行Python代码能干...

    Python 100 天学习计划 从小白到工程师的学习之路

    第3天:Python 变量与数据类型 第4天:Python 流程控制 第5天:Python 函数 第6天:Python 模块和包 第7天:Python 序列 第8天:Python 列表 第9天:Python 元组 第10天:Python 类与对象 第11天:Python dictionary...

    python详细学习教程.rar

    第二章:Python初探 第三章:变量类型和运算符 第四章:列表,元组,字典和集合 第五章:Python字符串常用方法 第六章:Python流程控制 第七章:函数和lambda表达式 第八章:Python类和对象 第九章:Python异常处理...

    青少年编程能力等级第 2 部分:Python编程.pdf

    《青少年编程能力等级》标准联合发布会...据悉,该系列标准主要针对课外培训领域,旨在解决阶梯型课程体系目标指引缺乏、培训内容良莠不齐、课程设计体系缺乏等问题,文档为青少年编程能力等级第 2 部分:Python编程部分

    Python3高级教程_python3_高级教程_电子版_

    Python3高级教程,接着上面的零基础教程,很好用

    青少年编程能力等级 第二部分:python编程.pdf

    青少年编程能力等级 第二部分:python编程 2019.11

    Python: Learn Python in 24 Hours: Complete Beginners Guide [2016]

    Python: Learn Python in 24 Hours: Complete Beginners Guide English | 19 July 2016 | ASIN: B01IRGB6MY | 110 Pages If you are one of them who easily get scared of Python’s long, complicated code, then...

    青少年编程能力等级 第2 部分:Python 编程.pdf

    青少年编程能力等级 第2 部分:Python 编程正式发布版,标准编号:T/CERACU /AFCEC/SIA/CNYPA 100.2—2019

    Python3入门视频教程百度网盘.pdf

    Python3⼊门视频教程百度⽹盘 视频内容: 第1章 Python⼊门导学 第2章 Python环境安装 第3章 理解什么是写代码与Python的基本类型 第4章 Python中表⽰"组"的概念与定义 第5章 变量与运算符 第6章 分⽀、循环、条件与...

    Python程序设计:Python介绍.pptx

    任务 搭建Python开发环境 任务知识点 Python介绍 Python开发环境 Python程序设计:Python介绍全文共13页,当前为第3页。 知识点:Python介绍 Python程序设计:Python介绍全文共13页,当前为第4页。 Python介绍 ...

    Python高级编程

    Python高级编程Python高级编程Python高级编程Python高级编程Python高级编程

    Python高级编程.pdf

    Python高级编程

    python高级编程中文版

    python高级编程中文版,python高级编程中文版 ,python高级编程中文版

    python全栈开发第9期视频教程.txt

    Python全栈9期(第五部分):django高级 Python全栈9期(第四部分):django进阶 Python全栈9期(第十一部分):django rest framework + vue Python全栈9期(第十二部分):Git + 路飞学城(01) Python全栈9期...

    Python毕业设计:车牌识别系统源码.zip

    Python毕业设计:车牌识别系统源码Python毕业设计:车牌识别系统源码Python毕业设计:车牌识别系统源码Python毕业设计:车牌识别系统源码Python毕业设计:车牌识别系统源码Python毕业设计:车牌识别系统源码Python...

    python免费课件-零基础python入门(含课件代码)PDF高清版.pdf

    ⽬录 第⼀章:课程介绍及python发展⽅向 第⼆章:程序设计与python语⾔ 第三章:案例1 -- 汇率兑换 第四章:案例2 -- 分形树的绘制 第五章:案例3 -- 基础代谢率(BMR)计算 第六章:案例4 -- 52周存钱挑战 第七章:...

    Python参考手册第四版

    书中还包括了一些没有在Python官方文档或其他资料中出现过的一些高级的主题。 这一版在内容上进行了全面更新。介绍了Python 2.6 和Python 3新引入的编程语言特性和库模块。同时还分析了Python程序员面临的如下难题:...

    python学习手册第8部分&附录

    python学习手册高级话题 第八部分&附录 python学习手册

    PYTHON参考手册 第4版_修订版

    此外,书中还包括一些Python官方文档或其他参考资料中未提及的高级主题。 作者简介 David M. Beazley 早在1996 年就开始使用Python 编程。在洛斯阿拉莫斯国家实验室工作期间,他教会很多志愿者用Python 编写科学计算...

Global site tag (gtag.js) - Google Analytics