简体   繁体   English

使用元类将Python2转换为Python3会导致错误的流程

[英]Converting Python2 to Python3 with metaclasses resulted in a wrong flow

I have a very large Python 2.7.6 project which I need to convert to Python 3.4. 我有一个非常大的Python 2.7.6项目,我需要将其转换为Python 3.4。 I used 2to3 script but 'metaclass' processing seems to be broken. 我使用了2to3脚本,但'metaclass'处理似乎被打破了。

I filtered the code to shorten and pinpoint the problem. 我过滤了代码以缩短并查明问题。 The following fragment works well with Python 2.7.6: 以下片段适用于Python 2.7.6:

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)

The code prints as expected: 代码按预期打印:

Base::__metaclass__::new. Called.
Base::__metaclass__::new. Called.
Heir::__metaclass__::new. Called.
Base::__metaclass__::new. Called.
Heir::__metaclass__::new. Called.
Heir::define. Called.

But when running code with Python 3.4 I have only the last print: 但是当使用Python 3.4运行代码时,我只有最后一个打印:

Heir::define. Called.

Either 2to3 miscalculated or there is some manual work required. 无论是2to3失算或者还需要一些手工的工作。 I have little experience with metaclasses unfortunately. 不幸的是,我对元类很少有经验。

Your original code uses the fact that it is the name __metaclass__ in the class body is used as the meta class, but the 2to3 fixer only looks for straight assignments : 您的原始代码使用以下事实:类主体中的名称 __metaclass__用作元类,但2to3修复程序仅查找直接赋值

__metaclass__ = MetaClassName

rather than a class __metaclass__ statement or other manner of defining the name ( from somemodule import MetaClassName as __metaclass__ would work in a Python 2 class body and 2to3 would miss that too). 而不是class __metaclass__语句或其他方式定义名称( from somemodule import MetaClassName as __metaclass__将在Python 2类体中工作, 2to3也会错过)。

You can fix this by moving the meta classes to separate class definitions: 您可以通过将元类移动到单独的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)

You'll have to do this to define metaclasses in Python 3 anyway , as the mechanism to define metaclasses was changed to determining the metaclass before the class body is run rather than during (so that a metaclass can influence that step too). 无论如何 ,你必须这样做才能在Python 3中定义元类,因为定义元类的机制被改为运行类体之前而不是在期间确定元类(因此元类也可以影响该元素)。

Now 2to3 will correctly detect that there is a __metaclass__ attribute on your classes and rewrite those to use the new Python 3 syntax: 现在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

and the refactored code works as expected: 并且重构的代码按预期工作:

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.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM