【问题标题】:Python, correct approach for instancing classes once and using them within other classesPython,一次实例化类并在其他类中使用它们的正确方法
【发布时间】:2013-02-02 06:28:34
【问题描述】:

我有一些代码用于解决一个名为 nurikabe 的益智游戏,最近我一直在将其重写为 OOP(仍在学习)并具有以下结构:

# CNurikabe.py
from includes import Board, Validation, Heuristics
class CNurikabe(object):
    ...

# CValidation.py
from includes import Board, Heuristics
class CValidation(object):
    ...

# CHeuristics.py
from includes import Board
class CHeuristics(object):
    ...

# CBoard.py
class CBoard(object):
    def __init__(self, filename):
        # Vars shared by every class
        self.x, self.y, self.z, self.t = self.parseData(filename)

# run.py
from CNurikabe import CNurikabe
nurikabe = CNurikabe()
nurikabe.solve('output')

# includes.py
from CBoard import CBoard   
Board = CBoard('data.dat')

from CHeuristics import CHeuristics
Heuristics = CHeuristics()

from CValidation import CValidation
Validation = CValidation()

CBoard 类具有必须在所有其他类之间共享的信息(例如板尺寸、数字坐标等),我也希望它被实例化一次,如果可能的话防止依赖注入(不必要地将文件名传递给每个类初始化方法,例如)

需要这些类来访问以下内容:

CValidation 类使用:CBoard 和 CHeuristics

CHeuristics 类使用:CBoard

CNurikabe 类使用:CBoard、CValidation 和 CHeuristics

我的代码按预期工作。我可以按照我想要的方式在其他类中调用其他类的方法,例如:

# CNurikabe.py:
class CNurikabe(object):
    def someFunc(self):
        for i in range(Board.dimensionx):
            Heuristics.doSomeStuff()
            Validation.doSomeMore()

但我可能读过太多关于全局变量如何邪恶的文章。此外,includes.py 中的代码有点骇人听闻,因为如果我更改导入的顺序,程序将无法运行,抱怨无法导入某些名称。

我还尝试了另一种方法,仅全局实例化 CBoard 类,然后为其他类创建我需要的类的实例。但我觉得这有点重复,例如在每个类中创建一个独特的 CHeuristics 全局实例,但这仍然无法解决 CBoard 全局问题。

我也想过在每个类的 init 中创建一个实例,但是代码会非常冗长,例如必须调用:self.Heuristics.doSomeStuff()

所以我的问题是有什么更好的方法来构建它?我读过关于单例模式(这可能有点过分,因为它是一个小项目),以及为多种语言(如 C++ 和 PHP)做这件事的无穷无尽的方法。实际上我这样做的方式类似于“外部类实例”;用 C++ 做的方式,很久以前我在做一个具有这种风格的 C++ 项目,我喜欢它,没有发现任何问题,尽管类实例是全局的。

【问题讨论】:

    标签: class python-3.x instance global


    【解决方案1】:

    全局变量是邪恶的。但是,您可能需要一个将一些东西封装在一起的单例模式。我从 C++ 和 Python 获得的经验是,您可以很好地使用语言的混合特性,并使用模块作为单例。 (如果你仔细想想,模块变量扮演单例成员变量的角色,模块中的普通函数类似于单例的方法。)

    这种方式我建议将板子功能放入board.py启发式功能放入heuristic.py,...,将方法转换为函数,将self.variable转换为variable并使用:

    import board
    import heuristic
    import validation
    
    ...
    
    class CNurikabe:     # the explicit (object) base class is not needed in Python 3
        def someFunc(self):
            for i in range(board.dimensionx):
                heuristics.doSomeStuff()
                validation.doSomeMore()
    

    import board 视为获取对单例实例的引用——实际上是这样,因为模块对象只有一个实例。它在语法上与您的旧代码相同——除了获取实例(模块)会更容易。

    更新:一旦你需要更多实例,你应该重新考虑类。然而,传递对象在 Python 中是非常便宜的操作——你只复制引用值(技术上是地址,即 4 或 8 个字节)。获得参考值后,您可以轻松地将其分配给局部变量。 (Python中的每一次赋值都意味着复制引用值,从而共享对被赋值对象的访问。这样,实际上就不需要全局变量,也没有借口使用全局变量。

    使用局部变量,语法仍然保持不变。

    【讨论】:

    • 感谢您的提示,我想这样可以使语法与我喜欢的完全相同,但对于更大的项目,这可能不是正确的方法。我还实现了一个小型依赖注入,但 komodo edit 7 失去了以这种方式自动完成的能力。
    • 已更新。我不知道你是如何实现依赖注入的(你的意思是什么)。事实上,你应该选择你能找到的最自然的解决方案。如果编辑器失去了追踪您的想法的能力,则可能是解决方案不太自然的症状。或者编辑对这个案子不够聪明。不管怎样,你写代码不是为了让编辑高兴:)
    • 我不太确定你的更新,你的意思是只是创建我需要的对象作为类中的局部变量?例如,如果需要 CNurikabe 中的 CHeuristics 对象,我会在类代码之前:Heuristics = CHeuristics() 吗?当然还有相应的导入。关于依赖注入,我刚刚将已创建的对象传递给需要它的对象的 init。例如 Nurikabe = CNurikabe(Board, Heuristics, Validation),其中 Board、Heuristics 和 Validation 已创建对象。然后我在课堂上调用它:self.Heuristics.doSomeStuff()
    • 那么我们谈论的是完全相同的事情。你叫它injection,我叫它assignment。如果说Heuristic 被传递给另一个CNurikabe,那么两个CNurikabe 实例将共享同一个启发式对象。实际上,对象是在分配给局部变量时首先创建还是从外部传入都没有关系。只有当你想分享它时它才会有所不同。我认为这种方法很好。 :)
    • 现在我明白了你在更新中的意思并实现了它,语法确实保持不变(我通过 init 方法传递它,它是相同的,但必须在每个前面加上 self.通过参数),无论如何我想它有助于更​​好的单元测试,我终于理解了这个概念并避免使用全局变量,谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-09
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 2021-07-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多