简体   繁体   中英

calling class/static method from class variable in python

I'm trying to make a ImageLoader class handle the loading and processing of image resources like this:

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

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

however, on compile i get: NameError: name '_loadTileTable' is not defined

If i replace the second line with TileTable = ImageLoader.__loadTileTable('image path', some other variable) then i get NameError: name 'ImageLoader' is not defined

As i'm going from C# to Python, static classes with static methods is what i'd use to implement this. However, i'm open to how I'd do this in general in python (that is, call static library functions that are only grouped together by their functionality).

UPDATE: After reading both answers, I'm getting a picture that what i'm trying to do probably isn't right. How would I go about imlementing ImageLoader so that I can do this:

Assuming that tile table returned an array

module1.py

aTile = ImageLoader.TileTable[1]

module2.py

anotherTile = ImageLoader.TileTable[2]

ideally, i'd populate TileTable just once.

Update:

Thanks for all the answers, I found my last answer to populating TileTable just once in the python modules doco

"A module can contain executable statements as well as function definitions. These statements are intended to initialize the module. They are executed only the first time the module is imported somewhere"

As for static class, i'm going to forgo classes and just make a module level variable.

Answering just the updated question, what you would do in Python is make TileTable a variable called tile_table in a module called imageloader . There is no reason at all to put any of this inside a class.

So then you get:

module1.py

import imageloader
aTile = imageloader.tile_table[1]

module2.py

import imageloader
anotherTile = imageloader.tile_table[2]

and imageload.py looks something like:

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

Think of a Python module as a singleton instance in other languages (which in fact it is) and you'll be able to reconcile this with any OO preconceptions you inherited from other languages.

In Python, the code in the class block is first executed, then the resultant namespace is passed to the class initializer. The code you wrote could have also been written as:

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

As you can see, the call of _loadTileTable appears before the definition of it. In your example, within the class definition, the call to _loadTileTable must come after the definition of _loadTileTable.

One possible fix is to simply re-arrange the class definition.

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

    TileTable = _loadTileTable('image path', other_var)

Note that I removed the 'staticmethod', because at the point where _loadTileTable is called, it's being called as a function and not a method. If you really want it to be available after class initialization, you can define it as a static method after the fact.

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

    TileTable = _loadTileTable('image path', other_var)

    _loadTileTable = staticmethod(_loadTileTable)

Class-level variables which get updated are a bad, bad thing. Our default expectation is that object instances are stateful and classes are stateless.

In this case, we're trying to "magically" initialize a collection as a class variable, which is a toweringly bad idea. A collection is simply an object with simple instance-level attributes.

The magical Tile Table should not be a concealed, static part of the ImageLoader. There is no possible reason for that. It should be an argument to the ImageLoader if you want to avoid loading it more than once.

Separating these promotes testability. It's not arbitrary. It's how unit testing gets done.

What you want is this.

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

No static anything. Independent units. More easily testable. No weird dependencies. No magic.

Is there a design reason you're using a static method? If so, because you're not overloading the class initialization, you'll need to declare the variable after the method definition.

But, if you do this, you'lll get a new error:

NameError: name 'arg1' is not defined

The reason for this is because you're executing the method within the class before the class is even instantiated, therefore you never have a chance to pass the arguments to the method.

So, the proper way to do this is to overload the __init__() method so that assignment to TileTable only happens when the class is constructed:

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

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

This gives you the ability to call ImageLoader._loadTileTable() without having an instance, but then it also allows you to create the TileTable instance variable upon creating an instance.

Using a Class method
In response to my comment about the possible need for a classmethod, here is an example that covers this:

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')

There might be a better way to do this, I don't know. But I do think that this covers what you are looking for:

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

There you have an instance i that has access to the class variable, but ImageLoader.TileTable is still available from the class object without the need for an instance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM