14

我创建了一个 python-packages /MyLibPackage,我将在我的项目中导入它。

MyLibPackage.____init____.py包括 mymodiciation.py。此外,MyLibPackage 文件夹包含另一个文件:base_classes.py(=external project)

mymodiciation.py 导入“ from base_classes import *”。

目标:我可以导入 MyLibPackage,它包含来自 base_classes 的所有类(=外部项目)。如果我需要修改一些类或函数,我可以在 mymodiciation.py 中覆盖它。它有效,但我遇到了问题。例如:

我在 mymodiciation.py 中覆盖了这些类:

class Bookcollection(Bookcollection):
   new_member = "lalala"


class user(user):
   def get_books(self):
      return Bookcollection()

如果我做:

from MyLibPackage import *
x = user()
books = x.get_books()

那么对象 Bookcollection 具有属性“new_member”。好的!但如果我这样做:

from MyLibPackage import *
x = shelf() #this class is not overwritten and used also the object "Bookcolelction"
books = x.get_books()

那么对象 Bookcollection 没有属性“new_member”,因为他是用 MyLibPackage.base_classes.Bookcollection 而不是我覆盖的类 MyLibPackage.mymodiciation.Bookcollection 实例化的

我怎么说:如果我在 mymodiciation 中覆盖了一个类,那么 MyLibPackage 必须使用它,尽管当调用来自 MyLibPackage.base_classes.shelf (get_books) 时。

4

2 回答 2

24

您要做的称为“猴子补丁”,与面向对象无关。

Python 确实支持它,但是您可以控制所有类,您应该认真检查您的项目以检查您是否真的需要它。

也许使用像 Zope 组件架构这样的框架,它允许您使用接口标记类,并提供适配器对象,以便您可以干净地使用一个对象,因为它具有一些最初没有被设计为具有的接口,这将是一个更好的主意。

也就是说,您要求的是在另一个模块中更改类,它所在的位置 - 以便所有其他模块都可以看到更改。

你就是这样做的:更改它所属的模块中的类。在 Python 中,只需在源模块中将您的新类归因于所需的名称即可完成:

import base_classes

class Bookcollection(base_classes.Bookcollection):
   new_member = "lalala"

base_classes.Bookcollection = Bookcollection

(要使这样的事情起作用,您必须避免在任何大于单个脚本的项目中使用“from x import *” - 在这种情况下,您有 2 个具有相同名称的变量,并且在整个代码中具有不同的含义:基类和例如,继承的类。Python 命名空间允许您避免这种情况)。

因此,这将更改 base_class 模块中的 Bookcollection 类 - 但仅适用于将从此时开始并在您的执行链上引用它的代码。如果示例中的“x”类在“base_classes”模块中定义,或者在“MyModule”导入之前定义,它将获得对旧“Bookcollection”类的引用。

如您所见,它很快就会变得一团糟,如果您真的选择了这种方法,那么保持项目可用的唯一方法就是进行单元测试来验证您想要修补的所有类是否都已实际修补。如您所见,即使是模块的导入顺序也会有所不同。如果你有测试地方,如果你按照破坏猴子补丁的顺序进行导入,它们就会中断。

如果您只需要在现有类中添加和替换内容,您可以修改类本身以替换其组件,而不是修改其所在的模块以替换类。这样,模块的导入顺序就无关紧要了——它甚至会影响该类的现有实例:

 import base_classes

 base_classes.Bookcollection.new_member = "lalala"

 def new_bookcol_method(self):
      pass

 # to replace or register a method in the other class:
 base_classes.Bookcollection.__dict__["old_bookcol_method"] = new_bookcol_method

与尝试将新类(本身就是一个对象)分配给原始模块中的相同名称相比,这将为您提供更一致的行为。

总而言之,您应该按照@jamesj 在他的回答中建议的那样做,并使用不同的类,或者如果您需要动态行为,请为此使用可维护的框架,例如 Zope 组件架构。无论你采取什么方法,都要编写单元测试。

于 2012-04-05T12:41:34.083 回答
1

当您遇到命名空间冲突时,拥有相同名称的类通常是一个坏主意。我建议将您的更改MyLibPackage.base_classes.BookcollectionMyLibPackage.base_classes.BaseBookcollection或类似的。这应该可以按预期工作。

于 2012-04-05T11:54:33.297 回答