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`` 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): > ... class Dog(Animal): zope.interface.implements(Doglike) > @perform_role(Treelike) > class Tree(Vegetable): > ... class Tree(Vegetable): zope.interface.implements(Treelike) > @perform_role(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 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() class GermanShepherd(Dog): zope.interface.implements(Guard, MummysLittleDarling) [rest of class definition is the same] > 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``. >>> 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(our_robot, Guard) > True >>> zope.interface.directlyProvides(our_robot, Guard) > What about that one robot over there? :: > > >>> performs(that_robot_over_there, 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 a ``Robot`` 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 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 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 a ``bark()`` 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. :: > > @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. 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. :: > > @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. 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 _______________________________________________ Python-3000 mailing list Python-3000@python.org http://mail.python.org/mailman/listinfo/python-3000 Unsubscribe: http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com