程序员文章、书籍推荐和程序员创业信息与资源分享平台

网站首页 > 技术文章 正文

python散装笔记——149: 从Python 2迁移到Python 3的不兼容性(四)

hfteth 2025-03-26 12:30:17 技术文章 6 ℃

16: 在Python 3中,所有类都是“新式类”

在Python 3.x中,所有类都是新式类;在定义一个新类时,Python会隐式地使其继承自object。因此,在类定义中指定object是完全可选的:

Python 3.x Version ≥ 3.0

class X: pass
class Y(object): pass

这两个类现在都包含object在其方法解析顺序(mro)中:

Python 3.x Version ≥ 3.0

>>> X.__mro__
(__main__.X, object)
>>> Y.__mro__
(__main__.Y, object)

在Python 2.x中,默认情况下,类是旧式类;它们不会隐式地继承自object。这导致类的语义取决于我们是否显式地将object作为基类添加:

Python 2.x Version ≥ 2.3

class X: pass
class Y(object): pass

在这种情况下,如果我们尝试打印Y__mro__,将出现与Python 3.x情况类似的输出:

Python 2.x Version ≥ 2.3

>>> Y.__mro__
(, )

这是因为我们在定义Y时显式地使其继承自objectclass Y(object): pass。对于没有继承自object的类X__mro__属性不存在,尝试访问它将导致AttributeError

为了确保与Python的两个版本兼容,类可以定义为以object为基类:

class mycls(object):
  """I am fully compatible with Python 2/3"""

或者,如果在全局作用域中将__metaclass__变量设置为type,则在给定模块中随后定义的所有类都将隐式地成为新式类,而无需显式地继承自object

__metaclass__ = type

class mycls:
  """I am also fully compatible with Python 2/3"""

17: reduce不再是内置函数

在Python 2中,reduce可以作为内置函数使用,也可以从functools包中使用(从2.6版本开始),而在Python 3中,reduce仅从functools中可用。然而,reduce在Python 2和Python 3中的语法是相同的,即reduce(function_to_reduce, list_to_reduce)

例如,让我们考虑通过将相邻数字相除将列表简化为一个值。这里我们使用operator库中的truediv函数。

在Python 2中,这很简单:

Python 2.x Version ≥ 2.3

>>> my_list = [1, 2, 3, 4, 5]
>>> import operator
>>> reduce(operator.truediv, my_list)
0.008333333333333333

在Python 3中,示例变得稍微复杂一些:

Python 3.x Version ≥ 3.0

>>> my_list = [1, 2, 3, 4, 5]
>>> import operator, functools
>>> functools.reduce(operator.truediv, my_list)
0.008333333333333333

我们也可以使用from functools import reduce来避免使用命名空间名称调用reduce

18: 绝对/相对导入

在Python 3中,PEP 404改变了Python 2中导入的工作方式。不再允许在包中使用隐式相对导入,from ... import *导入仅允许在模块级别代码中使用。

为了在Python 2中实现Python 3的行为:

  • 可以通过from __future__ import absolute_import启用absolute_import功能
  • 鼓励使用显式相对导入,而不是隐式相对导入

为了澄清,在Python 2中,一个模块可以通过以下方式导入位于同一目录中的另一个模块的内容:

import foo

注意,仅从导入语句中无法确定foo的位置。因此,这种类型的隐式相对导入被不推荐使用,而推荐使用显式相对导入,如下所示:

from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo
from ...package import bar
from ...sys import path

.允许明确声明模块在目录树中的位置。

关于相对导入的更多信息

考虑一个名为shapes的用户定义包。目录结构如下:

shapes
├── __init__.py
|
├── circle.py
|
├── square.py
|
└── triangle.py

circle.pysquare.pytriangle.py都作为模块导入了util.py。它们将如何引用同一级别的模块?

from . import util # use util.PI, util.sq(x), etc

或者

from .util import * #use PI, sq(x), etc to call functions

.用于同一级别的相对导入。

现在,考虑shapes模块的另一种布局:

shapes
├── __init__.py
|
├── circle
│ ├── __init__.py
│ └── circle.py
|
├── square
│ ├── __init__.py
│ └── square.py
|
├── triangle
│ ├── __init__.py
│ ├── triangle.py
|
└── util.py

