summaryrefslogtreecommitdiff
path: root/mtraits
diff options
context:
space:
mode:
authormichele.simionato <devnull@localhost>2008-08-13 05:37:38 +0000
committermichele.simionato <devnull@localhost>2008-08-13 05:37:38 +0000
commitc87ec2d3a2a83d8e4c93e52d4834d5a22b3369b1 (patch)
tree6f73d0bc7a31d671a0ca212bfb10ef0c3f139a20 /mtraits
parent587eddf6049de93a1288f10261011d685968bba0 (diff)
downloadmicheles-c87ec2d3a2a83d8e4c93e52d4834d5a22b3369b1.tar.gz
First version of mtraits committed
Diffstat (limited to 'mtraits')
-rw-r--r--mtraits/doc.py594
-rw-r--r--mtraits/mtrait.py298
2 files changed, 892 insertions, 0 deletions
diff --git a/mtraits/doc.py b/mtraits/doc.py
new file mode 100644
index 0000000..0dcaa1b
--- /dev/null
+++ b/mtraits/doc.py
@@ -0,0 +1,594 @@
+r"""An implementation of traits in Python
+==================================================================
+
+:Author: Michele Simionato
+:Date: XXX
+:Version: XXX
+:Licence: BSD
+
+Motivation
+------------------------------------------------
+
+Multiple inheritance is a hotly debated topic.
+The supporters of multiple inheritance
+claim that it makes code shorter and easier
+to read, whereas the opposers claim that is makes
+code more coupled and more difficult to understand. I have
+spent some time in the past facing the intricacies of `multiple
+inheritance in Python`_ ; moreover I have worked with frameworks making
+large use of multiple inheritance (I mean Zope 2) and nowadays I am in
+the number of the people who oppose multiple inheritance. Therefore I
+am interested in alternatives. In recent years, the approach
+of traits_ has gained some traction in a few languages, so I have
+decided to write a library to implement traits in Python, for
+experimentation purposes. The library is meant for framework builders,
+people who are thinking about writing a framework based on multiple
+inheritance (typically via the common mixin approach) but
+are not convinced that this is the best solution and would like to try
+an alternative. This library is also for the authors of existing frameworks
+based on mixins that are unsatisfied and would like to convert their
+framework to traits.
+
+Are traits a better solution than multiple inheritance and mixins?
+In theory I think so, otherwise I would not have write this library, but
+in practice (as always) things may be different. It may well be that in
+practice using traits or using mixins does not make a big difference
+and that changing paradigm is not worth the effort; or the opposite may be
+true. The only way to know is to try, to build software based on traits
+and to see how it scale *in the large* (in the small, more or less any
+approach works fine; it is only by programming in the large that you
+can see the differences).
+This is the reason why I am releasing
+this library with a liberal licence, so that people can try it out and
+see how it works. The library is meant to play well with pre-existing
+frameworks. As an example, I will show here how you could rewrite
+Tkinter to use traits instead of mixins. Of course, I am not advocating
+rewriting Tkinter: it would be silly at this point of its history and
+pointless; but it may have sense (or not) to rewrite
+your own framework using traits, perhaps a framework which is used
+in house but has not been released yet.
+
+Finally, let me notice that I am not the only one to have
+implemented traits for Python; after finishing my implementation
+I made a research and discovered a few implementations. The
+biggest effort seems to be `Enthought Traits`_ which I did
+not try, since the tutorial required Microsoft Windows,
+wxPython and enthought.traits.ui.wx to run. My implementation
+does not require anything, is short and I am committed
+to keep it short even in the future, according to
+the principle of `less is more`_.
+
+.. _less is more: http://www.artima.com/weblogs/viewpost.jsp?thread=236286
+.. https://svn.enthought.com/enthought/wiki/Traits
+
+
+.. _multiple inheritance in Python: MRO
+
+What are traits?
+------------------------------------------------------------
+
+The word *traits* has many meanings; I will refer to it in the sense of
+the paper `Traits - Composable Units of Behavior`_ which
+implements them in Squeak/Smalltalk. The paper appeared in 2003, but most
+of the ideas underlying traits have been floating around for at least
+30 years. There is also a trait implementation for `PLT Scheme`_ which is
+close in spirit (if not in practice) to what I am advocating here.
+The library
+you are reading about is by no means intended as a porting of the Smalltalk
+library: I am just stealing some of the ideas from that paper to implement
+a Pythonic alternative to mixins which, for lack of a better name, I have
+decided to call traits, but I feel no obligation whatsoever to be consistent
+with the Smalltalk library. In doing so, I am following a long tradition,
+since a lot of languages use the name "traits" to mean something completely
+different from the Smalltalk meaning. For instance the languages
+Fortress and Scala use the name "trait" but they mean by it what is
+usually called a "mixin".
+For me a trait is a bunch of methods and attributes will the following
+properties:
+
+1. the methods/attributes go logically together;
+2. traits can be used to enhance independent classes;
+3. the ordering is not important, so enhancing a class first with trait T1 and
+ then with trait T2 or viceversa is the same;
+4. if traits T1 and T2 have names in common, enhancing a class both
+ with T1 and T2 raises an error unless unless you specify *explicitely*
+ how the overriding of the common names has to be made;
+5. a class can be seen both as a composition of traits and as an homogeneous
+ entity.
+
+Properties 3 ,4, 5 are the distinguishing properties of traits with respect
+to multiple inheritance and mixins.
+In particular, because of 3 and 4, all the complications with the Method
+Resolution Order disappear and the overriding is never implicit.
+Property 5 has to be intended in the sense that a trait implementation
+must provide introspection facilities to make seemless the transition
+between classes viewed as atomic entities and as composed entities.
+
+A hands-on example
+------------------------------------------------------
+
+For pedagogical purpose I will show here how you could rewrite a
+Tkinter class to use traits instead of mixins. Consider the
+``Tkinter.Widget`` class, which is derived by the base class
+``BaseWidget`` and the mixin classes
+``Tkinter.Grid``, ``Tkinter.Pack`` and ``Tkinter.Place``: we want to
+rewrite it to use traits instead of mixins. The ``mtrait`` module
+provides a class decorator named ``include`` to do exactly that; it is
+enough to replace the multiple inheritance syntax
+
+.. code-block:: python
+
+ class Widget(BaseWidget, Grid, Pack, Place):
+ pass
+
+with a class decorator syntax:
+
+.. code-block:: python
+
+ @include(Pack, Place, Grid) # this syntax requires Python 2.6+
+ class Widget(BaseWidget):
+ pass
+
+For compatibility with old versions of Python (before Python 2.6)
+the ``include`` class decorator provides some magic to work even
+inside classes:
+
+.. code-block:: python
+
+ class Widget(BaseWidget): # this syntax works in Python 2.2+
+ include(Pack, Place, Grid)
+
+The preferred way however is to use the ``@`` notation, if available.
+I said that the conversion from mixins to traits was easy: but actually
+I lied since if you try to execute the code I just wrote you will
+get an ``OverridingError``:
+
+>>> from Tkinter import *
+>>> class Widget(BaseWidget):
+... include(Pack, Place, Grid)
+Traceback (most recent call last):
+ ...
+OverridingError: Pack.{info, config, configure, slaves, forget} overriding names in Place
+
+The reason for the error is clear: both ``Pack`` and ``Place`` provide
+methods called ``{info, config, configure, slaves, forget}``
+and the traits implementation cannot figure out
+which ones to use. This is a feature, since it forces you to be
+explicit. In this case, if we want to be consistent with
+multiple inheritance rules, clearly we want the methods coming from
+the first class (i.e. ``Pack``) to have precedence and we must say so:
+
+$$TOSWidget
+
+Notice that we had to specify the ``propagate`` method too, since
+it is common between ``Pack`` and ``Grid``.
+
+You can check that the TOSWidget class works, for instance by defining a
+label widget as follows (remember that ``TOSWidget`` inherits its signature
+from ``BaseWidget``):
+
+>>> label = TOSWidget(master=None, widgetName='label', cnf=dict(text="hello"))
+
+You may visualize the widget by calling the ``.pack`` method:
+
+>>> label.pack()
+
+This should open a small window with the message "hello" inside it.
+As you see, in a lot of cases replacing mixins with traits is fairly
+straightforward: however, in some cases you can run into problems. In
+particular if you have a cooperative multiple inheritance hierarchy
+(i.e. you use ``super`` in the mixin classes) you will have to rewrite
+your code not to use ``super`` (there are no problem if you use
+``super`` ony in methods of the base class). This is on purpose:
+traits are not a replacement for
+cooperative multiple inheritance, you should uses traits if you think
+that cooperative multiple inheritance is a bad idea and you are
+willing to remove it from your code. Old frameworks written before the
+introduction of ``super`` (such as Tkinter and Zope 2) are easier to
+convert in this respect, since they are non-cooperative.
+The ``mtrait`` module is
+intended for framework writers, so it assumes you can change the source
+code of your framework if you want; of course, it aims to
+reduce the needed changes as much as possible.
+
+How does it work
+---------------------------------------------------------
+
+The implementation of traits provided is an ``mtrait`` is short and
+relatively simple but if makes use of sophisticated techniques. The
+class decorator ``include`` takes a set of mixin classes in input,
+determines the right metaclass to use and returns a class in output;
+the metaclass enhances the class in output by adding to it a special
+attribute ``__traits__``, a custom ``__getattr__`` method and a custom
+``__getstate__`` method. The
+``__traits__`` attribute is a ``TraitContainer`` instance akin to a
+dictionary of trait objects (one for each mixin class); the
+``__getattr__`` method allows automatic dispatching to them;
+the ``__getstate__`` method is needed to preserve pickleability of
+the instances (assuming the original class before decoration was
+pickleable, of course)>
+
+Now let me describe a trait object. The ``mtrait`` module provides a
+``Trait`` class which you can use directly, instead of relying on the
+magic of ``include``. You can see a trait as a wrapper around an
+inner object. Usually you want to convert a mixin class into a trait; you
+can do it as follows:
+
+ ``trait = Trait(Mixin, Mixin.__name__)``
+
+It is also common to convert a module into a trait:
+
+ ``trait = Trait(module, module.__name__)``
+
+Trait objects are attribute descriptors, i.e. they can used as class
+attributes; when a trait is called from a class or an instance, we
+say that it is bound to the class or the instance.
+
+Here is an example of usage:
+
+>>> pack = Trait(Pack, 'Pack') # define an unbound trait
+>>> pack
+<Trait Pack {config, configure, forget, info, pack, pack_configure, pack_forget, pack_info, pack_propagate, pack_slaves, propagate, slaves} >
+
+>>> class MyWidget(BaseWidget): # add the trait to a class
+... p = pack
+
+Here is an example of trait a bound to a class:
+
+>>> MyWidget.p #doctest: +ELLIPSIS
+<Trait Pack {config, configure, forget, info, pack, pack_configure, pack_forget, pack_info, pack_propagate, pack_slaves, propagate, slaves} bound to <class __main__.MyWidget at 0x...>>
+
+Here is an example of a trait bound to an instance:
+
+>>> lbl = MyWidget(None, 'label', dict(text='hello'))
+>>> lbl.p #doctest: +ELLIPSIS
+<Trait Pack {config, configure, forget, info, pack, pack_configure, pack_forget, pack_info, pack_propagate, pack_slaves, propagate, slaves} bound to <__main__.MyWidget instance at 0x...>>
+
+You can call the methods of a trait as follows:
+
+>>> lbl.p.pack()
+>>> lbl.p.info().keys()
+['side', 'ipady', 'ipadx', 'in', 'pady', 'padx', 'anchor', 'expand', 'fill']
+
+The descriptor magic works in such a way that the instance methods get
+as first argument the instance to which the trait is bound; on the other
+hand, for traits bound to classes, you get an unbound method associated
+to the given class:
+
+>>> MyWidget.p.pack
+<unbound method MyWidget.pack_configure>
+
+Different traits or mixins can be composed into a ``TraitContainer``:
+for instance, you could define
+
+>>> tc = TraitContainer.from_([Pack, Place, Grid])
+>>> tc
+<Traits Grid, Place, Pack >
+
+The ``TraitContainer`` object has attributes ``Pack``, ``Place`` and ``Grid``
+which are traits corresponding to the mixin classes; for instance
+
+>>> tc.Pack
+<Trait Pack {config, configure, forget, info, pack, pack_configure, pack_forget, pack_info, pack_propagate, pack_slaves, propagate, slaves} >
+
+The magic of ``include``
+----------------------------------------------------------------------
+
+Even if you could build your trait objects yourself as explained in
+the previous paragraph, and you could implement the dispatch to traits
+by hand, usually it is easier if you just rely on the magic of
+the ``include`` class decorator, which is doing a lot of work on your
+behalf. In particular, internally the class decorator
+builds a trait object for each mixin and store all of them into the
+``__traits__`` attribute;
+moreover it adds a suitable ``__getattr__``
+method to the decorated class.
+Finally the class decorator adds a suitable ``__getstate_``
+method so that your objects stay pickleable if they were originally
+pickleable (adding ``__getattr__`` without adding ``__getstate__`` would
+break pickle). Notice that the original class should not define
+``__getattr__`` or ``__getstate__`` of its own, otherwise an
+``OverridingError`` is raised. This is intended to prevent
+accidental overriding; if you really know what you are doing,
+you can always replace the default implementation with your own
+after class creation.
+If you want to understand the details, you are welcome
+to give a look at the implementation, which is pretty small.
+
+The goal of the ``mtrait`` module it to modify the standard
+Python object model, turning it into a Trait Object System (TOS for short):
+TOS classes (i.e. instances of ``MetaTOS``) behave differently from regular
+classes. In particular TOS classes must not support multiple inheritance
+and must forbid your from defining your own ``__getattr__`` and
+``__getstate__`` methods; these
+two fundamental properties must be preserved under inheritance (i.e. the
+son of a TOS class must be a TOS class) and therefore the implementation
+requires necessarily metaclasses.
+
+People who do not like magic may do everything explicitely, by using
+the ``__metaclass__`` hook and by specifying the mixins with ``__mixins__``:
+
+$$TOSWidget2
+
+``include`` does more than that, since it
+takes care automatically of possible metaclass conflicts. In the case of
+Tkinter there is no such problem, since ``BaseWidget`` is just a traditional
+old-style class and the metaclass for ``TOSWidget2`` is just ``TOSMeta``:
+
+>>> type(TOSWidget)
+<class 'mtrait.TOSMeta'>
+
+However, in general you may need to build your Trait Based Framework
+on top of pre-existing classes with a nontrivial metaclass, for
+instance Zope classes; in that case having a class decorator smart
+enough to figure out the right metaclass to use is a convenient. Here
+is an example using Plone classes:
+
+>> from OSF.Folder import Folder
+
+
+when you call
+``super(<class>, <subclass>).method``
+>> from zope.plone import BaseContent
+>> class
+
+>> type(X)
+ExtensionClassMetaTOS
+
+
+<type 'ExtensionClass.ExtensionClass'>
+
+Why multiple inheritance is forbidden
+----------------------------------------------------------
+
+As I said, the mother metaclass of the Trait Object System ``MetaTOS`` forbids
+multiple inheritance and if you try to multiple inherit from a TOS
+class and another class you will get a ``TypeError``:
+
+>>> class C:pass
+...
+>>> class Widget2(TOSWidget, C): #doctest: +ELLIPSIS
+... pass
+...
+Traceback (most recent call last):
+ ...
+TypeError: Multiple inheritance of bases (<class '__main__.TOSWidget'>, <class __main__.C at 0x...>) is forbidden for TOS classes
+
+This behavior is intentional: with this restriction you can simulate
+an ideal world in which Python did not support multiple
+inheritance. Suppose you want to claim that supporting multiple
+inheritance was a mistake and that Python would have been better
+without it (which is the position I tend to have nowadays, but see the
+note below): how can you prove that claim? Simply by writing code that
+does not use multiple inheritance and it is clearer and more
+mantainable that code using multiple inheritance. I am releasing this
+trait implementation hoping you will help me to prove (or possibly
+disprove) the point. You may see traits as a restricted form of
+multiple inheritance without method resolution order and without name
+clashes which does not pollute the namespace of the original class; it
+does not have cooperative methods either. Still I think these are
+acceptable restrictions since they give back in return many
+advantages:
+
+1. many people use multiple inheritance incorrectly, confusing the ``is-a``
+relation with the ``has-a`` relation; with traits, there is no confusion,
+since the features coming
+by the base class correspond to ``is-a``, whereas the features coming
+from the traits correspond to ``has-a``.
+
+2. ``super`` becomes trivial, since each class has a single superclass.
+
+Are there disadvantages of my proposed trait implementation with
+respect to multiple inheritance? I don't think there are serious
+disadvantages, since you can always work around them. For instance, a nice
+property of inheritance is that if you have a class ``C`` inheriting from
+class ``M`` and you change a method in ``M`` at runtime, after ``C`` has been
+created and instantiated, automagically all instances of ``C`` gets the
+new version of the method (this is pretty useful for debugging
+purposes). This feature is not lost: the trait implementation
+is fully dynamic and if you change the mixin the instances will
+be changed too.
+
+*Note*: even if I think that a language would be better off without
+full multiple inheritance with cooperative methods, that does not mean
+that I am against interfaces. I think a class should be able to
+implement multiple interfaces at the same time, but this has nothing
+to do with multiple inheritance. In Python instead (starting from
+Python 2.6) multiple inheritance is abused to simulate interface
+requirements so that if you want to implement many interfaces at the
+same time you are suggested to inherit from many abstract base classes
+at the same time. In an ideal language without multiple inheritance
+you could just add an ``__interfaces__`` attribute to classes. In an
+ideal language interfaces would be abstract objects (a little more
+than a list of names) whereas in Python they are concrete classes and
+that explain why you inherit from them.
+
+Introspection
+------------------------------------------------------------------
+
+As I said, a trait implementation must provide introspection facilities.
+To this aim, ``mtrait`` provides a ``get_traits(obj)`` function returning
+a ``TraitContainer`` if ``obj`` has a ``__traits__`` attribute or the
+empty tuple otherwise. For instance
+
+>>> #get_traits(label) is label.__traits__
+
+>>> #get_traits(cls=TOSWidget) is TOSWidget.__traits__
+
+>>> TOSWidget.__traits__
+<Traits Grid, Place, Pack bound to <class '__main__.TOSWidget'>>
+
+>>> label.__traits__ #doctest: +ELLIPSIS
+<Traits Grid, Place, Pack bound to <__main__.TOSWidget object at 0x...>>
+
+Future work
+--------------------------------------------------------
+
+The Smalltalk implementation of traits provides method renaming
+out of the box. The Python implementation has no facilities in
+this sense. In the future I may decide to give some support for
+renaming, or I may not. At the present I am not sure renaming is a good
+idea: after all, it is pretty easy to create a new trait with different
+names. For instance I can rename two read/write methods
+
+$$ReadWriteMixin
+
+as dump/restore methods quite easily:
+
+$$DumpRestoreMixin
+
+Also, in the future I may decide to add some kind of adaptation mechanism
+or I may not: after all the primary goal of this implementation is semplicity
+and I don't want to clutter it with too many features.
+
+I am very open to feedback and criticism: I am releasing this module with
+the hope that it will be used in real life situations to gather experience
+with the traits concept. Clearly I am not proposing that Python should
+remove multiple inheritance in favor of traits: that will never happen.
+I am just looking for a few adventurous volunteers wanting to experiment
+with traits; if the experiment goes well, and people start using (multiple)
+inheritance less than they do now, I will be happy. The point I am
+trying to make is that Python is not Java: whereas in Java you have
+very little alternatives to inheritance, in Python you have lots of
+alternatives involving composition,
+so you should not keep programming Java in Python.
+
+.. _traits: http://www.iam.unibe.ch/~scg/Research/Traits/
+.. _Traits - Composable Units of Behavior: http://www.iam.unibe.ch/%7Escg/Archive/Papers/Scha03aTraits.pdf
+.. _PLT Scheme: http://www.cs.utah.edu/plt/publications/aplas06-fff.pdf
+
+"""
+import cPickle as pickle
+from mtrait import *
+from Tkinter import *
+
+class TOSWidget(BaseWidget):
+ include(Pack, Place, Grid)
+ info = Pack.info.im_func
+ config = Pack.config.im_func
+ configure = Pack.configure.im_func
+ slaves = Pack.slaves.im_func
+ forget = Pack.forget.im_func
+ propagate = Pack.propagate.im_func
+
+class TOSWidget2(BaseWidget):
+ __mixins__ = Pack, Place, Grid
+ __metaclass__ = TOSMeta
+ info = Pack.info.im_func
+ config = Pack.config.im_func
+ configure = Pack.configure.im_func
+ slaves = Pack.slaves.im_func
+ forget = Pack.forget.im_func
+ propagate = Pack.propagate.im_func
+
+label = TOSWidget(master=None, widgetName='label', cnf=dict(text="hello"))
+
+pickle.dumps(label)
+
+class HTTP(object):
+ def GET(self):
+ print 'calling HTTP.GET from %s' % self
+ def POST(self):
+ print 'calling HTTP.POST from %s' % self
+ @classmethod
+ def cm(cls):
+ print 'calling HTTP.cm from %s' % cls
+
+class FTP(object):
+ def SEND(self):
+ print 'calling FTP.SEND from %s' % self
+ def RECV(self):
+ print 'calling FTP.RECV from %s' % self
+ @staticmethod
+ def sm():
+ print 'calling staticmethod'
+
+class Mixin(object):
+ def helper(self):
+ return 1
+ def method(self):
+ return self.helper() + 1
+
+class Meta(type):
+ pass
+
+def test_getattr():
+ class C:
+ __metaclass__ = TOSMeta
+ def __getattr__(self, name):
+ pass
+
+def test_multi_include():
+ class B(object):
+ __metaclass__ = Meta
+ include(FTP)
+ class C(B):
+ include(HTTP)
+ print type(C)
+
+def test_Trait_pickle():
+ t = Trait(Mixin, Mixin.__name__)
+ pickle.loads(pickle.dumps(t))
+
+class C:
+ "An example not using include"
+ __metaclass__ = TOSMeta
+ __mixins__ = HTTP, FTP, Mixin
+
+class ReadWriteMixin(object):
+ def read():
+ pass
+ def write():
+ pass
+
+class DumpRestoreMixin(object):
+ dump = ReadWriteMixin.write.im_func
+ restore = ReadWriteMixin.read.im_func
+
+def setup():
+ global c
+ c = C()
+ print c
+
+class A(object):
+ def save(self):
+ print 'A.save'
+
+class B(A):
+ def save(self):
+ print 'B.save'
+ super(B, self).save()
+
+class C(A):
+ def save(self):
+ print 'C.save'
+ Super(self).save()
+
+class D(B, C):
+ def save(self):
+ print 'D.save'
+ super(D, self).save()
+
+C.C_save = C.save.im_func
+
+class E(B):
+ include(C)
+ def save(self):
+ print 'D.save'
+ print 'C.save'
+ B.save(self)
+
+check_overridden([B, C], set(), raise_="warning")
+
+d = D()
+e = E()
+e.save()
+
+if __name__ == '__main__':
+ import doctest; doctest.testmod()
+ try:
+ import nose
+ except ImportError:
+ pass
+ else:
+ nose.runmodule()
diff --git a/mtraits/mtrait.py b/mtraits/mtrait.py
new file mode 100644
index 0000000..c70d4b1
--- /dev/null
+++ b/mtraits/mtrait.py
@@ -0,0 +1,298 @@
+import re, sys, inspect, types, warnings
+
+class OverridingError(NameError):
+ pass
+
+class OverridingWarning(Warning):
+ pass
+
+def getnames(obj):
+ "Get the nonspecial attributes in obj"
+ return set(name for name in dir(obj)
+ if not (name.startswith('__') and name.endswith('__')))
+
+def find_common_names(mixins):
+ "Perform n*(n-1)/2 namespace overlapping checks on a set of n mixins"
+ n = len(mixins)
+ if n <= 1: return
+ names = [set(getnames(obj)) for obj in mixins]
+ for i in range(0, n):
+ for j in range(i+1, n):
+ ci, cj = mixins[i], mixins[j]
+ common = names[i] & names[j]
+ if common:
+ yield common, ci, cj
+
+def check_overridden(mixins, exclude, raise_='error'):
+ "Raise an OverridingError for common names not in the exclude set"
+ for common, c1, c2 in find_common_names(mixins):
+ overridden = ', '.join(common - exclude)
+ if ',' in overridden: # for better display of the names
+ overridden = '{%s}' % overridden
+ if overridden:
+ msg = '%s.%s overriding names in %s' % (
+ c1.__name__, overridden, c2.__name__)
+ if raise_ == 'error':
+ raise OverridingError(msg)
+ elif raise_ == 'warning':
+ warnings.warn(msg, OverridingWarning, stacklevel=2)
+
+class Trait(object):
+ """
+ Class for mixin dispatchers. Mixin dispatchers are instantiated through the
+ .to classmethod and possess a __mixin__ attribute. They are descriptors
+ acting as proxies to an inner dictionary.
+ There are bound and unbound dispatchers, just as there are methods and
+ functions. A bound dispatcher is a dispatcher instance bound to a class or
+ an object, whereas Trait(mixin(), name) returns an unbound dispatcher.
+ """
+
+ def __init__(self, inner, name, obj=None, objcls=None):
+ if isinstance(inner, self.__class__): # already a trait
+ self.__inner = inner._Trait__inner
+ else:
+ self.__inner = inner
+ self.__name__ = name
+ self.__obj = obj
+ self.__objcls = objcls
+
+ def __get__(self, obj, objcls=None):
+ "Return a bound dispatcher"
+ return self.__class__(self.__inner, self.__name__, obj, objcls)
+
+ def __getattr__(self, name):
+ "obj.dispatcher.method(args) returns mixin.method(obj, args)"
+ value = getattr(self.__inner, name)
+ try: # if (unbound) method, go back to the function
+ value = value.im_func
+ except AttributeError:
+ pass
+ obj, objcls = self.__obj, self.__objcls
+ if obj or objcls:
+ try: # return the bound descriptor
+ return value.__get__(obj, objcls)
+ except AttributeError: # not a descriptor
+ pass
+ return value
+
+ def __iter__(self):
+ return iter(getnames(self.__inner))
+
+ def __repr__(self):
+ names = ', '.join(sorted(self))
+ bound_obj = self.__obj or self.__objcls
+ if bound_obj:
+ msg = 'bound to %r' % bound_obj
+ else:
+ msg = ''
+ return '<%s %s {%s} %s>' % (
+ self.__class__.__name__, self.__name__, names, msg)
+
+ ## we could live with nonpickeable traits, but still ...
+
+ def __getstate__(self):
+ return self.__inner
+
+ def __setstate__(self, inner):
+ self.__init__(inner, inner.__name__)
+
+def __getattr__(obj, name):
+ "__getattr__ added by TOSMeta"
+ objcls = obj.__class__
+ for trait in obj.__traits__:
+ bt = trait.__get__(obj, objcls) # bound trait
+ try:
+ return getattr(bt, name)
+ except AttributeError:
+ continue
+ raise AttributeError(name)
+
+class TOSMeta(type):
+ """The metaclass for the Trait Object System. It is intended to be
+ called only indirectly via ``include``. It provides the following features
+ to its instances:
+ 1. forbids multiple inheritance
+ 2. checks for accidental overriding of __getattr__ and __getstate__
+ 3. provides the class with the correct base __getattr__ and __getstate__
+ 4. provides the basic empty __traits__ attribute
+ """
+
+ def __new__(mcl, name, bases, dic):
+ if len(bases) > 1:
+ raise TypeError(
+ 'Multiple inheritance of bases %s is forbidden for TOS classes'
+ % str(bases))
+ elif oldstyle(bases): # ensure new-style class
+ bases += (object, )
+ for meth in ('__getattr__', '__getstate__'):
+ if meth in dic:
+ raise OverridingError('class %s defines %s' % (name, meth))
+ traits = getattr(bases[0], '__traits__', ())
+ if not traits: # the first time
+ dic['__getattr__'] = dic.get('__getattr__', __getattr__)
+ dic['__getstate__'] = dic.get('__getstate__', vars)
+ basemixins = ()
+ else:
+ basemixins = tuple(t._Trait__inner for t in traits)
+ mixins = dic.get('__mixins__', ())
+ if mixins:
+ commonset = set(basemixins) & set(mixins)
+ if commonset:
+ raise TypeError("Redundant mixins %s!", commonset)
+ mixins = basemixins + mixins
+ check_overridden(mixins, exclude=set(dic))
+ dic['__traits__'] = TraitContainer.from_(mixins)
+ return super(TOSMeta, mcl).__new__(mcl, name, bases, dic)
+
+ def __getattr__(cls, name):
+ for trait in cls.__traits__:
+ bt = trait.__get__(None, cls) # class bound trait
+ try:
+ return getattr(bt, name)
+ except AttributeError:
+ continue
+ raise AttributeError(name)
+
+cache = {type: TOSMeta, types.ClassType: TOSMeta} # metaclass cache
+
+def getrightmeta(metacls, mcl):
+ "Determine the right metaclass to use between metacls and mcl (=TOSMeta)"
+ if issubclass(metacls, mcl): # has TOSMeta functionality
+ return metacls
+ else: # add TOSMeta functionality
+ try:
+ return cache[metacls]
+ except KeyError:
+ cache[metacls] = type('TOS' + metacls.__name__, (mcl, metacls), {})
+ return cache[metacls]
+
+def new(mcl, objcls, mixins):
+ "Returns a class akin to objcls, but meta-enhanced with mcl (TOSMeta)"
+ dic = vars(objcls).copy()
+ dic['__mixins__'] = mixins
+ metacls = getrightmeta(type(objcls), mcl)
+ return metacls(objcls.__name__, objcls.__bases__, dic)
+
+def oldstyle(bases):
+ "Return True if there are not bases or all bases are old-style"
+ return not bases or set(map(type, bases)) == set([types.ClassType])
+
+def include(*mixins):
+ "Class decorator factory"
+ frame = sys._getframe(1)
+ if ('__module__' in frame.f_locals and not # we are in a class
+ '__module__' in frame.f_code.co_varnames):
+ # usage as a Python < 2.6 class decorator
+ mcl = frame.f_locals.get("__metaclass__")
+ def makecls(name, bases, dic):
+ if mcl:
+ cls = mcl(name, bases, dic)
+ elif oldstyle(bases):
+ cls = types.ClassType(name, bases, dic)
+ else: # there is at least a new style base
+ cls = type(name, bases, dic)
+ return new(TOSMeta, cls, mixins)
+ frame.f_locals["__metaclass__"] = makecls
+ else:
+ # usage as a Python >= 2.6 class decorator
+ def _include(cls):
+ return new(TOSMeta, cls, mixins)
+ _include.__name__ = 'include_%s>' % '_'.join(m.__name__ for m in mixins)
+ return _include
+
+class TraitContainer(object):
+
+ @classmethod
+ def from_(cls, mixins):
+ return cls(dict((m.__name__, Trait(m, m.__name__)) for m in mixins))
+
+ def __init__(self, dic, obj=None, objcls=None):
+ self.__traits = dic # a dictionary name -> trait
+ self.__obj = obj
+ self.__objcls = objcls
+
+ def __getattr__(self, name):
+ try:
+ trait = self.__traits[name]
+ except KeyError:
+ raise AttributeError(name)
+ else:
+ return trait.__get__(self.__obj, self.__objcls)
+
+ def __iter__(self):
+ return self.__traits.itervalues()
+
+ def __len__(self):
+ return len(self.__traits)
+
+ def __bool__(self):
+ return bool(self.__traits)
+
+ def __get__(self, obj, objcls=None):
+ return self.__class__(self.__traits, obj, objcls)
+
+ def __repr__(self):
+ bound_obj = self.__obj or self.__objcls
+ if bound_obj:
+ msg = 'bound to %r' % bound_obj
+ else:
+ msg = ''
+ return '<Traits %s %s>' % (', '.join(self.__traits), msg)
+
+ def __getstate__(self):
+ return self.__traits
+
+ def __setstate__(self, dic):
+ self.__init__(dic)
+
+def get_traits(obj):
+ "Returns a container of traits for introspection purposes"
+ return getattr(obj, '__traits__', ())
+
+class Super(object):
+ """
+ A simple implementation of the super object valid for single inheritance
+ hierarchies.
+ """
+ def __init__(self, obj=None, cls=None):
+ assert obj or cls, 'Super objects must be bound to something'
+ self.__obj = obj
+ self.__objcls = cls or obj.__class__
+
+ def __getattribute__(self, name, get=object.__getattribute__):
+ obj, objcls = get(self, '_Super__obj'), get(self, '_Super__objcls')
+ attr = getattr(objcls.__bases__[0], name)
+ try: # return the bound descriptor
+ return attr.__get__(obj, objcls)
+ except AttributeError: # not a descriptor
+ return attr
+
+import readline, rlcompleter, re
+
+try:
+ from IPython.completer import Completer as BaseCompleter
+except ImportError:
+ from rlcompleter import Completer as BaseCompleter
+
+class Completer(BaseCompleter):
+ def attr_matches(self, text):
+ m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
+ if not m:
+ return
+ expr, attr = m.group(1, 3)
+ object = eval(expr, self.namespace)
+ words = dir(object)
+ if hasattr(object,'__class__'):
+ words.append('__class__')
+ words += rlcompleter.get_class_members(object.__class__)
+ if hasattr(object, '_Trait__dic'):
+ words += object._Trait__dic.keys()
+ matches = []
+ n = len(attr)
+ for word in words:
+ if word[:n] == attr and word != "__builtins__":
+ matches.append("%s.%s" % (expr, word))
+ return matches
+
+readline.set_completer(Completer().complete)
+readline.parse_and_bind("TAB: complete")