How can I refer to a class method outside of a function body in Python?
I want to do a one time callback registration in Observer. I don't want to do registration inside init or other function. I don't know if there is a level equivalent for init
class Observer:
@classmethod
def on_new_user_registration(new_user):
#body of handler...
# first I try
NewUserRegistered().subscribe \
(Observer.on_new_user_registration) #gives NameError for Observer
#so I try
NewUserRegistered().subscribe(on_new_user_registration) #says not callable
#neither does this work
NewUserRegistered().subscribe(__metaclass__.on_new_user_registration)
class BaseEvent(object):
_subscriptions = {}
def __init__(self, event_info = None):
self.info = event_info
def fire(self):
for callback in self._subscriptions[event_type]:
callback(event_info)
def subscribe(self, callback):
if not callable(callback):
raise Exception(str(callback) + 'is not callable')
existing = self._subscriptions.get(self.__class__, None)
if not existing:
existing = set()
self._subscriptions[self.__class__] = existing
existing.add(callback)
class NewUserRegistered(BaseEvent):
pass
a source to share
I've come to the conclusion that python is not very intuitive when it comes to functional programming in class definitions. See this question . The problem with the first method is that Observer doesn't exist as a namespace until the class is built. The problem with the second is that you've created a class method that doesn't actually do what it should, until the namespace is created. (I have no idea why you are trying to do the third one). In both cases, none of these things happen until the Observer class definition is complete.
This may sound like a sad limitation, but it really isn't that bad. Just register after defining the class. Once you realize that it is not bad style to do certain initialization routines for classes in the body of a module, but outside of the body of the class, python becomes much friendlier. Try: Observer class:
# Define the other classes first
class Observer:
@classmethod
def on_new_user_registration(new_user):
#body of handler...
NewUserRegistered().subscribe(Observer.on_new_user_registration)
Because of the way modules work in python, you are guaranteed that this registration will be done once and only once (no handling and possibly some other irrelevant edge cases) wherever the Observer is imported.
a source to share
I suggest reducing the number of classes - remember that Python is not Java. Every time you use @classmethod
or @staticmethod
, you should stop and think about it, as these keywords are quite rare in Python.
Doing it like this:
class BaseEvent(object):
def __init__(self, event_info=None):
self._subscriptions = set()
self.info = event_info
def fire(self, data):
for callback in self._subscriptions:
callback(self.info, data)
def subscribe(self, callback):
if not callable(callback):
raise ValueError("%r is not callable" % callback)
self._subscriptions.add(callback)
return callback
new_user = BaseEvent()
@new_user.subscribe
def on_new_user_registration(info, username):
print "new user: %s" % username
new_user.fire("Martin")
If you want the Observer class, you can do it like this:
Observer class:
@staticmethod
@new_user.subscribe
def on_new_user_registration(info, username):
print "new user: %s" % username
But note that the static method does not have access to the protocol instance, so this is probably not very useful. You cannot subscribe to a method associated with an object instance like this, as the object will not exist when the class definition is executed.
But you can of course do this:
class Observer:
def on_new_user_registration(self, info, username):
print "new user: %s" % username
o = Observer()
new_user.subscribe(o.on_new_user_registration)
where we use bound o.on_new_user_registration
as an argument to subscribe.
a source to share
oops. Sorry about that. All I had to do was move the subscription outside of the class definition
class Observer:
@classmethod
def on_new_user_registration(new_user):
#body of handler...
#after end of class
NewUserRegistered().subscribe(Observer.on_new_user_registration)
Guess this is a side effect of too much Java that you don't immediately think about.
a source to share
What you are doing should work:
>>> class foo:
... @classmethod
... def func(cls):
... print 'func called!'
...
>>> foo.func()
func called!
>>> class foo:
... @classmethod
... def func(cls):
... print 'func called!'
... foo.func()
...
func called!
It should be noted, however, that class methods take a cls argument instead of a self argument. So your class definition should look like this:
class Observer:
@classmethod
def on_new_user_registration(cls, new_user):
#body of handler...
a source to share