1

How do you reference class methods within class variables?

For example:

I have

class UU(object):

  map = {
    'username': get_username
  }

  @classmethod
  def get_username(cls):
    pass

However, get_username can't be found.

I tried UU.get_username but that does not appear to work.

James Lam
  • 1,179
  • 1
  • 15
  • 21
  • Why do you want to do this? – rlms Mar 25 '15 at 21:26
  • `UU.get_username` "works", it just doesn't do anything. What is `map` supposed to do? It just lets you write something verbose like `UU.map['username']` as a reference to the class method. (Or it would, if `get_username` were defined yet, which it isn't.) – chepner Mar 25 '15 at 21:27
  • In another function, I want to pass in certain keys to the map and run the functions that correspond to the values. However, these functions can be stateless. The map could be taken out of the class, but it's largely there for readability. – James Lam Mar 25 '15 at 21:28
  • I've added something to my answer about using `getattr`, which might be useful for you. – rlms Mar 25 '15 at 21:34

2 Answers2

3

The class body is executed just like a function is, and the local namespace is then turned into the class attributes.

As such normal naming order requirements apply; you cannot reference get_username() because it is not yet defined.

Moreover, you'd get the unbound classmethod object even if you moved the map definition down below the get_username() definition.

As such, you'd add the method to the mapping after creating the class:

class UU(object):
    @classmethod
    def get_username(cls):
        pass

UU.map = {
    'username': UU.get_username
}

Note that this means than from there on out UU.map['username'] is using a classmethod bound to the UU class. You'll not get the version on a subclass if you have ever subclassed UU and provided an override of that method.

You'd have to jump through a lot more hoops to make this work for subclasses; you'd have to make map a descriptor object so you can bind class methods when looking up values in the mapping, not when defining the mapping:

class BindingMap(dict):
    def __get__(self, instance, cls=None):
        return {k: v.__get__(instance, cls) if hasattr(v, '__get__') else v for k, v in self.items()}

class UU(object):
    @classmethod
    def get_username(cls):
        pass

    map = BindingMap({
        'username': get_username,
    })

The map will then produce bound class methods on demand, extending to subclasses:

>>> class BindingMap(dict):
...     def __get__(self, instance, cls=None):
...         return {k: v.__get__(instance, cls) if hasattr(v, '__get__') else v for k, v in self.items()}
... 
>>> class UU(object):
...     @classmethod
...     def get_username(cls):
...         pass
...     map = BindingMap({
...         'username': get_username,
...     })
... 
>>> UU.map
{'username': <bound method type.get_username of <class '__main__.UU'>>}
>>> UU.map['username']
<bound method type.get_username of <class '__main__.UU'>>
>>> UU.map['username']()
>>> class UU(object):
...     @classmethod
...     def get_username(cls):
...         print('Username for class {}'.format(cls.__name__))
...     map = BindingMap({
...         'username': get_username,
...     })
... 
>>> UU.map
{'username': <bound method type.get_username of <class '__main__.UU'>>}
>>> UU.map['username']
<bound method type.get_username of <class '__main__.UU'>>
>>> UU.map['username']()
Username for class UU
>>> class Foo(UU):
...     pass
... 
>>> Foo.map
{'username': <bound method type.get_username of <class '__main__.Foo'>>}
>>> Foo.map['username']
<bound method type.get_username of <class '__main__.Foo'>>
>>> Foo.map['username']()
Username for class Foo
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • unubutu's code seems to disagree with "Moreover, you'd get the unbound classmethod object even if you moved the map definition down below the get_username() definition.", if I understand you correctly. – rlms Mar 25 '15 at 21:31
  • 1
    @sweeneyrod: nope, his code shows you are getting just the classmethod object, unbound. The bound method looks like `>`. – Martijn Pieters Mar 25 '15 at 21:37
  • 1
    @sweeneyrod: You run into [`TypeError: 'classmethod' object is not callable`](http://stackoverflow.com/q/11058686/190597) if you try calling the unbound classmethod using my code. It's not useful, so I deleted my answer. – unutbu Mar 25 '15 at 21:39
1

The reason your code doesn't work is because UU's methods are defined after the class variables - so get_username doesn't exist when you try to add it to the dictionary.

Try this:

class UU(object):

  @classmethod
  def get_username(cls):
    pass

UU.map = {
    'username': UU.get_username
  }

Judging by your comment on the question, you might find getattr useful.

Example:

method_name = "get_username"
method = getattr(UU, method_name)
method()

or just

getattr(UU, "get_username")()

No dictionary needed!

rlms
  • 10,650
  • 8
  • 44
  • 61