Author lukasz.langa
Recipients ecatmur, gvanrossum, lukasz.langa, rhettinger
Date 2013-06-27.14:21:35
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1372342897.77.0.286523575122.issue18244@psf.upfronthosting.co.za>
In-reply-to
Content
The reason why I think it's wrong to special-case ABCs that are
explicitly in the MRO is that it's only one of four methods of virtual
subclassing:

1. Explicit MRO;

2. Abstract functionality implicitly implemented without any form of
   registration;

3. abc.register();

4. One of the above on a base of the type in question.

This creates more possibilities for conflicts than just the example
described in my last message. For instance, what's the preferred ABC
here, Iterable or Sized?

  >>> class E(Sized):
  ...   def __len__(self):
  ...     return 0
  ...   def __iter__(self):
  ...     for i in []:
  ...       yield i

My answer is: neither. E equally is-a Sized and is-a Iterable. If the
dispatcher favors one over the other, you will get people upset about
the decision, no matter which one it is.

Note that the conflict arises only for multiple ABCs which end up *on
the same level* of the MRO. For instance in the example below the
dispatch always chooses the Iterable implementation (the functionality
appears directly on F, whereas Sized appears on a base):

  >>> class HasSize(Sized):
  ...   def __len__(self):
  ...     return 0
  ...
  >>> class F(HasSize):
  ...   def __iter__(self):
  ...     for i in []:
  ...       yield i

If we wanted to favor the ABCs that are explicitly in the MRO, what
should we choose in this case? If we say "Sized", then it breaks the
whole idea of arranging the ABCs next to the class that first implements
them in the MRO.

But it gets better! Suppose you have a generic function with
implementations for Sized and Callable. Which implementation should we
choose for class G?

  >>> class G(MutableMapping):
  ...   def __call__(self):
  ...     return None
  ...   # the rest of the MutableMapping implementation

Seems like Sized because it is explicitly in the MRO, right? What about
H then?

  >>> class H(dict):
  ...   def __call__(self):
  ...     return None

Well, now it's suddenly Callable because Sized is "only" a virtual base
class. I don't think we should let that happen.

It all comes down to the question whether you consider ABCs to be bases
FOR REAL or only sort-of-but-not-really. I believe they're real bases
regardless of the method of registration. Implicit implementation and
abc.register() doesn't make the base any less real.

All in all, the user will ask: "Hey, it's true, I have a tricky type
that subclasses both an ABC1 and an ABC2 and singledispatch raises
a RuntimeError. How do I make this work?" The answer is simple: just
register a more specific implementation on the generic function, even if
it simply selects one of the existing ones:

  generic_func.register(TrickyType, generic_func.dispatch(ABC2))

Explicit is better than implicit.
History
Date User Action Args
2013-06-27 14:21:38lukasz.langasetrecipients: + lukasz.langa, gvanrossum, rhettinger, ecatmur
2013-06-27 14:21:37lukasz.langasetmessageid: <1372342897.77.0.286523575122.issue18244@psf.upfronthosting.co.za>
2013-06-27 14:21:37lukasz.langalinkissue18244 messages
2013-06-27 14:21:37lukasz.langacreate