[Python-3000] Traits/roles instead of ABCs

Collin Winter collinw at gmail.com
Mon Apr 30 03:40:41 CEST 2007


On 4/29/07, Steven Bethard <steven.bethard at gmail.com> wrote:
> On 4/29/07, Talin <talin at acm.org> wrote:
> > If it were technically possible, I would recommend that this PEP have to
> > run the same gauntlet that any other large library addition would, which
> > is to go through a long period of community feedback and criticism,
> > during which a large number of people actually attempt to use the
> > feature for real work.
> This sounds like a pretty good reason to add __isinstance__() and
> __issubclass__().  Then the various ABCs can be distributed as
> third-party modules but can still make sure that things like
> isinstance(42, Real) and issubclass(int, Complex) are True (or
> whatever other assertions people want to make).

To me, having to mess with something as fundamental as isinstance()
and issubclass() sounds like a pretty good reason to prefer a
different solution over ABCs.

The mechanism I'm most familiar with for solving this problem (which,
unless I've missed something, is, "how do I make sure this object does
what I expect?") is Perl 6's roles system; if you know about Squeak
Smalltalk's "traits" system, you're on the same page.

(I discussed this with Jeffery Yasskin at lunch the other day, and he
seemed interested, so here follows a medium-length explanation of

The idea is that we leave the traditional class system to manage an
object's implementation and use a more-or-less orthogonal system to
manage that same object's behavior.

Quoting from an article on Perl 6's roles,

A role is a named collection of behavior — a set of methods identified
by a unique name. This resembles a class or a type, in that referring
to the role refers to the combined set of behaviors, but it is more
general than a class and more concrete than a type.

Put another way, a role is an assertion about a set of capabilities.

The key part of traits/roles is that, because the system is separate
from classes, you can do runtime role composition without a) mucking
with __bases__, or b) making isinstance() and issubclass() squishy and
ill-defined. By "runtime role composition", I mean it would be
possible to do something like this at runtime:

py> int.implements(Ring)

or (depending on your spelling preferences)

py> Ring.implemented_by(int)

That is, it would be possible to distribute Jeffery's numeric kinds as
a third-party library and still have them affect the built-in numeric

Now, how do roles/traits differ from interfaces? The primary
distinction is that interfaces only define behavior, whereas roles can
provide a dummy implementation. It would be possible for an Equality
role to be defined like this (strawman syntax):

class Equality(Role):
  def __eq__(self, other):
    return not self != other

  def __ne__(self, other):
    return not self == other

How would all this work in Python? Here's a collection of strawman proposals:

1. For declaring roles statically, either class decorators or class
arguments could be used:

class A:

class A(does=Complex):

2. I gave a strawman syntax proposal for runtime role composition
above (modulo the method name): "Ring.done_by(int)" or
"int.does(Ring)". (Perl 6 speaks in terms of "an object/class *does* a
role".) Perl 6 also allows individual instances to do different roles
than their class does, but I'm not sure we want to go that far.

3. For querying role performance, something like this could work:
"isdoneby(x, Ring)".

If class decorators and the "role.method(object)" spellings were used,
I think this could probably be issued as a third-party package with no
need to modify the interpreter at all.

For the interested, here's some more reading about roles/traits in
roughly descending order of readability:


If there's interest in this, I could probably whip up a PEP before the deadline.

Collin Winter

More information about the Python-3000 mailing list