[Python-3000] PEP 3133: Introducing Roles (original) (raw)
Benji York benji at benjiyork.com
Mon May 14 23:35:34 CEST 2007
- Previous message: [Python-3000] PEP 3133: Introducing Roles
- Next message: [Python-3000] PEP 3133: Introducing Roles
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Collin Winter wrote:
PEP: 3133 Title: Introducing Roles
Everything included here is included in zope.interface. See in-line comments below for the analogs.
[snip]
Performing Your Role ====================
Static Role Assignment ---------------------- Let's start out by defining
Tree
andDog
classes :: class Tree(Vegetable): def bark(self): return self.isrough()class Dog(Animal): def bark(self): return self.goesruff() 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 simpleisinstance()
test will limit code reuse and/or force any dog-like classes to inherit fromDog
, whether or not that makes sense. Let's see if roles can help. :: @performrole(Doglike) class Dog(Animal): ...
class Dog(Animal): zope.interface.implements(Doglike)
@performrole(Treelike) class Tree(Vegetable): ...
class Tree(Vegetable):
zope.interface.implements(Treelike)
@performrole(SitThere) class Rock(Mineral): ...
class Rock(Mineral):
zope.interface.implements(SitThere)
We use class decorators from PEP 3129 to associate a particular role or roles with a class.
zope.interface.implements should be usable with the PEP 3129 syntax, but I showed the current class decorator syntax throughout.
Client code can now verify that an incoming object performs the
Doglike
role, allowing it to handleWolf
,LaughingHyena
andAibo
[#aibo] instances, too.Roles can be composed via normal inheritance: :: @performrole(Guard, MummysLittleDarling) class GermanShepherd(Dog): def guard(self, theprecious): while True: if intrudernear(theprecious): self.growl() def getpetted(self): self.swallowpride()
class GermanShepherd(Dog): zope.interface.implements(Guard, MummysLittleDarling)
[rest of class definition is the same]
Here,
GermanShepherd
instances perform three roles:Guard
andMummysLittleDarling
are applied directly, whereasDoglike
is inherited fromDog
.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 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
.
>>> zope.interface.classImplements(Robot, Guard)
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(ourrobot, Guard) True
>>> zope.interface.directlyProvides(our_robot, Guard)
What about that one robot over there? ::
>>> performs(thatrobotoverthere, Guard) True
>>> Guard.providedBy(that_robot_over_there)
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
>>> Guard.providedBy(Robot)
False
This is because the
Robot
class is not interchangeable with aRobot
instance.
But if you want to find out if a class creates instances that provide an interface you can::
>>> Guard.implementedBy(Robot)
True
Defining New Roles ================== Empty Roles ----------- Roles are defined like a normal class, but use the
Role
metaclass. :: class Doglike(metaclass=Role): ...
Interfaces are defined like normal classes, but subclass zope.interface.Interface:
class Doglike(zope.interface.Interface):
pass
Metaclasses are used to indicate that
Doglike
is aRole
in the same way 5 is anint
andtuple
is atype
.Composing Roles via Inheritance ------------------------------- Roles may inherit from other roles; this has the effect of composing them. Here, instances of
Dog
will perform both theDoglike
andFourLegs
roles. :: class FourLegs(metaclass=Role): pass class Doglike(FourLegs, Carnivor): pass @performrole(Doglike) class Dog(Mammal): pass
class FourLegs(zope.interface.Interface):
pass
class Doglike(FourLegs, Carnivore):
pass
class Dog(Mammal):
zope.interface.implements(Doglike)
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 abark()
method: :: class Doglike(FourLegs): def bark(self): pass
class Doglike(FourLegs):
def bark():
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. :: @performrole(FourLegs) class Horse(Mammal): def runliketehwind(self) ... This will cause the role system to raise an exception, complaining that you're missing a
runlikethewind()
method. The role system carries out these checks as soon as a class is flagged as performing a given role.
zope.interface does no runtime checking. It has a similar mechanism in zope.interface.verify::
>>> from zope.interface.verify import verifyObject
>>> verifyObject(Guard, our_robot)
True
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. ::@performrole(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.
zope.interface doesn't do anything like this. I suspect *args, and **kws make it impractical to do so (not mentioning whether or not it's a good idea).
The rest of the PEP concerns implementation and other details, so eliding that.
Benji York http://benjiyork.com
- Previous message: [Python-3000] PEP 3133: Introducing Roles
- Next message: [Python-3000] PEP 3133: Introducing Roles
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]