Classes are not just namespaces
I’m not sure if “instance greed” is a reasonable name for a mistake that Python developers make every now and then. But there actually is such phenomenon, lying in refusal to create a new class instance every time its needed.
For example, django-formwizard1 asks you to define a class and then instantiate it2 in your URLConf:
from formwizard.forms import SessionFormWizard
class FeedbackWizard(SessionFormWizard):
def done(self, request, storage, form_list):
return render_to_response(
'support/done.html',
{'form_list': [form.cleaned_data for form in form_list]},
context_instance=RequestContext(request)
)
def get_template(self, request, storage):
return ['support/form.html',]
feedback_form_instance = FeedbackWizard([FeedbackStep1, FeedbackStep2, \
FeedbackStep3])
urlpatterns = patterns('',
url(r'^$', feedback_form_instance, name='feedback_wizard'),
)
Can you point out a failure? I can. The FeedbackWizard is instatiated only once, so we can’t use self to store our current request-specific data! And this is the reason why they use expicit arguments: request, storage, form/form_list all over their FormWizard class:
def get_form_list(self, request, storage):
def process_get_request(self, request, storage, *args, **kwargs):
def process_post_request(self, request, storage, *args, **kwargs):
def render_next_step(self, request, storage, form, **kwargs):
def render_done(self, request, storage, form, **kwargs):
def get_form_prefix(self, request, storage, step=None, form=None):
Et cetera, it’s just a first half of a file. Here is a rule for your software engineering: if you are writing something again and again and again you should probably think about it and try to stop. If it’s a piece of code, you’ll probably need a function. If it is argument list, like in our case, you’ll need some kind of a state-holder: e.g., a class (or a State monad, if you’re too clever, or a global variable, if you can’t care enough).
Speculation about the history of the issue
If you ever tried Java or C#, you remember that you can’t just write a function - you need a class, maybe just a static one. So you can’t have sqrt function, you need to use something like Math.sqrt, and Math is a class there. It’s a pretty strange way to do things, but I’m not ranting about Java here. The point is that these static classes could not have state too, and if you want to be enterprisy and frameworky you mimic this behaviour in your Python code3.
The solution
Have you ever seen new Django’s Class-based generic views? You have to define them as a class and use something like this in the URLConf:
urlpatterns = patterns('',
(r'^about/', AboutView.as_view()),
)
This actually seems like a boring idea, especially with all decorator-related annoyance. But it is not. Think about it now a little bit: yeah, they could do it some “normal” way, like authors of django-formwizard did. But you understand now there it is coming. It seems like authors of Django were cleverer than me, at least - I was pretty annoyed at first by this .as_view() thingy.
What does .as_view() do? It returns a function which creates an instance which processes a request. If you can’t understand decorators it’s probably hard to understand; if you can, take a look at the code:
@classonlymethod
def as_view(cls, **initkwargs):
"""
Main entry point for a request-response process.
"""
# sanitize keyword arguments
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError(u"You tried to pass in the %s method name as a "
u"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError(u"%s() received an invalid keyword %r" % (
cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
return self.dispatch(request, *args, **kwargs)
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
It also solves a problem with configuration: you can specify some specific parameters as **kwargs, not as class vars. So yeah, this is a good way to solve the issue.
So, a rule:
Instantiate classes as much as you can. If your instances need to share a state globally, they can do it in class variables. Don’t ever ask anyone to take/pass same arguments everywhere.
Next time: a little more about django-formwizard (or about Chef, it depends on how busy I’ll be).
-
It was actually beginning as a rant about
↩django-formwizard, but I decided to write a little about “instance greed” (okay, there should be a term for that) and tell you a thing or two aboutdjango-formwizardtomorrow. -
They tell us to pass form_list as an argument to the constructor, but define
↩donefunction in the class body. Why? Is there a sane use case for that? It does not look like they want us to make fewer classes. Does anybody out there really think that several places for configuration is a great way to configure things? -
To be fair, I actually never seen something like instance greed in Java (I’ve not seen a lot of bad Java code anyway), but the whole issue really seems like a way to use classes in the most strange ways they can be used. It’s actually a Java way of thinking, because you have nothing else there.
↩
Please send your comments, ideas, rants and job offers at v.golev@gmail.com.
Made with Nginx, Jekyll, Git, EC2 and Emacs.