【问题标题】:calling class/static method from class variable in python从python中的类变量调用类/静态方法
【发布时间】:2011-02-15 14:25:07
【问题描述】:

我正在尝试让 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 时,我将使用带有静态方法的静态类来实现这一点。但是,我对在 python 中一般如何执行此操作持开放态度(即调用仅按功能分组在一起的静态库函数)。

更新: 在阅读了两个答案之后,我得到了一张图片,我正在尝试做的事情可能是不正确的。 我将如何实施 ImageLoader 以便我可以做到这一点:

假设瓦片表返回一个数组

module1.py

aTile = ImageLoader.TileTable[1]

module2.py

anotherTile = ImageLoader.TileTable[2]

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

更新:

感谢所有答案,我在 python 模块 doco 中找到了仅填充 TileTable 一次的最后一个答案

"一个模块可以包含可执行文件 语句和函数 定义。这些陈述是 用于初始化模块。 它们仅在第一次执行 模块被导入某处”

至于静态类,我将放弃类,只创建一个模块级变量。

【问题讨论】:

  • 当你在 Python 中使用 staticmethod 时,你通常会做错事。如果您既不需要实例变量也不需要类变量(静态字段),只需使用模块级函数。
  • @delnan:我不完全同意。例如 Python 的默认 dict.fromkeys()。这些东西有合法的用例。
  • @delnan:我不同意。封装是 Joe 尝试使用的面向对象编程的主要租户。在 ImageLoader 类中保留仅与 ImageLoader 相关的函数是一种以编程方式对这种关系进行编码的有效方法(而不是因为它们在同一个模块中而隐式关联它们)。
  • @jathanism @Jason R. Coombs:是的,有合法的用例。但它们很少见。当与包含类有明确的联系时,例如例如,它用作具有有用名称的替代构造函数 - 但这里不是这种情况,该函数是私有的,很可能永远不会在其他任何地方调用它。而且,如果您来自静态方法是编写与实例无关的代码的唯一方法的语言,那么恕我直言,您应该将静态方法视为需要学习的东西(因为通常您不需要它们)蟒蛇)。
  • @Joe:“我将如何实施 ImageLoader 以便我可以做到这一点:”这是一个单独的问题。关闭这个——它在静态方法上。用你的真实问题打开一个新问题。

标签: python class


【解决方案1】:

只回答更新后的问题,您在 Python 中要做的就是在名为 imageloader 的模块中创建一个名为 tile_table 的变量 TileTable。完全没有理由将这些放在一个类中。

那么你得到:

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 模块想象成其他语言中的单例实例(事实上确实如此),您将能够将其与从其他语言继承的任何 OO 偏见相协调。

【讨论】:

  • 我喜欢你的简单回答,但这是否意味着每次我调用 tile_table 时,它​​都会再次执行 _loadTileTable 函数,这会不必要地加载相同的东西?所以在这种情况下,我可能想要一个函数将其加载到模块级变量中,然后使用该变量。
  • 不,imageload.py 中的代码只执行一次,第一次导入时(哎呀,才注意到应该是imageloader.py!)。后续导入不会再次执行代码,它们只是重用现有模块。
【解决方案2】:

在 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 的调用必须在 _loadTileTable 的定义之后。

一种可能的解决方法是简单地重新安排类定义。

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

    TileTable = _loadTileTable('image path', other_var)

请注意,我删除了“静态方法”,因为在调用 _loadTileTable 时,它​​被作为函数而不是方法调用。如果你真的希望它在类初始化之后可用,你可以在事后将它定义为静态方法。

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

    TileTable = _loadTileTable('image path', other_var)

    _loadTileTable = staticmethod(_loadTileTable)

【讨论】:

  • 您的示例代码实际上无法工作,因为类内部的方法调用需要无法传递的参数。
  • jathanism:我更新了答案以匹配对问题的更改(假设 _loadTileTable 的参数在 globals() 中)
  • 我不确定我是否完全理解你。您的第二个代码块确实有效,我第一次没有尝试删除 staticmethod 装饰器。然而,这意味着我不得不担心我的变量/方法的声明顺序。有没有像c#这样的方法,我可以在类中声明一个变量并使用一个方法,而不必担心调用的顺序?
  • @Joe:不。Python 是动态的,并且将函数视为一等对象——因此,只有在执行流到达def ... 行时,才会创建函数并与名称相关联。但是由于函数的 body 不是直接执行的,因此只有将声明与要直接执行的语句混合使用才有意义(出于这个原因,很少这样做)。声明两个函数互相调用是可以的,只要你只在定义两个函数之后调用。
  • 我已经更新了这个问题,问我真正想要的是什么,现在我明白我不知道如何用 python 写这个
