使用元类将Python2转换为Python3导致错误流程
问题描述:
我有一个非常大的Python 2.7.6项目,我需要将其转换为Python 3.4。我用2to3脚本,但'元类'处理似乎被打破。使用元类将Python2转换为Python3导致错误流程
我过滤了代码以缩短并查明问题。
class Base(object):
class __metaclass__(type):
def __new__(cls, classname, bases, dict):
new = type.__new__(cls, classname, bases, dict)
new.classname = classname
print ("Base::__metaclass__::new. Called.")
return new
class Heir(Base):
class __metaclass__(Base.__metaclass__):
def __new__(self, *args):
new = Base.__metaclass__.__new__(self, *args)
print ("Heir::__metaclass__::new. Called.")
return new
@classmethod
def define(cls, nexttype):
print ("Heir::define. Called.")
class HeirOfHeir(Heir):
pass
Heir.define(HeirOfHeir)
代码打印效果与预期:
Base::__metaclass__::new. Called.
Base::__metaclass__::new. Called.
Heir::__metaclass__::new. Called.
Base::__metaclass__::new. Called.
Heir::__metaclass__::new. Called.
Heir::define. Called.
但与Python 3.4中运行的代码时,我只有最后一次打印:
Heir::define. Called.
下面的片段与Python 2.7.6效果很好
2to3
错误计算或需要一些手动工作。不幸的是,我对元类没有多少经验。
答
你原来的代码使用的事实,这是名__metaclass__
在类体内被用作元类,但2to3
定影液只查找直分配:
__metaclass__ = MetaClassName
,而不是class __metaclass__
声明或其他定义名称的方式(from somemodule import MetaClassName as __metaclass__
将在Python 2类主体中工作,而2to3
也会错过)。
您可以通过移动元类解决这个问题分开class
定义:
class BaseMeta(type):
def __new__(cls, classname, bases, dict):
new = type.__new__(cls, classname, bases, dict)
new.classname = classname
print ("BaseMeta::new. Called.")
return new
class Base(object):
__metaclass__ = BaseMeta
class HeirMeta(BaseMeta):
def __new__(self, *args):
new = BaseMeta.__new__(self, *args)
print ("HeirMeta::new. Called.")
return new
class Heir(Base):
__metaclass__ = HeirMeta
@classmethod
def define(cls, nexttype):
print ("Heir::define. Called.")
class HeirOfHeir(Heir):
pass
Heir.define(HeirOfHeir)
你有做到这一点在Python 3 反正定义元类,作为机制来定义元类改为在之前确定元类而不是在期间运行类体(以便元类也可以影响该步骤)。
现在2to3
将正确地检测到存在于你的班__metaclass__
属性和重写那些使用新的Python 3语法:
stackoverflow-2.7 $ bin/python -m lib2to3 fixed.py
RefactoringTool: Skipping implicit fixer: buffer
RefactoringTool: Skipping implicit fixer: idioms
RefactoringTool: Skipping implicit fixer: set_literal
RefactoringTool: Skipping implicit fixer: ws_comma
RefactoringTool: Refactored fixed.py
--- fixed.py (original)
+++ fixed.py (refactored)
@@ -5,8 +5,8 @@
print ("BaseMeta::new. Called.")
return new
-class Base(object):
- __metaclass__ = BaseMeta
+class Base(object, metaclass=BaseMeta):
+ pass
class HeirMeta(BaseMeta):
def __new__(self, *args):
@@ -14,9 +14,7 @@
print ("HeirMeta::new. Called.")
return new
-class Heir(Base):
- __metaclass__ = HeirMeta
-
+class Heir(Base, metaclass=HeirMeta):
@classmethod
def define(cls, nexttype):
print ("Heir::define. Called.")
RefactoringTool: Files that need to be modified:
RefactoringTool: fixed.py
和重构的代码按预期工作:
stackoverflow-2.7 $ bin/python -m lib2to3 -o ../stackoverflow-3.4 -nw --no-diffs fixed.py
lib2to3.main: Output in '../stackoverflow-3.4' will mirror the input directory '' layout.
RefactoringTool: Skipping implicit fixer: buffer
RefactoringTool: Skipping implicit fixer: idioms
RefactoringTool: Skipping implicit fixer: set_literal
RefactoringTool: Skipping implicit fixer: ws_comma
RefactoringTool: Refactored fixed.py
RefactoringTool: Writing converted fixed.py to ../stackoverflow-3.4/fixed.py.
RefactoringTool: Files that were modified:
RefactoringTool: fixed.py
stackoverflow-2.7 $ cd ../stackoverflow-3.4
stackoverflow-3.4 $ bin/python -V
Python 3.4.2
stackoverflow-3.4 $ bin/python fixed.py
BaseMeta::new. Called.
BaseMeta::new. Called.
HeirMeta::new. Called.
BaseMeta::new. Called.
HeirMeta::new. Called.
Heir::define. Called.
我还建议第六单元(https://pythonhosted.org/six/)用于编写与Py2和Py3兼容的代码。在这种情况下,你只需要在class Heir定义之前使用'@ six.add_metaclass(HeirMeta)'装饰器。 – 2015-02-24 14:25:09
我无法使用任何编码范例,因为整个项目非常庞大,需要太多时间才能使其兼容2/3。但是我有大约12-15个模块,只有元类,我会尝试采用上述方法。 – OGP 2015-02-25 04:56:42