从类变量中调用类/静态方法的Python方法

13 投票
4 回答
12539 浏览
提问于 2025-04-16 11:52

我正在尝试创建一个名为 ImageLoader 的类,用来处理图像资源的加载和处理,代码大概是这样的:

class ImageLoader:
    TileTable = __loadTileTable('image path', some other variable)

    @staticmethod
    def _loadTileTable(arg1, arg2):
        blah blah

但是在编译时我遇到了一个错误:NameError: name '_loadTileTable' is not defined

如果我把第二行改成 TileTable = ImageLoader.__loadTileTable('image path', some other variable),那么我又会遇到另一个错误:NameError: name 'ImageLoader' is not defined

因为我之前是用 C# 的,所以我习惯用静态类和静态方法来实现这个功能。不过,我也想了解在 Python 中一般该怎么做(也就是说,调用一些功能相似的静态库函数)。

更新:

在阅读了两个回答后,我开始意识到我想做的事情可能不太对。那我该如何实现 ImageLoader,才能做到以下这一点:

假设 tile table 返回的是一个数组

module1.py

aTile = ImageLoader.TileTable[1]

module2.py

anotherTile = ImageLoader.TileTable[2]

理想情况下,我只想填充一次 TileTable。

更新:

感谢大家的回答,我在 Python 模块文档中找到了关于只填充一次 TileTable 的最后一个答案:

"一个模块可以包含可执行的语句和函数定义。这些语句用于初始化模块。它们只在模块第一次被导入时执行一次"

至于静态类,我决定不使用类,而是直接创建一个模块级的变量。

4 个回答

3

类级别的变量如果被更新,那真是个大问题。我们通常的期望是,对象的实例是有状态的,而类本身是没有状态的。

在这种情况下,我们试图“神奇地”把一个集合初始化为类变量,这真是个糟糕的主意。集合其实就是一个简单的对象,里面有一些实例级别的属性。

这个神奇的瓷砖表不应该是ImageLoader的一个隐藏的、静态的部分。没有任何理由这样做。如果你想避免多次加载它,应该把它作为参数传给ImageLoader。

这样分开做有助于测试。这不是随便的做法,而是单元测试的基本方法。

你想要的应该是这样的。

class ImageLoader( object ):
    def __init__( self, theTileTable ):
        self.tile_table= theTileTable

class TileTable( object ):
    def __init__( self, path, some_other_arg ):
         self.tileTable= self._loadTileTable( path, some_other_arg )
    def _loadTileTable(arg1, arg2):
         blah blah

不要有静态的东西。要独立的单元。这样更容易测试。没有奇怪的依赖关系。没有魔法。

5

在Python中,类里面的代码会先执行,然后生成一个命名空间,这个命名空间会传递给类的初始化器。你写的代码其实也可以这样写:

TileTable = _loadTileTable(arg1, arg2)
@staticmethod
def _loadTileTable(arg1, arg2):
    pass # blah blah
ImageLoader = type('ImageLoader', (), {'TileTable': TileTable, '_loadTileTable': _loadTileTable})
del TileTable
del _loadTileTable

你可以看到,_loadTileTable的调用出现在它定义之前。在你的例子中,在类定义里面,调用_loadTileTable必须在它的定义之后。

一个可能的解决办法就是简单地调整一下类的定义顺序。

class ImageLoader:
    def _loadTileTable(arg1, arg2):
        pass # blah, blah

    TileTable = _loadTileTable('image path', other_var)

注意,我去掉了'staticmethod',因为在调用_loadTileTable的时候,它是作为一个函数被调用的,而不是作为一个方法。如果你真的想在类初始化后使用它,可以在之后把它定义成一个静态方法。

class ImageLoader:
    def _loadTileTable(arg1, arg2):
        pass # blah, blah

    TileTable = _loadTileTable('image path', other_var)

    _loadTileTable = staticmethod(_loadTileTable)
5

针对更新后的问题,如果你在Python中操作,可以把TileTable变成一个叫tile_table的变量,放在一个叫imageloader的模块里。其实没有必要把这些放在一个类里面。

这样你就可以得到:

module1.py

import imageloader
aTile = imageloader.tile_table[1]

module2.py

import imageloader
anotherTile = imageloader.tile_table[2]

imageload.py看起来大概是这样的:

def _loadTileTable(arg1, arg2):
    pass # blah blah
tile_table = _loadTileTable('image path', other_var)

可以把Python模块想象成其他语言中的单例实例(实际上它就是这样),这样你就能把这个概念和你从其他语言学到的面向对象的知识结合起来。

撰写回答