summaryrefslogtreecommitdiff
path: root/mtraits
diff options
context:
space:
mode:
authormichele.simionato <devnull@localhost>2008-08-17 12:00:55 +0000
committermichele.simionato <devnull@localhost>2008-08-17 12:00:55 +0000
commit31d64515be4345ce176793d232adc7301f400d79 (patch)
tree8ab7d8ae10d2d41bce46bdb9bd7914d512d5ec68 /mtraits
parentb3728f24fb2ab371fc6891852d65a74d096f478d (diff)
downloadmicheles-31d64515be4345ce176793d232adc7301f400d79.tar.gz
A version of mtrait with metaclass conflict resolution
Diffstat (limited to 'mtraits')
-rw-r--r--mtraits/CHANGES.txt4
-rw-r--r--mtraits/README.txt14
-rw-r--r--mtraits/doc.py704
-rw-r--r--mtraits/mtrait.py275
-rw-r--r--mtraits/noconflict.py50
-rw-r--r--mtraits/plone-hierarchy.pngbin0 -> 433665 bytes
6 files changed, 646 insertions, 401 deletions
diff --git a/mtraits/CHANGES.txt b/mtraits/CHANGES.txt
new file mode 100644
index 0000000..acc2600
--- /dev/null
+++ b/mtraits/CHANGES.txt
@@ -0,0 +1,4 @@
+HISTORY
+-----------------------------
+
+0.1.0. Initial release (2008-08-16)
diff --git a/mtraits/README.txt b/mtraits/README.txt
new file mode 100644
index 0000000..b76a86a
--- /dev/null
+++ b/mtraits/README.txt
@@ -0,0 +1,14 @@
+Dependencies:
+
+The mtrait module requires Python 2.4.
+
+Installation:
+
+Unzip the package and put the mtrait.py module somewhere in your
+Python path.
+
+Tests:
+
+You may run the tests from the package directory as follows:
+
+$ python doc.py
diff --git a/mtraits/doc.py b/mtraits/doc.py
index 80351a0..fe231f5 100644
--- a/mtraits/doc.py
+++ b/mtraits/doc.py
@@ -4,7 +4,9 @@ r"""An implementation of traits in Python
:Author: Michele Simionato
:Date: XXX
:Version: XXX
+:Download: XXX
:Licence: BSD
+:Status: XXX
Motivation
------------------------------------------------
@@ -15,105 +17,139 @@ 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
+inheritance in Python`_ and I was one of its supporters once; however,
+since then 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
+the number of the people who oppose it. Therefore I
+am interested in alternatives.
+
+In recent years, the approach
+of traits_ has gained some traction in a few circles and 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
+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
+an alternative. This library is also for authors of mixin-bases frameworks
+which 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
+Are traits a better solution than multiple inheritance and mixins? In
+theory I think so, otherwise I would not have written this library, but
+in practice (as always) things may be different. It may well be
+in practice that using traits or using mixins does not make a big
+difference and that the change of 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, of course, 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 (when possible) with pre-existing frameworks.
+As an example, I will show
+here how you could rewrite Tkinter classes to use traits instead of mixins. Of
+course, I am not advocating rewriting Tkinter: it would be silly
+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.
+
+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
+I made a little research and discovered a few implementations. The
+biggest effort seems to be `Enthought Traits`_ which however is
+large implementation and seems to use the name to intend something very
+different (i.e. a sort of type checking). My implementation has no
+dependencies, 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
-
+the principle of `less is more`_.
+
+There is also an hidden agenda behind this module: to popularize some
+advanced features of the Python object model which are little
+known. The ``mtrait`` module is actually a tribute to the
+metaprogramming capabilities of Python: such features are usually
+associated to languages with a strong academic tradition - Smalltalk,
+Scheme, Lisp - but actually the Python object model is no less
+powerful. For instance, changing the object system from a multiple
+inheritance one to a trait-based one, with *different* lookup rules,
+can be done *within* the fundamental object system. The reason is that
+the features that Guido used to implement the object system (special
+method hooks, descriptors, metaclasses) are there, available to the
+end user to build her own object system.
+
+Such features are usually little used in the Python community for
+various good reasons: the object system is good enough as it is and there
+is no reason to change it; moreover there is a strong opposition to
+change the language, because Python programmers believe in uniformity
+and in using common idioms; finally, it is difficult for application
+programmer to find a domain where these features are useful. An
+exception is the domain of the Object Relation Mappers, whereas the
+Python language is often stretched to mimic the SQL language, a famous
+example of this tendency being SQLAlchemy_).
+Still, I have never seen a perversion of the object model as big
+as the one implemented in the ``mtrait`` module, so I wanted to
+be the first one to perform that kind of abuse ;)
.. _multiple inheritance in Python: MRO
+.. _less is more: http://www.artima.com/weblogs/viewpost.jsp?thread=236286
+.. _Enthought Traits: https://svn.enthought.com/enthought/wiki/Traits
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
+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
+somewhat 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. 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 with 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
+1. the methods/attributes in a trait go logically together;
+2. if a trait enhances a class, then all subclasses are enhanced too;
+3. if a trait has methods in common with the class, then the
+ methods defined in the class have the precedence;
+4. the ordering of traits is not important, i.e. enhancing a class
+ first with trait T1 and then with trait T2 or viceversa is the same;
+5. 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
+6. if a trait has methods in common with the base class, then the
+ trait methods have the precedence;
+7. 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.
+Properties from 4 to 7 are the distinguishing properties of traits
+with respect to multiple inheritance and mixins. In particular,
+because of 4 and 5, all the complications with the Method Resolution
+Order disappear and the overriding is never implicit. Property 6 is
+mostly unusual: typically in Python the base class has the precedence
+over mixin classes. Property 7 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
+Let me begin by showing 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
+rewrite it to use traits. The ``mtrait`` module
+provides a class decorator named ``include`` to do the job; it is
enough to replace the multiple inheritance syntax
.. code-block:: python
@@ -135,7 +171,7 @@ inside classes:
.. code-block:: python
- class Widget(BaseWidget): # this syntax works in Python 2.2+
+ class Widget(BaseWidget): # this syntax works is backward-compatible
include(Pack, Place, Grid)
The preferred way however is to use the ``@`` notation, if available.
@@ -143,12 +179,14 @@ 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 overrides names in Place: {info, config, configure, slaves, forget}
+.. code-block:: python
+
+ >>> from Tkinter import *
+ >>> class Widget(BaseWidget):
+ ... include(Pack, Place, Grid)
+ Traceback (most recent call last):
+ ...
+ OverridingError: Pack overrides names in Place: {info, config, configure, slaves, forget}
The reason for the error is clear: both ``Pack`` and ``Place`` provide
methods called ``{info, config, configure, slaves, forget}``
@@ -156,7 +194,9 @@ 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:
+the first class (i.e. ``Pack``) to have precedence. That can be
+implemented by including directly those methods in the class namespace
+and relying on rule 3:
$$TOSWidget
@@ -167,51 +207,87 @@ 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"))
+.. code-block:: python
+
+ >>> label = TOSWidget(master=None, widgetName='label',
+ ... cnf=dict(text="hello"))
You may visualize the widget by calling the ``.pack`` method:
->>> label.pack()
+.. code-block:: python
+
+ >>> label.pack()
This should open a small window with the message "hello" inside it.
+
+A few caveats
+----------------------------------------------------
+
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
+straightforward: however, in some cases you can run into problems.
+For instance, rule 6 implies an attribute lookup different from
+the usual lookup. Consider the following example:
+
+.. code-block:: python
+
+ >>> class Base(object):
+ ... a = 1
+
+ >>> class ATrait(object):
+ ... a = 2
+
+ >>> class Class(Base):
+ ... include(ATrait)
+
+ >>> Class.a
+ 2
+
+In regular multiple inheritance instead the base class attribute
+would have the precedence:
+
+.. code-block:: python
+
+ >>> type('Class', (Base, ATrait), {}).a
+ 1
+
+Therefore replacing mixin classes with traits can break your code if
+you rely on the standard overriding rules. Be careful!
+
+Also, you should be aware of the fact that *special methods
+are special*: the ``mtrait`` module perverts the Python object system
+to follow rules 1-7 for all attribute access *except* for special
+attributes. Special attributes of the form ``__xxx___`` in a trait
+are just *ignored*:
+
+.. code-block:: python
+
+ >>> ATrait.__special__ = 3 # this attribute is NOT transmitted to Class
+ >>> hasattr(Class, '__special__')
+ False
+
+Finally, 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.
+your code not to use ``super`` (notice however that 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
+The implementation of traits provided is an ``mtrait`` is short (under
+300 lines of code including comments and docstrings) but if makes use
+of fairly sophisticated techniques. The building block is 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
+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__)``
@@ -226,102 +302,178 @@ 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} >
+.. code-block:: python
+
+ >>> 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
+ >>> 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...>>
+.. code-block:: python
+
+ >>> 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...>>
+.. code-block:: python
+
+ >>> 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']
+.. code-block:: python
+
+ >>> 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>
+.. code-block:: python
+
+ >>> 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 >
+.. code-block:: python
+
+ >>> 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} >
+.. code-block:: python
+
+ >>> 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``
+Internally the ``include`` decorator builds a trait object for each mixin and
+store all of them into the ``__traits__`` attribute descriptor,
+making your traits ready for introspection (rule 7):
+
+.. code-block:: python
+
+ >>> TOSWidget.__traits__ # bound-to-class TraitContainer
+ <Traits Grid, Place, Pack bound to <class '__main__.TOSWidget'>>
+
+ >>> label.__traits__ #doctest: +ELLIPSIS
+ <Traits Grid, Place, Pack bound to <__main__.TOSWidget object at 0x...>>
+
+Traits play well with pydoc, ipython and the other tools used
+to introspect Python objects: try to type
+``help(lbl)`` and you will see for yourself how it works.
+
+Beyond the looking glass
----------------------------------------------------------------------
-Even if you could build your trait objects yourself as explained in
+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 behave differently from regular
+classes. In particular TOS classes must not support multiple inheritance
+and must forbid your from defining your own ``__getattribute__`` 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.
+
+In theory 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
+by hand; in practice however it is much 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_``
+behalf. In particular, internally the class decorator works its magic
+by changing the metaclass of the original class to a subclass of
+``MetaTOS``, the basic class of the Trait Object System. ``MetaTOS``
+does a lot of work: in particular it adds a suitable ``__getattribute__``
+method to its instances.
+
+We need to override the
+``__getattribute__`` method
+since we want to change the attribute lookup rules: in regular
+Python, the usual rules are 1. look at the class 2. look at
+the base class and 3. look at ``__getattr__``; rules 6 instead
+says that traits must have the precedence over the base class,
+so overriding ``__getattr__`` would not be enough. Fortunately,
+the Python object model is powerful enough to allows users
+to change the rules of the game: by overriding ``__getattribute__``
+it is possible to lookup at the traits attributes *before* looking
+at the base class. Notice that is necessary to override
+``__getattribute__`` both at the class level and at the metaclass
+level, to be able to manage both instance attributes and class
+attributes.
+
+``MetaTOS`` also adds a suitable ``__getstate_``
method so that your objects stay pickleable if they were originally
-pickleable (adding ``__getattr__`` without adding ``__getstate__`` would
+pickleable (adding ``__getattribute__`` without adding ``__getstate__`` would
break pickle). Notice that the original class should not define
-``__getattr__`` or ``__getstate__`` of its own, otherwise an
+``__getattribute__`` 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__``:
+People wanting to do everything explicitely can use
+the ``__metaclass__`` hook and specify the mixins directly with ``__mixins__``:
$$TOSWidget2
-``include`` does more than that, since it
+However, ``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'>
+.. code-block:: python
+
+ >>> type(TOSWidget)
+ <class 'mtrait.TOSMeta'>
-However, in general you may need to build your Trait Based Framework
+Nevertheless, 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.
+enough to figure out the right metaclass to use is a convenient
+facility. Here is an example:
+
+$$MyMetacls
+
+.. code-block:: python
+
+ >>> class Base:
+ ... __metaclass__ = MyMetacls
+ ... include(Pack)
+ ...
+ >>> print type(Base)
+ <class 'noconflict._TOSMetaMyMetacls'>
+
+The ``include`` decorator automatically generates the right metaclass
+which avoids the dreaded `metaclass conflict`_, a daughter of
+``TOSMeta`` and ``MyMetacls``:
+
+.. code-block:: python
+
+ >>> print type(Base).__bases__
+ (<class 'mtrait.TOSMeta'>, <class '__main__.MyMetacls'>)
+
+The name is automatically generated from the name of the base
+metaclasses; moreover, a register of the generated metaclasses
+is kept, so that metaclasses are reused if possible.
+If you want to understand the details, you are welcome
+to give a look at the implementation, which is pretty small.
+
+.. _sqlalchemy: http://www.sqlalchemy.org/
+.. _metaclass conflict: http://code.activestate.com/recipes/204197/
Why multiple inheritance is forbidden
----------------------------------------------------------
@@ -330,55 +482,91 @@ 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
+.. code-block:: python
+
+ >>> class M: pass
+ ...
+ >>> class Widget2(TOSWidget, M): #doctest: +ELLIPSIS
+ ... pass
+ ...
+ Traceback (most recent call last):
+ ...
+ TypeError: Multiple inheritance of bases (<class '__main__.TOSWidget'>, <class __main__.M 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
+notes 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:
+acceptable restrictions since they give back in return many advantages
+in terms of simplicity: for instance, ``super`` becomes trivial, since
+each class has a single superclass.
+
+We all know that the `current
+super in Python`_ is very far from trivial, instead. More importantly,
+we all know that. many people use multiple inheritance incorrectly,
+confusing the ``is-a`` relation with the ``has-a`` relation; with
+traits, there is no confusion. Since there is a single base class, you
+can associate the ``is-a`` relation with the base class whereas the
+features coming from the traits correspond to ``has-a``: for instance
+in the Tkinter example a ``Widget`` *is* a ``BaseWidget`` but has the
+methods of the traits ``Pack``, ``Place`` and ``Grid``.
+
+One could take even a more extreme stance: abolish both multiple inheritance
+and traits and add methods to a class by hand, which something
+like this:
-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.
+.. code-block:: python
-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
+ def include_methods(cls, method_container):
+ for name, value in vars(method_container).itervalues():
+ setattr(cls, name, value)
+
+This approach is definitely simple, but perhaps *too* simple. 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, which is pretty useful
+for debugging purposes. This feature is lost in the simplistic
+approach but not lost in the trait approach: the trait implementation
+is fully dynamic and if you change the mixin the instances will be
+changed too. This however, is a minor point.
+
+The real reason why just
+including methods in the class namespace is a bad idea is that it
+easily leads you into the problem of *namespace pollution*. I have
+discussed the issue elsewhere_: if you keep injecting methods into a
+class (both directly or via inheritance) you may end up having
+hundreds of methods flattened at the same level. A picture is worth a
+thousand words, so have a look at the `PloneSite hierarchy`_ if you
+want to understand the horror I would like to avoid with traits
+(the picture shows the number of
+nonspecial attributes defined per class in square brackets): in
+the Plone Site hierarchy there
+are 38 classes, 88 overridden names, 42 special names, 648
+non-special attributes and methods. It is a maintenance nighmare.
+The
+``mtrait`` implementation avoids name space pollution since each trait
+has its own namespace and you are guaranteed against name
+conflicts. Moreover, introspecting traits is much easier than
+introspecting inheritance hierarchies: even the autocompletion
+feature works best.
+
+*Note 1*: 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
+to do with multiple inheritance. However in Python (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
@@ -386,25 +574,11 @@ 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__
+that explains why you inherit from them.
->>> #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...>>
+.. _current super in Python: http://www.artima.com/weblogs/viewpost.jsp?thread=236275
+.. _elsewhere: http://stacktrace.it/articoli/2008/06/i-pericoli-della-programmazione-con-i-mixin1/
+.. _PloneSite hierarchy: http://www.phyast.pitt.edu/~micheles/python/plone-hierarchy.png
Future work
--------------------------------------------------------
@@ -412,31 +586,29 @@ 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
-
+renaming, or I may not. At the present you can just rename
+your methods by hand.
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.
+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. 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 and it is worth to
+try them out.
+
+*Post Scriptum:* if your are curious about the origin of the letter ``m`` in
+``mtrait``, I added it to avoid conflicts with other traits
+implementation; it stands for *Meta* or for *My* or even for
+*Michele*, at your will ;)
.. _traits: http://www.iam.unibe.ch/~scg/Research/Traits/
.. _Traits - Composable Units of Behavior: http://www.iam.unibe.ch/%7Escg/Archive/Papers/Scha03aTraits.pdf
@@ -489,19 +661,19 @@ class FTP(object):
print 'calling staticmethod'
class Mixin(object):
- def helper(self):
+ def _helper(self):
return 1
def method(self):
- return self.helper() + 1
+ return self._helper() + 1
-class Meta(type):
- pass
+class MyMetacls(type):
+ "A do-nothing metaclass for exemplification purposes"
def test_getattr():
try:
class C:
__metaclass__ = TOSMeta
- def __getattr__(self, name):
+ def __getattribute__(self, name):
pass
except OverridingError: # expected
pass
@@ -510,11 +682,19 @@ def test_getattr():
def test_multi_include():
class B(object):
- __metaclass__ = Meta
+ __metaclass__ = MyMetacls
include(FTP)
class C(B):
include(HTTP)
- print type(C)
+ def __init__(self, a):
+ self.a = a
+ class D(C):
+ pass
+ x = D(1)
+ x.a
+ x.cm()
+ x.sm()
+ print type(B), type(C)
def test_Trait_pickle():
t = Trait(Mixin, Mixin.__name__)
@@ -525,16 +705,6 @@ class C:
__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()
@@ -544,6 +714,11 @@ class A(object):
def save(self):
print 'A.save'
+class AM(A):
+ include(Mixin)
+
+assert AM().method() == 2
+
class B(A):
def save(self):
print 'B.save'
@@ -552,7 +727,7 @@ class B(A):
class C(A):
def save(self):
print 'C.save'
- Super(self).save()
+ A.save(self)
class D(B, C):
def save(self):
@@ -582,3 +757,30 @@ if __name__ == '__main__':
pass
else:
nose.runmodule()
+
+'''
+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 ``__mixins__``, a descriptor ``__traits__``, a custom
+``__getattribute__`` method and a custom ``__getstate__`` method. The
+``__mixins__`` attribute is just the tuple of the included mixins;
+the ``__traits__`` descriptor is a ``TraitContainer`` object akin to a
+dictionary of ``Trait`` objects (one for each mixin class); the
+``__getattribute__`` 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.
+
+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.
+*Multiple inheritance is most useful at the metaclass level,
+where you need to cooperatively override __new__!*
+
+from ms.tools.conceptualmap import *
+PloneSite=app.rcare.__class__
+plot_classes(PloneSite.mro(), verbose=2)
+
+
+'''
diff --git a/mtraits/mtrait.py b/mtraits/mtrait.py
index 5e86af8..971d086 100644
--- a/mtraits/mtrait.py
+++ b/mtraits/mtrait.py
@@ -1,4 +1,5 @@
import re, sys, inspect, types, warnings
+from noconflict import get_noconflict_metaclass
class OverridingError(NameError):
pass
@@ -35,6 +36,62 @@ def check_overridden(mixins, exclude, raise_='error'):
elif raise_ == 'warning':
warnings.warn(msg, OverridingWarning, stacklevel=2)
+def getboundvalue(value, obj, objcls):
+ "Convert a value into a bound descriptor or do nothing"
+ try: # return the bound descriptor
+ return value.__get__(obj, objcls)
+ except AttributeError: # not a descriptor
+ return value
+
+# added to the instances of TOSMeta
+def __obj_getattribute__(obj, name, get=object.__getattribute__):
+ """
+ Lookup for TOS instances:
+ 1. look at the instance dictionary;
+ 2. look at the class dictionary;
+ 3. look at the traits;
+ 4. look at the base classes and to __getattr__
+ """
+ if name.startswith('__') and name.endswith('__'): # special name, do nothing
+ return get(obj, name)
+ try:
+ return vars(obj)[name]
+ except KeyError:
+ pass
+ objcls = type(obj)
+ try:
+ return getboundvalue(vars(objcls)[name], obj, objcls)
+ except KeyError:
+ pass
+ for boundtrait in obj.__traits__:
+ try:
+ return getattr(boundtrait, name)
+ except AttributeError:
+ pass
+ return get(obj, name)
+
+# added to TOSMeta
+def __cls_getattribute__(cls, name, get=type.__getattribute__):
+ """
+ Lookup for TOS classes:
+ 1. look at the class dictionary;
+ 2. look at the traits;
+ 3. look at the base classes and the metaclass __getattr__
+ """
+ if (name.startswith('__') and name.endswith('__')) or name == 'mro':
+ # special names, do nothing
+ return get(cls, name)
+ try:
+ return getboundvalue(vars(cls)[name], None, cls)
+ except KeyError:
+ pass
+ for boundtrait in cls.__traits__:
+ try:
+ return getattr(boundtrait, name)
+ except AttributeError:
+ pass
+ return get(cls, name)
+
class Trait(object):
"""
Class for mixin dispatchers. Mixin dispatchers are instantiated through the
@@ -94,40 +151,82 @@ class Trait(object):
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
+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:
- return getattr(bt, name)
- except AttributeError:
- continue
- raise AttributeError(name)
+ trait = self.__traits[name]
+ except KeyError:
+ raise AttributeError(name)
+ else:
+ return trait.__get__(self.__obj, self.__objcls)
+
+ def __iter__(self):
+ return (t.__get__(self.__obj, self.__objcls)
+ for t in 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 oldstyle(bases):
+ "Return True if all bases are old-style"
+ return set(map(type, bases)) == set([types.ClassType])
class TOSMeta(type):
- """The metaclass for the Trait Object System. It is intended to be
+ """
+ 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__
+ 2. checks for accidental overriding of __getattribute__ and __getstate__
+ 3. provides the class with the correct base __getattribute__ 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
+ elif not bases or oldstyle(bases): # ensure new-style class
bases += (object, )
- for meth in ('__getattr__', '__getstate__'):
+ for meth in ('__getattribute__', '__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['__getattribute__'] = dic.get('__getattribute__',
+ __obj_getattribute__)
dic['__getstate__'] = dic.get('__getstate__', vars)
basemixins = ()
else:
@@ -142,38 +241,13 @@ class TOSMeta(type):
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]
+ __getattribute__ = __cls_getattribute__
-def new(mcl, objcls, mixins):
- "Returns a class akin to objcls, but meta-enhanced with mcl (TOSMeta)"
- dic = vars(objcls).copy()
+def new(name, bases, dic, mixins, leftmetas):
+ "Returns a class akin to objcls, but meta-enhanced with metas"
+ metacls = get_noconflict_metaclass(bases, leftmetas, ())
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])
+ return metacls(name, bases, dic)
def include(*mixins):
"Class decorator factory"
@@ -184,113 +258,14 @@ def include(*mixins):
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)
+ return new(name, bases, dic, mixins, (TOSMeta, mcl))
+ else:
+ return new(name, bases, dic, mixins, (TOSMeta,))
frame.f_locals["__metaclass__"] = makecls
else:
# usage as a Python >= 2.6 class decorator
def _include(cls):
- return new(TOSMeta, cls, mixins)
+ return new(cls.__name__, cls.__bases__, cls.__dict__.copy(),
+ mixins, (TOSMeta,))
_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")
diff --git a/mtraits/noconflict.py b/mtraits/noconflict.py
new file mode 100644
index 0000000..bbdb678
--- /dev/null
+++ b/mtraits/noconflict.py
@@ -0,0 +1,50 @@
+"""
+An helper module for meta-type conflict resolution
+"""
+
+import inspect, types
+
+memoized_metaclasses_map = {}
+
+def skip_redundant(iterable, skipset=None):
+ "Redundant items are repeated items or items in the original skipset."
+ if skipset is None: skipset = set()
+ for item in iterable:
+ if item not in skipset:
+ skipset.add(item)
+ yield item
+
+def remove_redundant(metaclasses):
+ skipset = set([types.ClassType])
+ for meta in metaclasses: # determines the metaclasses to be skipped
+ skipset.update(inspect.getmro(meta)[1:])
+ return tuple(skip_redundant(metaclasses, skipset))
+
+# make tuple of needed metaclasses in specified priority order
+def get_noconflict_metaclass(bases, left_metas, right_metas):
+ metas = left_metas + tuple(map(type, bases)) + right_metas
+ needed_metas = remove_redundant(metas)
+
+ # return existing confict-solving meta, if any
+ if needed_metas in memoized_metaclasses_map:
+ return memoized_metaclasses_map[needed_metas]
+ # nope: compute, memoize and return needed conflict-solving meta
+ elif not needed_metas: # wee, a trivial case, happy us
+ meta = type
+ elif len(needed_metas) == 1: # another trivial case
+ meta = needed_metas[0]
+ # check for recursion, can happen i.e. for Zope ExtensionClasses
+ elif needed_metas == bases:
+ raise TypeError("Incompatible root metatypes", needed_metas)
+ else: # gotta work ...
+ metaname = '_' + ''.join(m.__name__ for m in needed_metas)
+ meta = classmaker()(metaname, needed_metas, {})
+ memoized_metaclasses_map[needed_metas] = meta
+ return meta
+
+# recursive builder used in conjunction with get_noconflict_metaclass
+def classmaker(left_metas=(), right_metas=()):
+ def make_class(name, bases, adict):
+ metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
+ return metaclass(name, bases, adict)
+ return make_class
diff --git a/mtraits/plone-hierarchy.png b/mtraits/plone-hierarchy.png
new file mode 100644
index 0000000..fa6d90d
--- /dev/null
+++ b/mtraits/plone-hierarchy.png
Binary files differ