Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 3133 – Introducing Roles

PEP 3133 – Introducing Roles

Author:
Collin Winter <collinwinter at google.com>
Status:
Rejected
Type:
Standards Track
Requires:
3115,3129
Created:
01-May-2007
Python-Version:
3.0
Post-History:
13-May-2007

Table of Contents

Rejection Notice

This PEP has helped pushPEP 3119 towards a saner, more minimalisticapproach. But given the latest version ofPEP 3119 I much preferthat. GvR.

Abstract

Python’s existing object model organizes objects according to theirimplementation. It is often desirable – especially induck typing-based language like Python – to organize objects bythe part they play in a larger system (their intent), rather than byhow they fulfill that part (their implementation). This PEPintroduces the concept of roles, a mechanism for organizingobjects according to their intent rather than their implementation.

Rationale

In the beginning were objects. They allowed programmers to marryfunction and state, and to increase code reusability through conceptslike polymorphism and inheritance, and lo, it was good. There camea time, however, when inheritance and polymorphism weren’t enough.With the invention of both dogs and trees, we were no longer able tobe 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 mechanismorthogonal and complementary to the traditional class/instance system.Whereas classes concern themselves with state and implementation, theroles mechanism deals exclusively with the behaviours embodied in agiven class.

This system was originally called “traits” and implemented for SqueakSmalltalk[4]. It has since been adapted for use inPerl 6[3] where it is called “roles”, and it is primarilyfrom there that the concept is now being interpreted for Python 3.Python 3 will preserve the name “roles”.

In a nutshell: roles tell youwhat an object does, classes tell youhow an object does it.

In this PEP, I will outline a system for Python 3 that will make itpossible to easily determine whether a given object’s understandingof “bark” is tree-like or dog-like. (There might also be moreserious examples.)

A Note on Syntax

A syntax proposals in this PEP are tentative and should beconsidered to be strawmen. The necessary bits that this PEP dependson – namelyPEP 3115’s class definition syntax andPEP 3129’s classdecorators – are still being formalized and may change. Functionnames will, of course, be subject to lengthy bikeshedding debates.

Performing Your Role

Static Role Assignment

Let’s start out by definingTree andDog classes

classTree(Vegetable):defbark(self):returnself.is_rough()classDog(Animal):defbark(self):returnself.goes_ruff()

While both implement abark() method with the same signature,they do wildly different things. We need some way of differentiatingwhat we’re expecting. Relying on inheritance and a simpleisinstance() test will limit code reuse and/or force any dog-likeclasses to inherit fromDog, whether or not that makes sense.Let’s see if roles can help.

@perform_role(Doglike)classDog(Animal):...@perform_role(Treelike)classTree(Vegetable):...@perform_role(SitThere)classRock(Mineral):...

We use class decorators fromPEP 3129 to associate a particular roleor roles with a class. Client code can now verify that an incomingobject performs theDoglike role, allowing it to handleWolf,LaughingHyena andAibo[1] instances, too.

Roles can be composed via normal inheritance:

@perform_role(Guard,MummysLittleDarling)classGermanShepherd(Dog):defguard(self,the_precious):whileTrue:ifintruder_near(the_precious):self.growl()defget_petted(self):self.swallow_pride()

Here,GermanShepherd instances perform three roles:Guard andMummysLittleDarling are applied directly, whereasDoglikeis inherited fromDog.

Assigning Roles at Runtime

Roles can be assigned at runtime, too, by unpacking the syntacticsugar provided by decorators.

Say we import aRobot class from another module, and since weknow thatRobot already implements ourGuard interface,we’d like it to play nicely with guard-related code, too.

>>>perform(Guard)(Robot)

This takes effect immediately and impacts all instances ofRobot.

Asking Questions About Roles

Just because we’ve told our robot army that they’re guards, we’dlike to check in on them occasionally and make sure they’re still attheir task.

>>>performs(our_robot,Guard)True

What about that one robot over there?

>>>performs(that_robot_over_there,Guard)True

