James Lam
James Lam

Reputation: 1269

Referencing Class Methods inside Class Variables

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.

Upvotes: 1

Views: 84

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1122292

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

Upvotes: 3

rlms
rlms

Reputation: 11060

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!

Upvotes: 1

Related Questions