Thanks for your explanations and research on this. I think it is a case
where the docs don't specify the process exactly enough, so it is
somewhat borderline.
The docs don't actually make sense, as they talk about "method call" as
a "lookup", when technically there is no such thing in Python - just
attribute lookup, followed by calling a callable - and at the level of
detail we are arguing that distinction matters.
So we then have to think about what is sensible behaviour, and I think
the new behaviour is much simpler and saner, and much more consistent
with itself. Also, given the nature of the Django's template system
(i.e. usable for designers), it is difficult to come up with a use case
for the old behaviour. The use case linked by Tai is very unconvincing -
to me, using 'mycallable.func_name' is a gross thing to do in a template
- a fairly blatant case of logic that belongs in the view function.
If we decide that the old behaviour was a bug, it doesn't (technically)
matter that the fix breaks some people's templates, because our policy
is to fix bugs and not to support bugs. The only exception would be when
a large fraction of people are relying on the bug, and I don't think
that is likely to be the case here.
So, yes, you were relying on a bug IMO - Django was treating objects
retrieved by dictionary and list index lookup inconsistently compared to
objects retrieved by attribute lookup, which is not a sensible thing to
do in Python.
We may want to put a note in the backwards incompatible changes section,
as a courtesy to those whose templates may stop working. But I don't
think the deprecation path proposed by Tai would be useful - it would
just confuse things more, and it isn't appropriate if we treat the
original behaviour as a bug. I also think that mrmachine's analysis and
patch on #15025 is correct.
Thanks,
Luke
--
"Whom have I in heaven but You?
And there is none upon earth that I desire besides You." Psalm 73:25
Luke Plant || http://lukeplant.me.uk/
Ah, you are mrmachine - thanks :-)
Ticket accepted.
> The use case Tai linked was the first (and really, only) bug I ran
> into when porting to SVN trunk. I understand the "usable for
> designers" mantra, and I don't pretend that 'mycallable.func_name' is
> elegant, but frankly, I don't care. I don't have the luxury of
> working with a dedicated designer. I write the templates. If I find
> a snippet that makes my life easier in a .py file, I don't spend too
> long making sure it follows the template language's philosophy.
Obviously, I have no problem with you doing that. People are free to use
and abuse how they like, and to follow their own judgement as to which
is which, just as they are with Python features - and just as they are
free to do risky things like depend on internal methods and
implementation details. The philosophy of the template language is
relevant here simply for deciding which behaviour is more expected and
consistent, and working out which should be considered the bug in
borderline cases like this.
> I can't speak to the number of templates actually in the wild that
> rely on the old behavior. I will say that the debug template has been
> "relying on the bug" since 2005. By my estimation, that makes nearly
> every template ever created a candidate for relying on the old
> behavior. And for a python dev to treat callables as first-class
> objects is hardly unusual.
Yep, I'm not saying its a stupid thing to do for Python in general, I'm
saying its an unusual and unlikely choice for someone who knows that
Django's template language automatically calls callables (at least
sometimes), and knows that the language is supposed to keep view logic
out. Even though I also write my own templates in the vast majority of
cases, it would never have occurred to me to do what that snippet did,
and if you had shown me it before this discussion, I think I would have:
1) been very surprised it worked
2) quite probably classed it as a bug that needed fixing!
> What do you think about throwing in a nod towards backwards
> compatibility by special-casing attribute access on callables?
> Basically, keep the functionality of [14992] that says "Callables are
> always called" and just add " unless the next lookup is for an
> attribute that exists on the callable". There's a potential for name
> clashes if you were expecting the template lookup to happen on the
> return value of the callable instead of the callable itself, but I
> would think that's a much smaller incompatibility than the one created
> by [14992].
I agree with Tai, and think this would make the behaviour more confusing
and complicated.
Regards,
the guys that don't want the dict-item-funcion to be called, could set
the alters_data attribute, no?
while we're at it why not using inspect module to check function
signature before calling it?
if requires an arg, raise TypeError
else call it without catching TypeError
I'm sure it has been discussed before but, hey...
Marco
> +1 for [14992] and the proposed documentation change
>
> the guys that don't want the dict-item-funcion to be called, could set
> the alters_data attribute, no?
In this case, they will just get settings.TEMPLATE_STRING_IF_INVALID,
not anything useful.
> while we're at it why not using inspect module to check function
> signature before calling it?
>
> if requires an arg, raise TypeError
> else call it without catching TypeError
One reason is that is an extra cost, another is that the result of
inspect.getargspec() is still ambiguous - if you get:
ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None)
then you don't actually know whether it takes an argument or not, and
this kind of signature is often generated by decorators.
In conclusion, I'm going to leave [14992] in. Due to the backwards
incompatibility discovered, it's not a good idea to backport to 1.2.X.
But since it is cleaning up an inconsistency/ambiguity, I'm happy to
leave it in, and say that code that was relying on the old behaviour was
relying on ambiguity/inconsistency/corner case that we don't want to
support. I will add a note to the 1.3 release notes.
Thanks Don for bringing this up. None of the other core devs have
stepped up in favour of reverting or the deprecation path, so I hope
you're as happy you can be with the outcome.
Thanks,
Luke
--
"Yes, wearily I sit here, pain and misery my only companions. And
vast intelligence of course. And infinite sorrow. And..." (Marvin
the paranoid android)
Luke Plant || http://lukeplant.me.uk/
what if you add an alters_data class attribute to your ModelForm subclass?
class MyForm(ModelForm):
alters_data = True
....
this way __call__ is not going to get called...
then maybe a new special attribute like "dont_call_me" or
"i_will_call_you_if_needed" (way more polite)
could be handy in this situations? What do you think?
Marco
I think it's too much of a corner case to justify another check in lookup.
When I need to pass a class to a tag (which is quite rare), I prefer
to use it's name (e.g. "app.forms.MyForm") and fetch the actuall
object later. If you really need a to pass the callable itself, you
can wrap it in a 1-tuple or a custom proxy object.
--
Łukasz Rekucki