[Python-ideas] adding dictionaries

Steven D'Aprano steve at pearwood.info
Tue Jul 29 15:35:56 CEST 2014


On Mon, Jul 28, 2014 at 11:15:44PM -0700, Andrew Barnert wrote:
> On Monday, July 28, 2014 8:34 PM, Steven D'Aprano <steve at pearwood.info> wrote:
> [snip]
> > * when called from a class, it should behave like a class method: 
> >   MyMapping.merged(a, b, c) should return an instance of MyMapping;
> > 
> > * but when called from an instance, it should behave like an instance
> >   method, with self included in the chain of mappings to merge:
> >   a.merged(b, c) rather than a.merged(a, b, c).
> > 
> > 
> > I have a descriptor type which implements the behaviour from the last 
> > two bullet points, so from a technical standpoint it's not hard to 
> > implement this. But I can imagine a lot of push-back from the more 
> > conservative developers about adding a *fourth* method type (even if it 
> > is private) to the Python builtins, so it would take a really compelling 
> > use-case to justify adding a new method type and a new dict method.
> > 
> > (Personally, I think this hybrid class/instance method type is far more 
> > useful than staticmethod, since I've actually used it in production 
> > code, but staticmethod isn't going away.)
> 
> 
> How is this different from a plain-old (builtin or normal) method?

I see I failed to explain clearly, sorry about that.

With class methods, the method always receives the class as the first 
argument. Regardless of whether you write dict.fromkeys or 
{1:'a'}.fromkeys, the first argument is the class, dict.

With instance methods, the method receives the instance. If you call it 
from a class, the method is "unbound" and you are responsible for 
providing the "self" argument.

To me, this hypothetical merged() method sometimes feels like an 
alternative constructor, like fromkeys, and therefore best written as a 
class method, but sometimes like a regular method. Since it feels like a 
hybrid to me, I think a hybrid descriptor approach is best, but as I 
already said I can completely understand if conservative developers 
reject this idea.

In the hybrid form I'm referring to, the first argument provided is the 
class when called from the class, and the instance when called from an 
instance. Imagine it written in pure Python like this:

class dict:
    @hybridmethod
    def merged(this, *args, **kwargs):
        if isinstance(this, type):
            # Called from the class
            new = this()
        else:
            # Called from an instance.
            new = this.copy()
        for arg in args:
            new.update(arg)
        new.update(kwargs)
        return new


If merged is a class method, we can avoid having to worry about the 
case where your "a" mapping happens to be a list of (key,item) pairs:

    a.merged(b, c, d)  # Fails if a = [(key, item), ...]
    dict.merged(a, b, c, d)  # Always succeeds.

It also allows us to easily specify a different mapping type for the 
result:

    MyMapping.merged(a, b, c, d)

although some would argue this is just as clear:

     MyMapping().merged(a, b, c, d)

albeit perhaps not quite as efficient if MyMapping is expensive to 
instantiate. (You create an empty instance, only to throw it away 
again.)

On the other hand, there are use-cases where merged() best communicates 
the intent if it is a regular instance method. Consider:

    settings = application_defaults.merged(
                       global_settings, 
                       user_settings, 
                       commandline_settings)

seems more clear to me than:

    settings = dict.merged(
                       application_defaults,
                       global_settings, 
                       user_settings, 
                       commandline_settings)

especially in the case that application_defaults is a dict literal.

tl;dr It's not often that I can't decide whether a method ought to be a 
class method or an instance method, the decision is usually easy, but 
this is one of those times.


-- 
Steven


More information about the Python-ideas mailing list