返回介绍

从 Python 3 移植

发布于 2025-04-20 18:52:18 字数 5583 浏览 0 评论 0 收藏

Python 3 添加了很多 Python 2 没有的功能,可以把这些用法移植到使用 Python 2 的项目中。

partialmethod

顾名思义,partialmethod 是作用于类方法的 partial 函数:

def get_name(self):
    return self._name
 
     
class Cell(object):
    def __init__(self):
        self._alive=False
      
    @property
    def alive(self):
        return self._alive
      
    def set_state(self, state):
        self._alive=bool(state)
    set_alive=partialmethod(set_state, True)
    set_dead=partialmethod(set_state, False)
      
    get_name=partialmethod(get_name)

这样使用它:

In : cell=Cell('cell_1')
In : cell.alive
Out: False
In : cell.set_alive()
In : cell.alive
Out: True
In : cell.get_name()
Out: 'cell_1'

在 Python 2 中可以这样实现:

from functools import partial
       
                 
class partialmethod(partial): # noqa
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return partial(self.func, instance,
                *(self.args or ()),**(self.keywords or{}))

这个使用描述符实现的版本并不是 Python 3 标准库中的实现,它只能作用于类方法,不能作用于已经被 partialmethod 包装的方法。举个 Python 3 的例子:

from functools import partialmethod
      
               
class Request(object):
    default_url='http://www.dongwm.com'
      
    def request(self, method, url, params=None, data=None):
        print('execute request:{}'.format(url))
      
    get=partialmethod(request, 'GET')
    post=partialmethod(request, 'POST')
    put=partialmethod(request, 'PUT')
    delete=partialmethod(request, 'DELETE')
      
    get_default_url=partialmethod(get, default_url)

使用 Python 2 版本的 partialmethod 不能包装这个例子中的 get 方法,也就是不能实现 get_default_url 这个方法。如果有这样的需求,就需要从 Python 3 标准库中移植代码了。

singledispatch

Python 3.4 为 functools 模块引入了将普通函数转换为泛型函数的工具 singledispatch。泛型设计是一种编程范式,泛型函数是一组实现不同类型输入,但是执行相同操作的的函数。

在 Python 2 中使用 singledispatch 需要安装第三方包:

> pip install singledispatch

我们知道 JSON 默认并不能序列化时间格式的值,举个例子:

import json
from datetime import date, datetime
   
       
class Board(object):
    def __init__(self, id, name, create_at=None):
        self.id=id
        self.name=name
        if create_at is None:
            create_at=datetime.now()
        self.create_at=create_at
      
    def to_dict(self):
        return{'id':self.id, 'name':self.name,
              'create_at':self.create_at}
      
board=Board(1, 'board_1')

如果想序列化 to_dict 方法的结果,就会报错:

In : print(json.dumps(board.to_dict()))
---------------------------------------------------------------------------
TypeError                    Traceback (most recent call last)
<ipython-input-2-9225d99f1e8c> in <module>()
----> 1 print(json.dumps(Board(1, 'board_1').to_dict()))
...
TypeError:datetime.datetime(2016, 6, 08, 4, 14, 29, 857598) is not JSON serializable

这个时候需要在 to_dict 方法中对时间格式的值做特殊处理,通常是添加一个 encoder 函数,函数代码如下:

def json_serial(obj):
    if isinstance(obj, datetime):
        serial=obj.isoformat()
        return serial
    TypeError(repr(obj)+ ' is not JSON serializable')
      
json.dumps(board.to_dict(), default=json_serial)

特殊类型越多,这样的判断也就越多,性能越差。如果使用 singledispatch 实现,则代码如下:

from singledispatch import singledispatch
      
                 
@singledispatch
def json_encoder(obj):
    raise TypeError(repr(obj)+ ' is not JSON serializable')
       
               
@json_encoder.register(date)
@json_encoder.register(datetime)
def encode_date_time(obj):
    return obj.isoformat()
      
json.dumps(board.to_dict(), default=json_encoder)

除了转换函数,还可以转换方法:

from functools import update_wrapper
       
                
def methdispatch(func):
    dispatcher=singledispatch(func)
      
    def wrapper(*args, **kw):
        return dispatcher.dispatch(args[1].__class__)(*args, **kw)
    wrapper.register=dispatcher.register
    update_wrapper(wrapper, func)
    return wrapper

现在给 Board 添加两个泛型方法:

@methdispatch
def get(self, arg):
    return getattr(self, arg, None)
      
@get.register(list)
def_(self, arg):
    return [self.get(x) for x in arg]

现在就可以对 get 方法传入不同的参数获取不同类型的结果了:

In : print(board.get('name'))
board_1
In : print(board.get(['id', 'create_at']))
[1, datetime.datetime(2016, 5, 17, 4, 14, 29, 857598)]

suppress

Python 3 的 contextlib 模块中,suppress 通过 with 语句忽略指定的异常,Python 2 的移植版本如下:

from contextlib import contextmanager
      
              
@contextmanager
def suppress(*exceptions):
    try:
        yield
    except exceptions:
        pass
      
              
with suppress(OSError):
    os.remove('/no/such/file')

上述的删除不存在对文件的操作,所以不会抛出异常。

redirect_stdout/redirect_stderr

有时候为了调试之类的目的,希望把输出重定向到某个文件。之前的做法是:

with open('help.txt', 'w') as f:
    oldstdout=sys.stdout
    sys.stdout=f
    try:
        help(pow)
    finally:
        sys.stdout=oldstdout

Python 3 的 contextlib 提供了更优雅的方式,Python 2 的移植版本如下:

@contextmanager
def redirect_stdout(fileobj, std_type='stdout'):
    oldstdout=getattr(sys, std_type)
    setattr(sys, std_type, fileobj)
    try:
        yield fileobj
    finally:
        setattr(sys, std_type, oldstdout)
       
               
redirect_stderr=partial(redirect_stdout, std_type='stderr')

可以通过如下方式使用它们:

with open('help_out.txt', 'w') as out, open('help_err.txt', 'w') as err:
    with redirect_stdout(out), redirect_stderr(err):
    msg='Test'
    sys.stdout.write('(stdout) A:{!r}\n'.format(msg))
sys.stderr.write('(stderr) A:{!r}\n'.format(msg))

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。