On Wed, 2009-01-21 at 19:38 -0800, Keyton Weissinger wrote:
> OK. I figured it out and it was VERY STRANGE. Maybe some one reading
> this can tell me WWWHHHYYY my solution works. For now, it's voodoo to
> me...

You're going to kick yourself after this. The reason for the failure
turns out to be really easy to understand, now that your show the
code. :-)

> 
> Turns out that some of my code (in the view where I was having
> trouble) called some code that looked sort of like this:

Hmm .. you didn't exactly simplify your failing case to the smallest
possible example, did you? All this extra stuff would have set off a lot
of alarm bells about other places to look (although my first thought
would have been whether you could do without it and retest to see if
that changed anything).

Before getting into the more-or-less two line explanation, some
background information. Firstly, model._meta is obviously internal API,
so there are some shortcuts taken for performance and maintainability
reasons. We don't send the black helicopters after people who access it,
but it's very much "user beware" country. It's one of the areas that is
currently intentionally undocumented, for example (we'll eventually drop
some documentation about internal datastructures into docs/internals/,
but have had other things to do so far).

Of relevance to this particular case, we use lists a lot internally and
return references to them from certain methods. We use lists, rather
than tuples, because they are updated a fair bit during creation and all
the extra copying isn't free (updating a tuple requires a copy). Lists
are just nicer to work with, generally.

> field_list = model._meta.fields

Have a look at the implementation here
(django.db.models.options.Options._fields(), since _meta.fields is a
property). It returns a reference to a list. A *reference*, not a copy!

Your code would have worked if you wrote

        # Work with a copy of the fields list.
        field_list = model._meta.fields[:]

> field_list.extend(model._meta.many_to_many)

Here you update that reference in place. So you've just corrupted one of
the internal data structures of self._meta.

That's pretty much going to guarantee that the wheels will fall off at
some point in the future. If not in the way you saw, in some other
really hard to debug fashion.

It's kind of a Python trap and if you're designing a public API, you
would generally return a copy of the list if the caller wasn't expected
to change the internal structure. In the Options class, we "know" (in
quotes, because there are people who call it from public code, but
that's their problem, not ours) that the only callers are functions that
are read-only on the data structure, so we save a bit of time and code
complexity. We could return a copy each time, but we access self._fields
a *lot*, particularly in queryset operations. Things might have changed
a bit in the last six months (although not dramatically), but when I was
working on the queryset-refactor branch, I spent a lot of time profiling
various common execution paths and a lot of the newer shared data
structures in Options are implemented based on that work (self.fields
used to be a simple list, for example, but when model inheritance came
in, it became, internally, more complicated).

Regards,
Malcolm



--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To post to this group, send email to django-users@googlegroups.com
To unsubscribe from this group, send email to 
django-users+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-users?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to