565 lines
16 KiB
ReStructuredText
565 lines
16 KiB
ReStructuredText
PEP: 3133
|
|
Title: Introducing Roles
|
|
Version: $Revision$
|
|
Last-Modified: $Date$
|
|
Author: Collin Winter <collinwinter@google.com>
|
|
Status: Rejected
|
|
Type: Standards Track
|
|
Content-Type: text/x-rst
|
|
Requires: 3115, 3129
|
|
Created: 01-May-2007
|
|
Python-Version: 3.0
|
|
Post-History: 13-May-2007
|
|
|
|
|
|
Rejection Notice
|
|
================
|
|
|
|
This PEP has helped push :pep:`3119` towards a saner, more minimalistic
|
|
approach. But given the latest version of :pep:`3119` I much prefer
|
|
that. GvR.
|
|
|
|
|
|
Abstract
|
|
========
|
|
|
|
Python's existing object model organizes objects according to their
|
|
implementation. It is often desirable -- especially in
|
|
duck typing-based language like Python -- to organize objects by
|
|
the part they play in a larger system (their intent), rather than by
|
|
how they fulfill that part (their implementation). This PEP
|
|
introduces the concept of roles, a mechanism for organizing
|
|
objects according to their intent rather than their implementation.
|
|
|
|
|
|
Rationale
|
|
=========
|
|
|
|
In the beginning were objects. They allowed programmers to marry
|
|
function and state, and to increase code reusability through concepts
|
|
like polymorphism and inheritance, and lo, it was good. There came
|
|
a time, however, when inheritance and polymorphism weren't enough.
|
|
With the invention of both dogs and trees, we were no longer able to
|
|
be content with knowing merely, "Does it understand 'bark'?"
|
|
We now needed to know what a given object thought that "bark" meant.
|
|
|
|
One solution, the one detailed here, is that of roles, a mechanism
|
|
orthogonal and complementary to the traditional class/instance system.
|
|
Whereas classes concern themselves with state and implementation, the
|
|
roles mechanism deals exclusively with the behaviours embodied in a
|
|
given class.
|
|
|
|
This system was originally called "traits" and implemented for Squeak
|
|
Smalltalk [#traits-paper]_. It has since been adapted for use in
|
|
Perl 6 [#perl6-s12]_ where it is called "roles", and it is primarily
|
|
from there that the concept is now being interpreted for Python 3.
|
|
Python 3 will preserve the name "roles".
|
|
|
|
In a nutshell: roles tell you *what* an object does, classes tell you
|
|
*how* an object does it.
|
|
|
|
In this PEP, I will outline a system for Python 3 that will make it
|
|
possible to easily determine whether a given object's understanding
|
|
of "bark" is tree-like or dog-like. (There might also be more
|
|
serious examples.)
|
|
|
|
|
|
A Note on Syntax
|
|
----------------
|
|
|
|
A syntax proposals in this PEP are tentative and should be
|
|
considered to be strawmen. The necessary bits that this PEP depends
|
|
on -- namely :pep:`3115`'s class definition syntax and :pep:`3129`'s class
|
|
decorators -- are still being formalized and may change. Function
|
|
names will, of course, be subject to lengthy bikeshedding debates.
|
|
|
|
|
|
Performing Your Role
|
|
====================
|
|
|
|
Static Role Assignment
|
|
----------------------
|
|
|
|
Let's start out by defining ``Tree`` and ``Dog`` classes ::
|
|
|
|
class Tree(Vegetable):
|
|
|
|
def bark(self):
|
|
return self.is_rough()
|
|
|
|
|
|
class Dog(Animal):
|
|
|
|
def bark(self):
|
|
return self.goes_ruff()
|
|
|
|
While both implement a ``bark()`` method with the same signature,
|
|
they do wildly different things. We need some way of differentiating
|
|
what we're expecting. Relying on inheritance and a simple
|
|
``isinstance()`` test will limit code reuse and/or force any dog-like
|
|
classes to inherit from ``Dog``, whether or not that makes sense.
|
|
Let's see if roles can help. ::
|
|
|
|
@perform_role(Doglike)
|
|
class Dog(Animal):
|
|
...
|
|
|
|
@perform_role(Treelike)
|
|
class Tree(Vegetable):
|
|
...
|
|
|
|
@perform_role(SitThere)
|
|
class Rock(Mineral):
|
|
...
|
|
|
|
We use class decorators from :pep:`3129` to associate a particular role
|
|
or roles with a class. Client code can now verify that an incoming
|
|
object performs the ``Doglike`` role, allowing it to handle ``Wolf``,
|
|
``LaughingHyena`` and ``Aibo`` [#aibo]_ instances, too.
|
|
|
|
Roles can be composed via normal inheritance: ::
|
|
|
|
@perform_role(Guard, MummysLittleDarling)
|
|
class GermanShepherd(Dog):
|
|
|
|
def guard(self, the_precious):
|
|
while True:
|
|
if intruder_near(the_precious):
|
|
self.growl()
|
|
|
|
def get_petted(self):
|
|
self.swallow_pride()
|
|
|
|
Here, ``GermanShepherd`` instances perform three roles: ``Guard`` and
|
|
``MummysLittleDarling`` are applied directly, whereas ``Doglike``
|
|
is inherited from ``Dog``.
|
|
|
|
|
|
Assigning Roles at Runtime
|
|
--------------------------
|
|
|
|
Roles can be assigned at runtime, too, by unpacking the syntactic
|
|
sugar provided by decorators.
|
|
|
|
Say we import a ``Robot`` class from another module, and since we
|
|
know that ``Robot`` already implements our ``Guard`` interface,
|
|
we'd like it to play nicely with guard-related code, too. ::
|
|
|
|
>>> perform(Guard)(Robot)
|
|
|
|
This takes effect immediately and impacts all instances of ``Robot``.
|
|
|
|
|
|
Asking Questions About Roles
|
|
----------------------------
|
|
|
|
Just because we've told our robot army that they're guards, we'd
|
|
like to check in on them occasionally and make sure they're still at
|
|
their task. ::
|
|
|
|
>>> performs(our_robot, Guard)
|
|
True
|
|
|
|
What about that one robot over there? ::
|
|
|
|
>>> performs(that_robot_over_there, Guard)
|
|
True
|
|
|
|
The ``performs()`` function is used to ask if a given object
|
|
fulfills a given role. It cannot be used, however, to ask a
|
|
class if its instances fulfill a role: ::
|
|
|
|
>>> performs(Robot, Guard)
|
|
False
|
|
|
|
This is because the ``Robot`` class is not interchangeable
|
|
with a ``Robot`` instance.
|
|
|
|
|
|
Defining New Roles
|
|
==================
|
|
|
|
Empty Roles
|
|
-----------
|
|
|
|
Roles are defined like a normal class, but use the ``Role``
|
|
metaclass. ::
|
|
|
|
class Doglike(metaclass=Role):
|
|
...
|
|
|
|
Metaclasses are used to indicate that ``Doglike`` is a ``Role`` in
|
|
the same way 5 is an ``int`` and ``tuple`` is a ``type``.
|
|
|
|
|
|
Composing Roles via Inheritance
|
|
-------------------------------
|
|
|
|
Roles may inherit from other roles; this has the effect of composing
|
|
them. Here, instances of ``Dog`` will perform both the
|
|
``Doglike`` and ``FourLegs`` roles. ::
|
|
|
|
class FourLegs(metaclass=Role):
|
|
pass
|
|
|
|
class Doglike(FourLegs, Carnivor):
|
|
pass
|
|
|
|
@perform_role(Doglike)
|
|
class Dog(Mammal):
|
|
pass
|
|
|
|
|
|
Requiring Concrete Methods
|
|
--------------------------
|
|
|
|
So far we've only defined empty roles -- not very useful things.
|
|
Let's now require that all classes that claim to fulfill the
|
|
``Doglike`` role define a ``bark()`` method: ::
|
|
|
|
class Doglike(FourLegs):
|
|
|
|
def bark(self):
|
|
pass
|
|
|
|
No decorators are required to flag the method as "abstract", and the
|
|
method will never be called, meaning whatever code it contains (if any)
|
|
is irrelevant. Roles provide *only* abstract methods; concrete
|
|
default implementations are left to other, better-suited mechanisms
|
|
like mixins.
|
|
|
|
Once you have defined a role, and a class has claimed to perform that
|
|
role, it is essential that that claim be verified. Here, the
|
|
programmer has misspelled one of the methods required by the role. ::
|
|
|
|
@perform_role(FourLegs)
|
|
class Horse(Mammal):
|
|
|
|
def run_like_teh_wind(self)
|
|
...
|
|
|
|
This will cause the role system to raise an exception, complaining
|
|
that you're missing a ``run_like_the_wind()`` method. The role
|
|
system carries out these checks as soon as a class is flagged as
|
|
performing a given role.
|
|
|
|
Concrete methods are required to match exactly the signature demanded
|
|
by the role. Here, we've attempted to fulfill our role by defining a
|
|
concrete version of ``bark()``, but we've missed the mark a bit. ::
|
|
|
|
@perform_role(Doglike)
|
|
class Coyote(Mammal):
|
|
|
|
def bark(self, target=moon):
|
|
pass
|
|
|
|
This method's signature doesn't match exactly with what the
|
|
``Doglike`` role was expecting, so the role system will throw a bit
|
|
of a tantrum.
|
|
|
|
|
|
Mechanism
|
|
=========
|
|
|
|
The following are strawman proposals for how roles might be expressed
|
|
in Python. The examples here are phrased in a way that the roles
|
|
mechanism may be implemented without changing the Python interpreter.
|
|
(Examples adapted from an article on Perl 6 roles by Curtis Poe
|
|
[#roles-examples]_.)
|
|
|
|
1. Static class role assignment ::
|
|
|
|
@perform_role(Thieving)
|
|
class Elf(Character):
|
|
...
|
|
|
|
``perform_role()`` accepts multiple arguments, such that this is
|
|
also legal: ::
|
|
|
|
@perform_role(Thieving, Spying, Archer)
|
|
class Elf(Character):
|
|
...
|
|
|
|
The ``Elf`` class now performs both the ``Thieving``, ``Spying``,
|
|
and ``Archer`` roles.
|
|
|
|
2. Querying instances ::
|
|
|
|
if performs(my_elf, Thieving):
|
|
...
|
|
|
|
The second argument to ``performs()`` may also be anything with a
|
|
``__contains__()`` method, meaning the following is legal: ::
|
|
|
|
if performs(my_elf, set([Thieving, Spying, BoyScout])):
|
|
...
|
|
|
|
Like ``isinstance()``, the object needs only to perform a single
|
|
role out of the set in order for the expression to be true.
|
|
|
|
|
|
Relationship to Abstract Base Classes
|
|
=====================================
|
|
|
|
Early drafts of this PEP [#proposal]_ envisioned roles as competing
|
|
with the abstract base classes proposed in :pep:`3119`. After further
|
|
discussion and deliberation, a compromise and a delegation of
|
|
responsibilities and use-cases has been worked out as follows:
|
|
|
|
* Roles provide a way of indicating an object's semantics and abstract
|
|
capabilities. A role may define abstract methods, but only as a
|
|
way of delineating an interface through which a particular set of
|
|
semantics are accessed. An ``Ordering`` role might require that
|
|
some set of ordering operators be defined. ::
|
|
|
|
class Ordering(metaclass=Role):
|
|
def __ge__(self, other):
|
|
pass
|
|
|
|
def __le__(self, other):
|
|
pass
|
|
|
|
def __ne__(self, other):
|
|
pass
|
|
|
|
# ...and so on
|
|
|
|
In this way, we're able to indicate an object's role or function
|
|
within a larger system without constraining or concerning ourselves
|
|
with a particular implementation.
|
|
|
|
* Abstract base classes, by contrast, are a way of reusing common,
|
|
discrete units of implementation. For example, one might define an
|
|
``OrderingMixin`` that implements several ordering operators in
|
|
terms of other operators. ::
|
|
|
|
class OrderingMixin:
|
|
def __ge__(self, other):
|
|
return self > other or self == other
|
|
|
|
def __le__(self, other):
|
|
return self < other or self == other
|
|
|
|
def __ne__(self, other):
|
|
return not self == other
|
|
|
|
# ...and so on
|
|
|
|
Using this abstract base class - more properly, a concrete
|
|
mixin - allows a programmer to define a limited set of operators
|
|
and let the mixin in effect "derive" the others.
|
|
|
|
By combining these two orthogonal systems, we're able to both
|
|
a) provide functionality, and b) alert consumer systems to the
|
|
presence and availability of this functionality. For example,
|
|
since the ``OrderingMixin`` class above satisfies the interface
|
|
and semantics expressed in the ``Ordering`` role, we say the mixin
|
|
performs the role: ::
|
|
|
|
@perform_role(Ordering)
|
|
class OrderingMixin:
|
|
def __ge__(self, other):
|
|
return self > other or self == other
|
|
|
|
def __le__(self, other):
|
|
return self < other or self == other
|
|
|
|
def __ne__(self, other):
|
|
return not self == other
|
|
|
|
# ...and so on
|
|
|
|
Now, any class that uses the mixin will automatically -- that is,
|
|
without further programmer effort -- be tagged as performing the
|
|
``Ordering`` role.
|
|
|
|
The separation of concerns into two distinct, orthogonal systems
|
|
is desirable because it allows us to use each one separately.
|
|
Take, for example, a third-party package providing a
|
|
``RecursiveHash`` role that indicates a container takes its
|
|
contents into account when determining its hash value. Since
|
|
Python's built-in ``tuple`` and ``frozenset`` classes follow this
|
|
semantic, the ``RecursiveHash`` role can be applied to them. ::
|
|
|
|
>>> perform_role(RecursiveHash)(tuple)
|
|
>>> perform_role(RecursiveHash)(frozenset)
|
|
|
|
Now, any code that consumes ``RecursiveHash`` objects will now be
|
|
able to consume tuples and frozensets.
|
|
|
|
|
|
Open Issues
|
|
===========
|
|
|
|
Allowing Instances to Perform Different Roles Than Their Class
|
|
--------------------------------------------------------------
|
|
|
|
Perl 6 allows instances to perform different roles than their class.
|
|
These changes are local to the single instance and do not affect
|
|
other instances of the class. For example: ::
|
|
|
|
my_elf = Elf()
|
|
my_elf.goes_on_quest()
|
|
my_elf.becomes_evil()
|
|
now_performs(my_elf, Thieving) # Only this one elf is a thief
|
|
my_elf.steals(["purses", "candy", "kisses"])
|
|
|
|
In Perl 6, this is done by creating an anonymous class that
|
|
inherits from the instance's original parent and performs the
|
|
additional role(s). This is possible in Python 3, though whether it
|
|
is desirable is still is another matter.
|
|
|
|
Inclusion of this feature would, of course, make it much easier to
|
|
express the works of Charles Dickens in Python: ::
|
|
|
|
>>> from literature import role, BildungsRoman
|
|
>>> from dickens import Urchin, Gentleman
|
|
>>>
|
|
>>> with BildungsRoman() as OliverTwist:
|
|
... mr_brownlow = Gentleman()
|
|
... oliver, artful_dodger = Urchin(), Urchin()
|
|
... now_performs(artful_dodger, [role.Thief, role.Scoundrel])
|
|
...
|
|
... oliver.has_adventures_with(ArtfulDodger)
|
|
... mr_brownlow.adopt_orphan(oliver)
|
|
... now_performs(oliver, role.RichWard)
|
|
|
|
|
|
Requiring Attributes
|
|
--------------------
|
|
|
|
Neal Norwitz has requested the ability to make assertions about
|
|
the presence of attributes using the same mechanism used to require
|
|
methods. Since roles take effect at class definition-time, and
|
|
since the vast majority of attributes are defined at runtime by a
|
|
class's ``__init__()`` method, there doesn't seem to be a good way
|
|
to check for attributes at the same time as methods.
|
|
|
|
It may still be desirable to include non-enforced attributes in the
|
|
role definition, if only for documentation purposes.
|
|
|
|
|
|
Roles of Roles
|
|
--------------
|
|
|
|
Under the proposed semantics, it is possible for roles to
|
|
have roles of their own. ::
|
|
|
|
@perform_role(Y)
|
|
class X(metaclass=Role):
|
|
...
|
|
|
|
While this is possible, it is meaningless, since roles
|
|
are generally not instantiated. There has been some
|
|
off-line discussion about giving meaning to this expression, but so
|
|
far no good ideas have emerged.
|
|
|
|
|
|
class_performs()
|
|
----------------
|
|
|
|
It is currently not possible to ask a class if its instances perform
|
|
a given role. It may be desirable to provide an analogue to
|
|
``performs()`` such that ::
|
|
|
|
>>> isinstance(my_dwarf, Dwarf)
|
|
True
|
|
>>> performs(my_dwarf, Surly)
|
|
True
|
|
>>> performs(Dwarf, Surly)
|
|
False
|
|
>>> class_performs(Dwarf, Surly)
|
|
True
|
|
|
|
|
|
Prettier Dynamic Role Assignment
|
|
--------------------------------
|
|
|
|
An early draft of this PEP included a separate mechanism for
|
|
dynamically assigning a role to a class. This was spelled ::
|
|
|
|
>>> now_perform(Dwarf, GoldMiner)
|
|
|
|
This same functionality already exists by unpacking the syntactic
|
|
sugar provided by decorators: ::
|
|
|
|
>>> perform_role(GoldMiner)(Dwarf)
|
|
|
|
At issue is whether dynamic role assignment is sufficiently important
|
|
to warrant a dedicated spelling.
|
|
|
|
|
|
Syntax Support
|
|
--------------
|
|
|
|
Though the phrasings laid out in this PEP are designed so that the
|
|
roles system could be shipped as a stand-alone package, it may be
|
|
desirable to add special syntax for defining, assigning and
|
|
querying roles. One example might be a role keyword, which would
|
|
translate ::
|
|
|
|
class MyRole(metaclass=Role):
|
|
...
|
|
|
|
into ::
|
|
|
|
role MyRole:
|
|
...
|
|
|
|
Assigning a role could take advantage of the class definition
|
|
arguments proposed in :pep:`3115`: ::
|
|
|
|
class MyClass(performs=MyRole):
|
|
...
|
|
|
|
|
|
Implementation
|
|
==============
|
|
|
|
A reference implementation is forthcoming.
|
|
|
|
|
|
Acknowledgements
|
|
================
|
|
|
|
Thanks to Jeffery Yasskin, Talin and Guido van Rossum for several
|
|
hours of in-person discussion to iron out the differences, overlap
|
|
and finer points of roles and abstract base classes.
|
|
|
|
|
|
References
|
|
==========
|
|
|
|
.. [#aibo]
|
|
http://en.wikipedia.org/wiki/AIBO
|
|
|
|
.. [#roles-examples]
|
|
http://www.perlmonks.org/?node_id=384858
|
|
|
|
.. [#perl6-s12]
|
|
http://dev.perl.org/perl6/doc/design/syn/S12.html
|
|
|
|
.. [#traits-paper]
|
|
http://www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf
|
|
|
|
.. [#proposal]
|
|
https://mail.python.org/pipermail/python-3000/2007-April/007026.html
|
|
|
|
|
|
Copyright
|
|
=========
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
..
|
|
Local Variables:
|
|
mode: indented-text
|
|
indent-tabs-mode: nil
|
|
sentence-end-double-space: t
|
|
fill-column: 70
|
|
coding: utf-8
|
|
End:
|
|
|