Theperforms() function is used to ask if a given objectfulfills a given role. It cannot be used, however, to ask aclass if its instances fulfill a role:

>>>performs(Robot,Guard)False

This is because theRobot class is not interchangeablewith aRobot instance.

Defining New Roles

Empty Roles

Roles are defined like a normal class, but use theRolemetaclass.

classDoglike(metaclass=Role):...

Metaclasses are used to indicate thatDoglike is aRole inthe same way 5 is anint andtuple is atype.

Composing Roles via Inheritance

Roles may inherit from other roles; this has the effect of composingthem. Here, instances ofDog will perform both theDoglike andFourLegs roles.

classFourLegs(metaclass=Role):passclassDoglike(FourLegs,Carnivor):pass@perform_role(Doglike)classDog(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 theDoglike role define abark() method:

classDoglike(FourLegs):defbark(self):pass

No decorators are required to flag the method as “abstract”, and themethod will never be called, meaning whatever code it contains (if any)is irrelevant. Roles provideonly abstract methods; concretedefault implementations are left to other, better-suited mechanismslike mixins.

Once you have defined a role, and a class has claimed to perform thatrole, it is essential that that claim be verified. Here, theprogrammer has misspelled one of the methods required by the role.

@perform_role(FourLegs)classHorse(Mammal):defrun_like_teh_wind(self)...

This will cause the role system to raise an exception, complainingthat you’re missing arun_like_the_wind() method. The rolesystem carries out these checks as soon as a class is flagged asperforming a given role.

Concrete methods are required to match exactly the signature demandedby the role. Here, we’ve attempted to fulfill our role by defining aconcrete version ofbark(), but we’ve missed the mark a bit.

@perform_role(Doglike)classCoyote(Mammal):defbark(self,target=moon):pass

This method’s signature doesn’t match exactly with what theDoglike role was expecting, so the role system will throw a bitof a tantrum.

Mechanism

The following are strawman proposals for how roles might be expressedin Python. The examples here are phrased in a way that the rolesmechanism may be implemented without changing the Python interpreter.(Examples adapted from an article on Perl 6 roles by Curtis Poe[2].)

  1. Static class role assignment
    @perform_role(Thieving)classElf(Character):...

    perform_role() accepts multiple arguments, such that this isalso legal:

    @perform_role(Thieving,Spying,Archer)classElf(Character):...

    TheElf class now performs both theThieving,Spying,andArcher roles.

  2. Querying instances
    ifperforms(my_elf,Thieving):...

    The second argument toperforms() may also be anything with a__contains__() method, meaning the following is legal:

    ifperforms(my_elf,set([Thieving,Spying,BoyScout])):...

    Likeisinstance(), the object needs only to perform a singlerole out of the set in order for the expression to be true.

Relationship to Abstract Base Classes

Early drafts of this PEP[5] envisioned roles as competingwith the abstract base classes proposed inPEP 3119. After furtherdiscussion and deliberation, a compromise and a delegation ofresponsibilities and use-cases has been worked out as follows:

  • Roles provide a way of indicating an object’s semantics and abstractcapabilities. A role may define abstract methods, but only as away of delineating an interface through which a particular set ofsemantics are accessed. AnOrdering role might require thatsome set of ordering operators be defined.
    classOrdering(metaclass=Role):def__ge__(self,other):passdef__le__(self,other):passdef__ne__(self,other):pass# ...and so on

    In this way, we’re able to indicate an object’s role or functionwithin a larger system without constraining or concerning ourselveswith a particular implementation.

  • Abstract base classes, by contrast, are a way of reusing common,discrete units of implementation. For example, one might define anOrderingMixin that implements several ordering operators interms of other operators.
    classOrderingMixin:def__ge__(self,other):returnself>otherorself==otherdef__le__(self,other):returnself<otherorself==otherdef__ne__(self,other):returnnotself==other# ...and so on

    Using this abstract base class - more properly, a concretemixin - allows a programmer to define a limited set of operatorsand let the mixin in effect “derive” the others.

By combining these two orthogonal systems, we’re able to botha) provide functionality, and b) alert consumer systems to thepresence and availability of this functionality. For example,since theOrderingMixin class above satisfies the interfaceand semantics expressed in theOrdering role, we say the mixinperforms the role:

