views:

167

answers:

1

This works:

>>> def bar(x, y):
...     print x, y
...
>>> bar(y=3, x=1)
1 3

And this works:

>>> class Foo(object):
...     def bar(self, x, y):
...             print x, y
...
>>> z = Foo()
>>> z.bar(y=3, x=1)
1 3

And even this works:

>>> Foo.bar(z, y=3, x=1)
1 3

But why doesn't this work?

>>> Foo.bar(self=z, y=3, x=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method bar() must be called with Foo instance as first argument (got nothing instead)

This makes metaprogramming more difficult, because it requires special case handling. I'm curious if it's somehow necessary by Python's semantics or just an artifact of implementation.

+6  A: 

z.bar is a bound method -- it already has an im_self attribute that becomes the first argument (conventionally named self) to the underlying function object, the bound method's im_func attribute. To override that you obviously need to re-bind im_self (edit: or call the im_func instead) -- whatever you do in terms of argument passing is not going to have any effect on it, of course. Yep, that's the documented way bound methods object work in Python (not just an implementation detail: every correct Python implementation has to do it exactly this way). So it's "necessary" in the sense that it's part of what makes Python exactly the language it is, as opposed to being a slighty or strongly different language. Of course you could design a different language that chooses to play by completely different rules, but -- it wouldn't be Python, of course.

Edit: the OP's edits clarified he's calling an unbound method, not a bound one. This still doesn't work, and the reason is clear from the error message the attempt gets:

TypeError: unbound method bar() must be called with Foo instance as first argument (got nothing instead)

The rule underlying this very clear error message is that the instance must be the first argument (so of course a positional one: named arguments have no ordering). The unbound method doesn't "know" (nor care) what that parameter's name may be (and the use of name self for it is only a convention, not a rule of the Python language): it only care about the unambiguous condition of "first argument" (among the positional ones, of course).

This obscure corner case could certainly be altered (with a Python 3.2 patch, if and when the language-changes "freeze" ends;-) by making unbound methods seriously more complicated: they'd have to introspect and save the first-argument's name at creation time, and check keyword arguments on each call just in case somebody's passing self by name instead of by position. I don't think this would break any existing, working code, it would only slow down just about every existing Python program. If you write and propose a patch implementing this complication, and get active on python-dev to advocate for it against the sure-to-come firestorm of opposition, you do no doubt stand a > 0 chance to ram it through -- good luck.

The rest of us, meanwhile, will keep getting the im_func attribute instead, as one absurdly-tiny extra step in what has to be a pretty complicated inded edifice of metaprogramming to warrant such a change -- it isn't a "special case" at all, compared with the horrid difficulties of adapting named-argument passing to builtins that don't take named arguments (and don't expose their "argument names" to easily allow the transformation of named arguments into positional ones (now that would be a windmill worth attacking, IMHO: of all callables, builtins are the worst to metaprogram about, because of that!-).

Alex Martelli
`foo` is the class, so `foo.bar` is an unbound method, no?
Ignacio Vazquez-Abrams
As I understand the term, foo.bar is not a bound method, because foo is a class, not an instance. foo.bar has an im_self attribute, but its value is None.
Joseph Garvin
I've edited my post to capitalize Foo so this is more clear.
Joseph Garvin
@Mike: `z.bar()` *isn't* being called; `Foo.bar()` is. The question is why not specifying a kwarg for self works, while specifying it fails.
Ignacio Vazquez-Abrams
Alex, I'm not sure I understand your edited answer, specifically the part about a performance penalty. Currently if you call a function with named arguments, python has no choice but to inspect the original function's definition to determine the parameter names. I don't understand why 'self' would have any extra performance burden compared to any other parameter. kwargs would only need to be searched for 'self' if self wasn't already passed positionally (like any other parameter) or via normal object.method invocation. It's likely I don't appreciate how this interacts with all python features.
Joseph Garvin
@Joseph, right now the bound-method object's `__call__` peels the first positional argument off, typechecks it, and hands all other work to the underlying `im_func` -- it doesn't even **look** at named args (the underlying func of course does, just like every other function always must -- except pesky builtin ones which don't *take* named args). Having unbound methods' `__call__` "know" the name of the first argument and check for it is the extra work. Do the patch to Python's runtime that I suggested, and time it to see if it slows things down, by how much, and exactly how.
Alex Martelli
@Alex: I couldn't help attacking your windmill, how does this look?: http://pastebin.com/pUcZPFsV
Joseph Garvin
@Joseph, _slow_ -- that's how it looks, and there's the rub... builtins are fast to call and frequently called. BTW, if you're OK with requiring a transformation before named parms can be passed then your problem as posed in this Question totally and immediately disappears of course -- the needed transformation for THAT is so incredibly simpler, just use the `im_func` attribute of the unbound method object rather than the object itself -- it's actually even faster as it *saves* the normal unbound method overhead!-)
Alex Martelli
@Alex: If you want to see my misguided metaprogramming goal, it's here: http://stackoverflow.com/questions/2448187/
Joseph Garvin
I assume that's reflected in your other question which I just answered. Though I admit your low 33% acceptance rate is starting to be a factor in discouraging doing work to answer your Qs!-)
Alex Martelli
@Alex: I'm not sure I buy your claim that "it would only slow down just about every existing Python program" -- looking at instancemethod_call() in Objects/classobject.c, it looks to me like you could make the necessary change without any effect on the main code path. That said, I still agree with you that it's probably not worth doing. :)
Edward Loper