更改超类而不修改原始代码

时间:2016-07-19 13:53:40

标签: python oop

我正在GTK中编写应用程序。我们需要支持VTE终端API 2.90和2.91。所以我在名为'兼容性的模块中创建了两者之间的愚蠢兼容层。看起来像这样。

import gi
try:
    gi.require_version('Vte', '2.91')
    vte_version = '2.91'
except ValueError:
    gi.require_version('Vte', '2.90')
    vte_version = '2.90'

from gi.repository import Vte


class CompatibleVteTerminal(Vte.Terminal):
    """A simple VTE terminal modified to be compatible with both 2.90
    and 2.91 API"""
    def __init__(self):
        Vte.Terminal.__init__(self)

    def spawn_sync(self, pty_flags, working_directory, argument_vector,
                   env_variables, glib_spawn_flags, child_setup,
                   child_setup_data, cancellable=None):
        """Returns the corresponden version os 'spawn_sync' method
        according to the Vte version the user has"""
        if vte_version == '2.91':
            return Vte.Terminal.spawn_sync(self, pty_flags, working_directory,
                                           argument_vector, env_variables,
                                           glib_spawn_flags, child_setup,
                                           child_setup_data, cancellable)
        elif vte_version == '2.90':
            return Vte.Terminal.fork_command_full(self, pty_flags,
                                                  working_directory,
                                                  argument_vector, env_variables,
                                                  glib_spawn_flags, child_setup,
                                                  child_setup_data, cancellable)

现在,我已经在另一个模块中创建了一个终端。

class Terminal(Vte.Terminal):
   """Do terminal stuff"""

当然,我希望这个类现在从我的兼容终端继承。

from compatibility import CompatibleVteTerminal as Vte.Terminal

class Terminal(Vte.Terminal):
    """Do terminal stuff"""

但是这给了我一个无效的语法'错误。显然你不能在名字中导入带点的东西。如何在不修改原始代码的情况下导入兼容终端?

1 个答案:

答案 0 :(得分:1)

我真的认为有更好的方法,这就是为什么我最初发布的评论而不是答案,但为了说明如何创建动态模块并为其添加类,请考虑以下内容:

class AlternativeCounter:
    def __init__(self, *args):
        pass

    def __repr__(self):
        return "HA! I'm an imposter!"


use_stdlib_collections = False

if use_stdlib_collections:
    import collections
else:
    import imp
    collections = imp.new_module('collections')
    collections.Counter = AlternativeCounter


class MyCounter(collections.Counter):
    def foo(self):
        print("I'm a %r" % self.__class__.__name__)
        print("My parents are: %r" % self.__class__.__bases__)


c = MyCounter("12321")
c.foo()
print(c)

如果use_stdlib_collections = True,则输出:

I'm a 'MyCounter'
My parents are: <class 'collections.Counter'>
MyCounter({'2': 2, '1': 2, '3': 1})

如果use_stdlib_collections = False,则输出:

I'm a 'MyCounter'
My parents are: <class '__main__.AlternativeCounter'>
HA! I'm an imposter!

代码的顺序可能看起来很奇怪,为了将它全部保存在一个文件中,我必须在条件导入机制之上定义AlternativeCounter。在实践中,代码可能看起来更像:

use_stdlib_collections = False

if use_stdlib_collections:
    import collections
else:
    import imp
    collections = imp.new_module('collections')
    from some_other_module import AlternativeCounter
    collections.Counter = AlternativeCounter

# ...

AlternativeCounter将放置在外部some_other_module模块中。

根据您的使用情况,您甚至可能不需要创建实际模块,您可以使用空类:

# ...

class FakeModule: pass

if use_stdlib_collections:
    import collections
else:
    collections = FakeModule()
    collections.Counter = AlternativeCounter

#...

请注意,在任何一种情况下,任何附加引用collections模块(例如collections.OrderedDict)(或者在您的情况下,对{{1)的其他引用没有额外的工作就会失败。

修改

由于您想要访问模块的其他属性(在您的案例Vte中,在我的Vte中),您将需要一种在所有情况下“退回”真实模块的方法除了你明确覆盖的那些。

使用伪装成模块的类的方法,您可以执行以下操作:

collections

这里,class AlternativeCounter: def __init__(self, *args): pass def __repr__(self): return "HA! I'm an imposter!" class ModuleWithFallback: def __init__(self, backup): self.backup = backup # Provide a fallback mechanism for un-overridden attribute access def __getattr__(self, name): return getattr(self.backup, name) use_stdlib_collections = False if use_stdlib_collections: import collections else: import collections as std_collections collections = ModuleWithFallback(std_collections) collections.Counter = AlternativeCounter class MyCounter(collections.Counter): def foo(self): print("I'm a %r" % self.__class__.__name__) print("My parents are: %r" % self.__class__.__bases__) c = MyCounter("12321") c.foo() print(c) print("---") # Despite defaultdict not being overridden, it still works as you would expect. dd = collections.defaultdict(int) dd["bar"] += 1 print(dd["bar"]) # 1 print(type(dd)) # <class 'collections.defaultdict'> 是一个简单的类似代理的类。它的构造函数只接受一个参数,一个模块的参数,我在别名ModuleWithFallback下导入,以免冲突。现在,当访问一个未在类上定义的属性时(在上面的示例中我使用std_collections),它会尝试返回“backup”模块的相应属性(在我的例子中,真正的收集模块)。

请注意,此仍然可能不起作用,具体取决于您的课程的具体情况,但使用此代码至少您有机会。