[Python-ideas] Pseudo methods

Steven D'Aprano steve at pearwood.info
Fri Aug 4 10:37:01 EDT 2017


On Fri, Aug 04, 2017 at 10:20:55AM -0300, Joao S. O. Bueno wrote:
> Had not this been discussed here earlier this year?
> 
> (And despite there being perceived dangers to readability in the long term,
> was accepted?)
> 
> Here it is on an archive:
> https://mail.python.org/pipermail/python-ideas/2017-February/044551.html

I don't read this as the same proposal. For starters, I don't believe 
that it was intended to allow monkey-patching of builtins.

Another is that the syntax is much more explicit about where the method 
is going:

def MyClass.method(self, arg):
    ...

is clearly a method of MyClass.

There was, if I recall, some open discussion of whether arbitrary 
assignment targets should be allowed:

def module.func(x or None)[23 + n].attr.__type__.method(self, arg):
    ...

or if we should intentionally limit the allowed syntax, like we do for 
decorators. My vote is for intentionally limiting it to a single dotted 
name, like MyClass.method.



> And anyway - along that discussion, despite dislikng the general idea, I
> got convinced that
> creating an outside method that makes "super" or "__class__" work was
> rather complicated.

Complicated is an understatement. It's horrid :-)

Here's the problem: we can successfully inject methods into a class:

# -----%<-----
class Parent:
    def spam(self):
        return "spam"

class Child(Parent):
    def food(self):
        return 'yummy ' + self.spam()

c = Child()
c.food()  # returns 'yummy spam' as expected

# inject a new method
def spam(self):
    return 'spam spam spam'

Child.spam = spam
c.food()  # returns 'yummy spam spam spam' as expected
# -----%<-----


But not if you use the zero-argument form of super():


# -----%<-----
del Child.spam  # revert to original

def spam(self):
    s = super().spam()
    return ' '.join([s]*3)

Child.spam = spam
c.food()
# -----%<-----


This raises:

RuntimeError: super(): __class__ cell not found


This is the simplest thing I've found that will fix it:

# -----%<-----
del Child.spam  # revert to original again
def outer():
    __class__ = Child
    def spam(self):
        s = super().spam()
        return ' '.join([s]*3)
    return spam

Child.spam = outer()
c.food()  # returns 'yummy spam spam spam' as expected
# -----%<-----


It's probably possibly to wrap this up in a decorator that takes Child 
as argument, but I expect it will probably require messing about with 
the undocumented FunctionType constructor to build up a new closure from 
the bits and pieces scavenged from the decorated function. 


> Maybe we could just have a decorator for that, that would properly create
> the __class__ cell?

I expect its possible. A challenge to somebody who wants to get their 
hands dirty.


-- 
Steve


More information about the Python-ideas mailing list