【解决方案3】:

更新的类级变量是一件坏事。我们的默认期望是对象 instances 是有状态的,而类是无状态的。

在这种情况下,我们试图“神奇地”将集合初始化为类变量,这是一个非常糟糕的主意。集合只是具有简单实例级属性的对象。

神奇的 Tile Table 不应该是 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

没有静态的东西。独立单位。更容易测试。没有奇怪的依赖。没有魔法。

【讨论】:

  • 类级变量永远不会被更新,它只会被检索。顾名思义,它是一个图像图块表(从文件中检索一次)。 imageloader 类仅用于管理图块资源的创建和存储,我看不到创建另一个类来保存一个逻辑上属于 imageloader 类的方法的意义。最后,要在两个单独的模块中使用它,我要么必须创建两个实例,要么传递一个 imageloader 引用,我在我的问题中提到了我不想这样做。
  • 不管怎样,谢谢你的意见,我会试试邓肯的建议,看看我会怎么做。
  • @Joe:“我没有看到创建另一个类来保存一个逻辑上属于图像加载器类的方法的意义。”可测试性。我相信答案已经说过了。 “要在两个单独的模块中使用它,我要么必须创建两个实例,要么传递一个 imageloader 引用,”。可悲的是,传递实例是正确的答案,并且是最可测试的。简单地拒绝编写可测试的代码并不是最好的主意。
  • 嗯...这有点离题了,但即使我确实想测试从静态位置获取这些静态图像,我不能自己运行模块并看到它是否生成图像?或者您是在谈论模拟/存根类的能力,而这是我无法使用模块级变量做的?
  • 啊,好吧,点了。你是说为了测试,我不应该有任何类或模块级变量吗?我注意到你没有提到模块级变量,我假设它们在测试时和类变量一样糟糕?嗯...我认为暂时,这会给我的初学者程序增加太多的复杂性,但这些是我学习的有用指针
【解决方案4】:

您使用静态方法是否有设计原因?如果是这样,因为你没有重载类初始化,你需要在方法定义之后声明变量

但是,如果你这样做,你会得到一个新的错误:

NameError: name 'arg1' is not defined

这是因为你在类被实例化之前就在类中执行了方法,因此你永远没有机会将参数传递给方法。

因此,正确的做法是重载__init__() 方法,以便仅在构造类时才对TileTable 进行赋值:

class ImageLoader(object):
    def __init__(self, arg1, arg2):
        self.TileTable = self._loadTileTable(arg1, arg2)

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

这使您能够在没有实例的情况下调用ImageLoader._loadTileTable(),但它还允许您在创建实例时创建TileTable 实例变量。

使用类方法
为了回应我对可能需要类方法的评论,下面是一个涵盖此内容的示例:

class ImageLoader:
    @classmethod
    def _loadTileTable(cls, arg1, arg2):
        return arg1, arg2

# We're creating the class variable outside of the class definition. If you're doing 
# this in a module, no one will ever notice.
ImageLoader.TileTable = ImageLoader._loadTileTable('foo', 'bar')

可能有更好的方法来做到这一点,我不知道。但我确实认为这涵盖了您正在寻找的内容:

>>> i = ImageLoader()
>>> i
<__main__.ImageLoader instance at 0x100488f80>
>>> ImageLoader.TileTable
('foo', 'bar')
>>> i.TileTable
('foo', 'bar')

您有一个实例 i 可以访问类变量,但 ImageLoader.TileTable 仍然可以从类对象中获得,而无需实例。

【讨论】:

  • 我认为应该指出我的目标是创建一个静态 TileTable 以便其他类可以访问它。我不希望 ImageLoader 的单独实例四处飘荡。 loadTileTable 是一个私有函数,当第一次使用 TileTable 或第一次初始化静态类时,它会做一些工作来加载图块。所以当应用启动时。
  • 听起来你可能需要一个classmethod,它使用类对象作为第一个参数(其中实例方法使用实例作为第一个参数)。静态方法未绑定到类,因此无法访问类变量。
  • 我确实尝试将装饰更改为 classmethod,但它仍然告诉我该方法未定义。
  • 我更新了答案以使用类方法,看看。我想看看这是否有帮助。 :)
  • 那么调用 ImageLoader.TileTable 只会被重定向到 ImageLoader._loadTileTable 对吗?但这意味着每次我调用 ImageLoader.TileTable 时,我都会像其他答案一样重新加载平铺表。我想要的是在应用程序的整个生命周期中调用一次 _loadTileTable。
猜你喜欢
  • 1970-01-01
  • 2013-09-08
  • 2017-04-16
  • 1970-01-01
  • 2018-07-04
  • 2011-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多