0

So I got this code:

class MyClass:

    ACTIONS = {
        "ACTION_A": MyClass.__a,
        "ACTION_B": MyClass.__b
    }

    @staticmethod
    def do(constant):
        ACTIONS[constant]()

    @staticmethod
    def __a():
        print("A")

    @staticmethod
    def __b():
        print("B")

I'm trying to map the private __a and __b functions to a static dictionary so I'm able to execute functions with the method do.

When trying to run this code I get the error: "Unresolved reference 'MyClass'" on each line of the ACTIONS dictionary.

Any idea on how make this work properly?

Strinnityk
  • 548
  • 1
  • 5
  • 17
  • In Python `static` means something different from the same keyword in Java. Don't use it to manage per-class data. Use `classmethod`s for that. – Martijn Pieters May 11 '17 at 15:23
  • And, no, `MyClass` is not yet defined when the `ACTIONS` mapping is created. That name is set *after the `class` statement has completed*. – Martijn Pieters May 11 '17 at 15:24
  • Last but not least, don't use `__name` style attributes unless you are building a base class where subclasses should not have to worry about what names might clash (like in a framework). There is no Java-esque privacy model here, those attributes are *not inaccessible from outside callers*. all they do is give the attributes a different namespace. – Martijn Pieters May 11 '17 at 15:26
  • I'm not trying to manage any sort of class data, I think. What are you referring to? – Strinnityk May 11 '17 at 15:26
  • 2
    So yes, you can set `MyClass.ACTIONS` *after* the `class` is built, but you can't then expect `ACTIONS` to work in `do(constant)`, that's not how class scoping works in Python. You'd have to use `MyClass.ACTIONS` instead. Don't use a class just to bundle a bunch of functions either, classes should not be used just to namespace functions, use modules for that. – Martijn Pieters May 11 '17 at 15:27
  • I thought it had to do with that exactly (MyClass not being defined yet), but I wasn't sure. – Strinnityk May 11 '17 at 15:28
  • If you are not using classes to manage class data, then don't use classes. `ACTIONS` would be class data however. – Martijn Pieters May 11 '17 at 15:28
  • I know python doesn't have privacy built in, but I've seen a bunch of people using the double underscore naming for "private-like" functions. Good to know they are used for that, guess I shouldn't have blindly copied that style. – Strinnityk May 11 '17 at 15:31
  • See [What is the meaning of a single- and a double-underscore before an object name?](http://stackoverflow.com/q/1301346/4014959) and http://stackoverflow.com/questions/6930144/underscore-vs-double-underscore-with-variables-and-methods – PM 2Ring May 11 '17 at 15:35
  • With "ACTIONS not being class data" I was referring to your first comment. What do you mean by not using `static` or `classmethod` to manage class data? – Strinnityk May 11 '17 at 15:37

1 Answers1

5

You shouldn't be using a class in the first place. All you are doing is creating a namespace, use a module for that. Create a new module in a package and put all your functions in that:

def _a():
    print("A")

def _b():
    print("B")


ACTIONS = {
    'ACTION_A': _a,
    'ACTION_B': _b,
}

def do(constant):
    ACTIONS[constant]()

Note that I used single underscore names. Python uses double-underscore names in classes to create an additional per-class namespace. MyClass.__a becomes MyClass._MyClass__a to avoid clashes with subclasses (so they can freely re-use names without fear of breaking the implentation of a superclass), there is no privacy model in Python.

You could use a decorator to register the _a and _b functions:

def do(constant):
    ACTIONS[constant]()

ACTIONS = {}

def action(name):
    def decorator(f):
        ACTIONS[name] = f
        return f

@action('ACTION_A')
def _a():
    print("A")

@action('ACTION_B')
def _b()
    print("B")

The specific error you see is due to the MyClass name not being set until the whole class statement has completed. You'd have to set that dictionary afterwards:

class MyClass:
    @classmethod
    def do(cls, constant):
        cls.ACTIONS[constant]()

    @staticmethod
    def _a():
        print("A")

    @staticmethod
    def _b():
        print("B")

MyClass.ACTIONS = {
    'ACTION_A': MyClass._a,
    'ACTION_B': MyClass._b,
}

Note that do is a class method, you can't just access ACTIONS as a global, you need to use either MyClass.ACTIONS or, as I used above, a class method then reference the object on cls.

You could work around having to set ACTIONS after the class statement by using names instead, and make def do a class method:

class MyClass:
    ACTIONS = {
        'ACTION_A': '_a',
        'ACTION_B': '_b',
    }

    @classmethod
    def do(cls, constant):
        getattr(cls, cls.ACTIONS[constant])()

    @staticmethod
    def _a():
        print("A")

    @staticmethod
    def _b():
        print("B")
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    That is really interesting, I've learned a couple of valuable Python concepts. Thank you for your insight. – Strinnityk May 11 '17 at 16:33