现在,这三个类将如何引用util.py

from .. import util # use util.PI, util.sq(x), etc

或者

from ..util import * # use PI, sq(x), etc to call functions

..用于父级别的相对导入。根据父级和子级之间的级别数量,添加相应数量的.

19: map()

map()是一个内置函数,用于对可迭代对象的元素应用函数。在Python 2中,map返回一个列表。在Python 3中,map返回一个map对象,这是一个生成器。

# Python 2.X
>>> map(str, [1, 2, 3, 4, 5])
['1', '2', '3', '4', '5']
>>> type(_)
>>> 

# Python 3.X
>>> map(str, [1, 2, 3, 4, 5])

>>> type(_)


# 我们需要再次应用map,因为我们“消耗”了之前的map....
>>> map(str, [1, 2, 3, 4, 5])
>>> list(_)
['1', '2', '3', '4', '5']

在Python 2中,你可以传递None作为map的函数参数,以用作恒等函数。在Python 3中,这不再有效。

Python 2.x Version ≥ 2.3

>>> map(None, [0, 1, 2, 3, 0, 4])
[0, 1, 2, 3, 0, 4]

Python 3.x Version ≥ 3.0

>>> list(map(None, [0, 1, 2, 3, 0, 5]))
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'NoneType' object is not callable

此外,在Python 2中,传递多个可迭代对象作为参数时,map会用None填充较短的可迭代对象(类似于itertools.izip_longest)。在Python 3中,迭代会在最短的可迭代对象结束时停止。

在Python 2中:

Python 2.x Version ≥ 2.3

>>> map(None, [1, 2, 3], [1, 2], [1, 2, 3, 4, 5])
[(1, 1, 1), (2, 2, 2), (3, None, 3), (None, None, 4), (None, None, 5)]

在Python 3中:

Python 3.x Version ≥ 3.0

>>> list(map(lambda x, y, z: (x, y, z), [1, 2, 3], [1, 2], [1, 2, 3, 4, 5]))
[(1, 1, 1), (2, 2, 2)]

# 为了在Python 2中获得相同的填充效果,请使用itertools中的zip_longest
>>> import itertools
>>> list(itertools.zip_longest([1, 2, 3], [1, 2], [1, 2, 3, 4, 5]))
[(1, 1, 1), (2, 2, 2), (3, None, 3), (None, None, 4), (None, None, 5)]

注意:考虑使用列表推导式代替map,因为它们在Python 2和Python 3中都是兼容的。替换map(str, [1, 2, 3, 4, 5])

>>> [str(i) for i in [1, 2, 3, 4, 5]]
['1', '2', '3', '4', '5']

20: round()函数的四舍五入规则和返回类型

round()四舍五入规则

在Python 2中,使用round()对一个与两个整数等距离的数字进行四舍五入时,会返回离0最远的那个整数。例如:

Python 2.x Version ≤ 2.7

round(1.5) # Out: 2.0
round(0.5) # Out: 1.0
round(-0.5) # Out: -1.0
round(-1.5) # Out: -2.0

然而,在Python 3中,round()会返回偶数整数(也称为银行家舍入)。例如:

Python 3.x Version ≥ 3.0

round(1.5) # Out: 2
round(0.5) # Out: 0
round(-0.5) # Out: 0
round(-1.5) # Out: -2

round()函数遵循半舍入到偶数的四舍五入策略,现在round(2.5)返回2而不是3.0。

根据维基百科的参考,这还被称为无偏四舍五入、收敛四舍五入、统计学家四舍五入、荷兰四舍五入、高斯四舍五入或奇偶四舍五入。

半舍入到偶数的四舍五入是IEEE 754标准的一部分,也是微软.NET中的默认四舍五入模式。

这种四舍五入策略倾向于减少总四舍五入误差。由于平均而言,向上舍入的数字量与向下舍入的数字量相同,因此四舍五入误差相互抵消。其他四舍五入方法则倾向于存在向上或向下的偏差。

round()返回类型

在Python 2.7中,round()函数返回一个float类型。

Python 2.x Version ≤ 2.7

round(4.8)
# 5.0

从Python 3.0开始,如果省略第二个参数(小数位数),则返回一个int

Python 3.x Version ≥ 3.0

round(4.8)
# 5

Tags:

最近发表
标签列表