Explicit self argument, implicit super argument
(Disclaimer: I have no issue with "self." and "super." attribute access, which is what most people think of when they think "implicit self".) While showing a coworker a bytecode hack I made this weekend - it allows insertion of arbitrary function parameters into an already-existing function - he asked for a use case. I gave him this: class A(object): # ... def method(x, y): self.x = x super.method(y) where 'method' is replaced by this method wrapper via metaclass or decorator: def method_wrapper(self, *args, **kwargs): return hacked_method(self, super(cls, self), *args, **kwargs) These hackish details aren't important, the resulting "A.method" is. It occurred to me that explicit self and implicit super is semantically inconsistent. Here's Python 3000's version of the above (please compare): class A(object): def method(self, x, y): self.x = x super.method(y) Why have a magic "super" local but not a magic "self" local? From a *general usage* standpoint, the only reason I can think of (which is not necessarily the only one, which is why I'm asking) is that a person might want to change the name of "self", like so: class AddLike(object): # ... def __add__(a, b): # return something def __radd__(b, a): # return something But reverse binary special methods are the only case where it's not extremely bad form. Okay, two reasons for explicit self: backward compatibility, but 2to3 would make it a non-issue. From an *implementation standpoint*, making self implicit - a cell variable like super, for example - would wreak havoc with the current bound/unbound method distinction, but I'm not so sure that's a bad thing. Neil
The reason for explicit self in method definition signatures is semantic consistency. If you write class C: def foo(self, x, y): ... This really *is* the same as writing class C: pass def foo(self, x, y): ... C.foo = foo And of course it works the other way as well: you really *can* invoke foo with an explicit argument for self as follows: class D(C): ... C.foo(D(), 1, 2) IOW it's not an implementation hack -- it is a semantic device. --Guido On Nov 19, 2007 11:00 AM, Neil Toronto <ntoronto@cs.byu.edu> wrote:
(Disclaimer: I have no issue with "self." and "super." attribute access, which is what most people think of when they think "implicit self".)
While showing a coworker a bytecode hack I made this weekend - it allows insertion of arbitrary function parameters into an already-existing function - he asked for a use case. I gave him this:
class A(object): # ... def method(x, y): self.x = x super.method(y)
where 'method' is replaced by this method wrapper via metaclass or decorator:
def method_wrapper(self, *args, **kwargs): return hacked_method(self, super(cls, self), *args, **kwargs)
These hackish details aren't important, the resulting "A.method" is.
It occurred to me that explicit self and implicit super is semantically inconsistent. Here's Python 3000's version of the above (please compare):
class A(object): def method(self, x, y): self.x = x super.method(y)
Why have a magic "super" local but not a magic "self" local? From a *general usage* standpoint, the only reason I can think of (which is not necessarily the only one, which is why I'm asking) is that a person might want to change the name of "self", like so:
class AddLike(object): # ... def __add__(a, b): # return something def __radd__(b, a): # return something
But reverse binary special methods are the only case where it's not extremely bad form. Okay, two reasons for explicit self: backward compatibility, but 2to3 would make it a non-issue.
From an *implementation standpoint*, making self implicit - a cell variable like super, for example - would wreak havoc with the current bound/unbound method distinction, but I'm not so sure that's a bad thing.
Neil _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
-- --Guido van Rossum (home page: http://www.python.org/~guido/)
On 11/19/07, Guido van Rossum <guido@python.org> wrote:
The reason for explicit self in method definition signatures is semantic consistency. If you write
class C: def foo(self, x, y): ...
This really *is* the same as writing
class C: pass
def foo(self, x, y): ... C.foo = foo
What about an instancemethod decorator? @instancemethod(C) def foo(x, y): ...
And of course it works the other way as well: you really *can* invoke foo with an explicit argument for self as follows:
class D(C): ...
C.foo(D(), 1, 2)
Couldn't __builtin__.__super__ be used? It would look pretty weird if you invoked a method higher up the MRO, though. I find that these cases come up rarely in my code, while I forget the 'self' argument much more frequently, but YMMV. Luke
Guido van Rossum wrote:
The reason for explicit self in method definition signatures is semantic consistency. If you write
class C: def foo(self, x, y): ...
This really *is* the same as writing
class C: pass
def foo(self, x, y): ... C.foo = foo
And of course it works the other way as well: you really *can* invoke foo with an explicit argument for self as follows:
class D(C): ...
C.foo(D(), 1, 2)
IOW it's not an implementation hack -- it is a semantic device.
Ah, thanks, that helps. (I'll be able to sleep tonight. :D) This semantic device, of course, would really suck if applied to "super": d = D() C.foo(d, super(C, d), 1, 2) # strange and hideous which is a great reason that the new "super" is implicit. (Before I continue, please understand that I'm not arguing for a language change. Responses to my last two ideas have shown me that I need to thoroughly understand why things are as they are right now while considering a change, and long before advocating one. It also goes over better with the language designers.) Now, correct me if I'm wrong, but it seems there are only two use cases for DistantParentOfD.method(D_instance, ...): 1. The Good Case: you know the "next-method" as determined by the MRO isn't the right one to call. Multiple inheritance can twist you into this sort of behavior, though if it does, your design likely needs reconsideration. 2. The Evil Case: you know the override method as defined by D isn't the one you want for your extra-special D instance. This should be possible but never encouraged. Because the runtime enforces isinstance(D_instance, D), everything else can be handled with D_instance.method(...) or self.method() or super.method(). We know that #1 and #2 above are the uncommon cases, which is why the new "super", which covers the common ones, doesn't cover those. Is it right to say that the explicit "self" parameter only exists to enable those two uncommon cases? Of course, if self were implicit, there would still need to be a way to spell DistantParentOfD.method(D_instance, ...). Being the uncommon case, maybe it shouldn't have a nice spelling: as_parent(C, D_instance).method(...) Trying-to-understandingly-yours, Neil
On 19 Nov 2007, at 20:42, Neil Toronto wrote:
[...]
Now, correct me if I'm wrong, but it seems there are only two use cases for DistantParentOfD.method(D_instance, ...):
1. The Good Case: you know the "next-method" as determined by the MRO isn't the right one to call. Multiple inheritance can twist you into this sort of behavior, though if it does, your design likely needs reconsideration.
2. The Evil Case: you know the override method as defined by D isn't the one you want for your extra-special D instance. This should be possible but never encouraged.
Because the runtime enforces isinstance(D_instance, D), everything else can be handled with D_instance.method(...) or self.method() or super.method(). We know that #1 and #2 above are the uncommon cases, which is why the new "super", which covers the common ones, doesn't cover those.
Is it right to say that the explicit "self" parameter only exists to enable those two uncommon cases?
Self being explicit makes it less selfish :) To illustrate, I like that you can do: class Foo(str): def mybar(self): class Bar(str): def madeby(me): return "I am %s and I was made by %s" % (me, self) return Bar
foo=Foo("foo") bar=foo.mybar() Bar=foo.mybar() bar=Bar("bar") print bar.madeby() I am bar and I was made by foo
This depends on 'self' being explicit and is not related to super. I didn't know about implicit super, it's probably great but my initial reaction is that I don't like it :( Why not: class Foo: @with_super def bar(super, self, x, y): super.bar(x, y) ... -- Arnaud
Arnaud Delobelle wrote:
Self being explicit makes it less selfish :) To illustrate, I like that you can do:
class Foo(str): def mybar(self): class Bar(str): def madeby(me): return "I am %s and I was made by %s" % (me, self) return Bar
foo=Foo("foo") #bar=foo.mybar() # typo Bar=foo.mybar() bar=Bar("bar") print bar.madeby() I am bar and I was made by foo
Ah, I see. If self were passed implicitly, you would need to make a Bar.__init__ that received and stored the outer self. I think I'd call this a third uncommon case. Outside functional idioms, common is usually flat.
This depends on 'self' being explicit and is not related to super. I didn't know about implicit super, it's probably great but my initial reaction is that I don't like it :(
Why not:
class Foo: @with_super def bar(super, self, x, y): super.bar(x, y) ...
Probably because it's way too common to require a decorator for it. Users would have to make "always use @with_super" into a coding habit. (Sort of like "self" actually.) It'd also be yet another thing to keep in mind while reading code: did this method use @with_super or not? Neil
Neil Toronto wrote:
Because the runtime enforces isinstance(D_instance, D), everything else can be handled with D_instance.method(...) or self.method() or super.method().
But super() is not a general replacement for explicit inherited method calls. It's only appropriate in special, quite restricted circumstances. -- Greg
Neil Toronto wrote:
class A(object): def method(self, x, y): self.x = x super.method(y)
Is that really how it's going to be? What if self isn't called 'self'? I would rather see super.method(self, y)
From an *implementation standpoint*, making self implicit - a cell variable like super, for example - would wreak havoc with the current bound/unbound method distinction, but I'm not so sure that's a bad thing.
What happens to explicit inherited method calls? If they become impossible or awkward, it's very definitely a bad thing. -- Greg
On 11/19/07, Arnaud Delobelle <arno@marooned.org.uk> wrote:
Self being explicit makes it less selfish :) To illustrate, I like that you can do:
class Foo(str): def mybar(self): class Bar(str): def madeby(me): return "I am %s and I was made by %s" % (me, self) return Bar
How about: class Foo(str): def mybar(): outer = self class Bar(str): def madeby(): return "I am %s and I was made by %s" % (self, outer) return Bar Luke
On 11/19/07, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Neil Toronto wrote:
class A(object): def method(self, x, y): self.x = x super.method(y)
Is that really how it's going to be? What if self isn't called 'self'?
I would rather see
super.method(self, y)
PEP 3135 specifies that the first argument of the method is used, regardless of name: http://www.python.org/dev/peps/pep-3135/#specification Luke
Luke Stebbing wrote:
On 11/19/07, Arnaud Delobelle <arno@marooned.org.uk> wrote:
Self being explicit makes it less selfish :) To illustrate, I like that you can do:
class Foo(str): def mybar(self): class Bar(str): def madeby(me): return "I am %s and I was made by %s" % (me, self) return Bar
How about:
class Foo(str): def mybar(): outer = self class Bar(str): def madeby(): return "I am %s and I was made by %s" % (self, outer) return Bar
Good point. I actually like this better, since it forces the outer scope self to have a different name, removing a source of confusion. Back down to two uncommon use cases so far, then. Neil
On Tue, November 20, 2007 1:27 am, Luke Stebbing wrote:
On 11/19/07, Arnaud Delobelle <arno@marooned.org.uk> wrote:
Self being explicit makes it less selfish :) To illustrate, I like that you can do:
class Foo(str): def mybar(self): class Bar(str): def madeby(me): return "I am %s and I was made by %s" % (me, self) return Bar
How about:
class Foo(str): def mybar(): outer = self class Bar(str): def madeby(): return "I am %s and I was made by %s" % (self, outer) return Bar
Luke
I suppose, though it's a waste of a cell IMHO ;) -- Arnaud
Neil Toronto wrote:
Because the runtime enforces isinstance(D_instance, D), everything else can be handled with D_instance.method(...) or self.method() or super.method().
But super() is not a general replacement for explicit inherited method calls. It's only appropriate in special, quite restricted circumstances.
Exactly. There are two common method-calling cases, and an uncommon one. In order of expected number of occurrences, with #3 being quite low: 1. self.method(...) 2. super.method(...) 3. DistantParent.method(self, ...) (either to get out of the MRO or because you're feeling evil - two use cases for it) If self were only implicitly available, #3 would need a new spelling, as you say. That's not hard to do, and I've already suggested as_parent(DistantParent, self).method(...) as an alternate spelling for the uncommon cases. That's not to say I'm advocating such a thing for Python 3.0 - just showing that it's possible to cover the current known use cases. Actually, I suspect there aren't any more use cases, as all correct ways of calling the method (those that don't raise an exception) are covered, and implicit self would still be as accessible from anywhere as explicit self is. Would saving six keystrokes per method, reducing noise in every method header, and removing the need for a habit (always including self in the parameter list) be enough to justify a change? I'm going to guess either "no" or "not right now". If I were doing it from scratch, I'd make self and super into keywords, and change method binding to return a function with them included in the locals somehow. Neil
On 11/20/07, ntoronto@cs.byu.edu <ntoronto@cs.byu.edu> wrote:
Neil Toronto wrote:
Because the runtime enforces isinstance(D_instance, D), everything else can be handled with D_instance.method(...) or self.method() or super.method().
But super() is not a general replacement for explicit inherited method calls. It's only appropriate in special, quite restricted circumstances.
I would say it it almost always appropriate. The times it fails are when (1) You want to change the name of the method. Fair enough -- but you can usually forward to self.othername (2) You want to change the arguments of the method. Changing the signature is generally a bad idea, though it is tolerable for constructors. (3) You're explicitly managing the order of super-calls (==> fragile, and the inheritance is already a problem) (4) Backwards compatibility with some other class that uses explicit class names instead of super. Number 4 is pretty common still, but it is just a backwards compatibility hack that makes code more fragile.
Would saving six keystrokes per method, reducing noise in every method header, and removing the need for a habit (always including self in the parameter list) be enough to justify a change? I'm going to guess either "no" or "not right now". If I were doing it from scratch, I'd make self and super into keywords, and change method binding to return a function with them included in the locals somehow.
Agreed. The fact that method parameter lists look different at the definition and call sites is an annoying wart, but ... too late to change. -jJ
ntoronto@cs.byu.edu wrote:
There are two common method-calling cases, and an uncommon one. In order of expected number of occurrences, with #3 being quite low:
1. self.method(...)
2. super.method(...)
3. DistantParent.method(self, ...)
You're still missing an important case. I would rank them as 1. self.method(...) 2. DirectParent.method(self, ...) 3. super.method(...) 4. DistantParent.method(self, ...) Anything that made number 2 difficult would be unacceptable. -- Greg
Jim Jewett wrote:
I would say it it almost always appropriate. The times it fails are when
(5) Someone multiply-inherits from your class, and you end up calling one of their methods instead of yours, when neither your method or their method is expecting this to happen. Plus various other problems. There's a good discussion of the issues here: http://fuhm.net/super-harmful/ -- Greg
On Mon, Nov 19, 2007 at 12:00:04PM -0700, Neil Toronto wrote:
(Disclaimer: I have no issue with "self." and "super." attribute access, which is what most people think of when they think "implicit self".)
I don't feel easy about the new super either (maybe from a different perspective than Neil's). Why should self be passed to methods using a parameter but super should use magic (something like a global name that holds different objects in different places). Instead of making self implicit, I'd like super to use less magic. I much preferred super(self).foo(*args). Some magic for finding the surrounding class might be needed but at least we don't use the first parameter of a method implicitly. (I don't see this in the alternative proposals sections of :PEP:`3135`). It can be made backward compatible, too. I have not read new super discussions; So sorry if it has been already discussed. -- Ali
participants (8)
-
Ali Gholami Rudi
-
Arnaud Delobelle
-
Greg Ewing
-
Guido van Rossum
-
Jim Jewett
-
Luke Stebbing
-
Neil Toronto
-
ntoronto@cs.byu.edu