@perform_role(Ordering)classOrderingMixin:def__ge__(self,other):returnself>otherorself==otherdef__le__(self,other):returnself<otherorself==otherdef__ne__(self,other):returnnotself==other# ...and so on

Now, any class that uses the mixin will automatically – that is,without further programmer effort – be tagged as performing theOrdering role.

The separation of concerns into two distinct, orthogonal systemsis desirable because it allows us to use each one separately.Take, for example, a third-party package providing aRecursiveHash role that indicates a container takes itscontents into account when determining its hash value. SincePython’s built-intuple andfrozenset classes follow thissemantic, theRecursiveHash role can be applied to them.

>>>perform_role(RecursiveHash)(tuple)>>>perform_role(RecursiveHash)(frozenset)

Now, any code that consumesRecursiveHash objects will now beable 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 affectother 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 thiefmy_elf.steals(["purses","candy","kisses"])

In Perl 6, this is done by creating an anonymous class thatinherits from the instance’s original parent and performs theadditional role(s). This is possible in Python 3, though whether itis desirable is still is another matter.

Inclusion of this feature would, of course, make it much easier toexpress the works of Charles Dickens in Python:

>>>fromliteratureimportrole,BildungsRoman>>>fromdickensimportUrchin,Gentleman>>>>>>withBildungsRoman()asOliverTwist:...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 aboutthe presence of attributes using the same mechanism used to requiremethods. Since roles take effect at class definition-time, andsince the vast majority of attributes are defined at runtime by aclass’s__init__() method, there doesn’t seem to be a good wayto check for attributes at the same time as methods.

It may still be desirable to include non-enforced attributes in therole definition, if only for documentation purposes.

Roles of Roles

Under the proposed semantics, it is possible for roles tohave roles of their own.

@perform_role(Y)classX(metaclass=Role):...

While this is possible, it is meaningless, since rolesare generally not instantiated. There has been someoff-line discussion about giving meaning to this expression, but sofar no good ideas have emerged.

class_performs()

It is currently not possible to ask a class if its instances performa given role. It may be desirable to provide an analogue toperforms() 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 fordynamically assigning a role to a class. This was spelled

>>>now_perform(Dwarf,GoldMiner)

This same functionality already exists by unpacking the syntacticsugar provided by decorators:

>>>perform_role(GoldMiner)(Dwarf)

At issue is whether dynamic role assignment is sufficiently importantto warrant a dedicated spelling.

Syntax Support

Though the phrasings laid out in this PEP are designed so that theroles system could be shipped as a stand-alone package, it may bedesirable to add special syntax for defining, assigning andquerying roles. One example might be a role keyword, which wouldtranslate

classMyRole(metaclass=Role):...

into

roleMyRole:...

Assigning a role could take advantage of the class definitionarguments proposed inPEP 3115:

classMyClass(performs=MyRole):...

Implementation

A reference implementation is forthcoming.

Acknowledgements

Thanks to Jeffery Yasskin, Talin and Guido van Rossum for severalhours of in-person discussion to iron out the differences, overlapand finer points of roles and abstract base classes.

References

[1]
http://en.wikipedia.org/wiki/AIBO
[2]
http://www.perlmonks.org/?node_id=384858
[3]
http://dev.perl.org/perl6/doc/design/syn/S12.html
[4]
http://www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf
[5]
https://mail.python.org/pipermail/python-3000/2007-April/007026.html

Copyright

This document has been placed in the public domain.


Source:https://github.com/python/peps/blob/main/peps/pep-3133.rst

Last modified:2025-02-01 08:59:27 GMT


[8]ページ先頭

©2009-2026 Movatter.jp