diff options
author | michele.simionato <devnull@localhost> | 2009-01-04 12:09:07 +0000 |
---|---|---|
committer | michele.simionato <devnull@localhost> | 2009-01-04 12:09:07 +0000 |
commit | f95206f9273645869981a4cecdc306d86ff255df (patch) | |
tree | 7f703bd4f737c4408ce8b069e7dcde9c475a6fa2 /mtraits | |
parent | 7be3bae53a2c1e2c23f5387951717db0eb0e7e6b (diff) | |
download | micheles-f95206f9273645869981a4cecdc306d86ff255df.tar.gz |
First published version of the strait module
Diffstat (limited to 'mtraits')
-rw-r--r-- | mtraits/Makefile | 4 | ||||
-rw-r--r-- | mtraits/docs.py | 118 | ||||
-rw-r--r-- | mtraits/setup.py | 26 | ||||
-rw-r--r-- | mtraits/strait.py | 9 |
4 files changed, 89 insertions, 68 deletions
diff --git a/mtraits/Makefile b/mtraits/Makefile new file mode 100644 index 0000000..b332e1d --- /dev/null +++ b/mtraits/Makefile @@ -0,0 +1,4 @@ +MINIDOC = python ~/trunk/ROnline/RCommon/Python/ms/tools/minidoc.py + +docs: docs.py + $(MINIDOC) -dH docs; scp /tmp/docs.html merlin.phyast.pitt.edu:public_html/python/strait.html diff --git a/mtraits/docs.py b/mtraits/docs.py index 0db6006..85f1788 100644 --- a/mtraits/docs.py +++ b/mtraits/docs.py @@ -1,16 +1,15 @@ -r"""A simple implementation of traits in Python +r"""A simple implementation of traits for Python ================================================================== :Author: Michele Simionato -:Date: XXX -:Version: XXX -:Download: XXX +:Date: TODAY +:Version: VERSION +:Download: http://pypi.python.org/pypi/strait :Licence: BSD -:Status: XXX :Abstract: *I provide a simple implementation of - traits as units of composable behavior for Python, then I + traits as units of composable behavior for Python. I argue that traits are better than multiple inheritance. Implementing frameworks based on traits is left as an exercise for the reader.* @@ -44,11 +43,12 @@ framework to traits. 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 +that using traits or using mixins does not make a big +difference in practice +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 +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 @@ -64,9 +64,9 @@ 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 little research and discovered a few implementations. The -biggest effort seems to be `Enthought Traits`_ which however -seems to use the name to intend something very +I made a little research and discovered a few implementations. Then +I have also discovered the `Enthought Traits`_ framework, which however +seems to use the name to intend something completely 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 @@ -74,7 +74,7 @@ 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 +known. The ``strait`` 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 @@ -87,7 +87,7 @@ end user to build her own object system. Such features are usually little used in the Python community, for many good reasons: most people feel that the object system is good -enough as it is and there is no reason to change it; moreover there is +enough and that 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 an application programmer to find a domain where these @@ -119,15 +119,16 @@ 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*. +the name *trait* but with a different meaning (Scala traits are very +close to multiple inheritance). For me a trait is a bunch of methods and attributes with the following properties: -1. the methods/attributes in a trait go logically together; +1. the methods/attributes in a trait belong 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 +4. the trait order 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; @@ -141,7 +142,7 @@ 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 +over mixin classes. Property 7 should 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. @@ -156,16 +157,12 @@ Tkinter class to use traits instead of mixins. Consider the ``Tkinter.Grid``, ``Tkinter.Pack`` and ``Tkinter.Place``: I want to rewrite it by using traits. The ``strait`` module provides a factory function named ``include`` that does the job. -It is enough to replace the multiple inheritance syntax - -.. code-block:: python +It is enough to replace the multiple inheritance syntax:: class Widget(BaseWidget, Grid, Pack, Place): pass -with the following syntax: - -.. code-block:: python +with the following syntax:: class Widget(BaseWidget): __metaclass__ = include(Pack, Place, Grid) @@ -174,8 +171,6 @@ 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``: -.. code-block:: python - >>> from Tkinter import * >>> class Widget(BaseWidget): ... __metaclass__ = include(Pack, Place, Grid) @@ -188,29 +183,25 @@ 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. That can be +multiple inheritance rules, we want the methods coming from +the first class (i.e. ``Pack``) to take precedence. That can be implemented by including directly those methods in the class namespace and relying on rule 3: $$TOSWidget Notice that we had to specify the ``propagate`` method too, since -it is common between ``Pack`` and ``Grid``. +it is a common method 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``): -.. code-block:: python - >>> label = TOSWidget(master=None, widgetName='label', ... cnf=dict(text="hello")) You may visualize the widget by calling the ``.pack`` method: -.. code-block:: python - >>> label.pack() This should open a small window with the message "hello" inside it. @@ -220,7 +211,7 @@ A few caveats and warnings First of all, let me notice that, in spite of apparency, ``include`` does not return a metaclass. Insted, it returns a class factory -function with signature name, bases, dic: +function with signature ``name, bases, dic``: >>> print include(Pack, Place, Grid) #doctest: +ELLIPSIS <function include_Pack_Place_Grid at 0x...> @@ -239,8 +230,6 @@ one not inheriting from ``MetaTOS``. The exact rules followed by Here I want to remark that according to rule 6 traits take the precedence over the base class attributes. Consider the following example: -.. code-block:: python - >>> class Base(object): ... a = 1 @@ -256,15 +245,11 @@ over the base class attributes. Consider the following example: In regular multiple inheritance you would do the same by including ``ATrait`` *before* ``Base``, i.e. -.. code-block:: python - >>> type('Class', (ATrait, Base), {}).a 2 -Take care to not mix-up the order, otherwise you will get a different -result: - -.. code-block:: python +You should take care to not mix-up the order, otherwise you will get a +different result: >>> type('Class', (Base, ATrait), {}).a 1 @@ -275,15 +260,13 @@ you rely on the order. Be careful! The Trait Object System ---------------------------------------------------------------------- -The goal of the ``mtrait`` module it to modify the standard +The goal of the ``strait`` 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 do not support multiple inheritance. If you try to multiple inherit from a TOS class and another class you will get a ``TypeError``: -.. code-block:: python - >>> class M: ... "An empty class" ... @@ -337,7 +320,7 @@ in the Tkinter example a ``Widget`` *is* a ``BaseWidget`` but has the methods of the traits ``Pack``, ``Place`` and ``Grid``. .. _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/ +.. _elsewhere: http://www.artima.com/weblogs/viewpost.jsp?thread=246341 .. _PloneSite hierarchy: http://www.phyast.pitt.edu/~micheles/python/plone-hierarchy.png The magic of ``include`` @@ -353,7 +336,7 @@ which implements a single inheritance check. If you build your TOS hierarchy starting from pre-existing classes, you should be aware of how ``include`` determines the metaclass: if your base class was an old-style -class or a simple new style class (i.e. a direct instance of the +class or a plain new style class (i.e. a direct instance of the ``type`` metaclass), them ``include`` will change it to ``MetaTOS``: >>> type(TOSWidget) @@ -373,8 +356,6 @@ $$PackWidget ``include`` automatically generates the right metaclass as a subclass of ``AddGreetings``: -.. code-block:: python - >>> print type(PackWidget).__mro__ (<class 'strait._TOSAddGreetings'>, <class '__main__.AddGreetings'>, <type 'type'>, <type 'object'>) @@ -439,8 +420,8 @@ Traceback (most recent call last): OverridingError: LogOnInitMI overrides names in RegisterOnInitMI: {__init__} This is a feature, of course, since the trait object system is designed -to avoid name clashes. However, the situation is worse than that and -even if you try to mixin a single class you will run into trouble: +to avoid name clashes. However, the situation is worse than that: +even if you try to mixin a single class you will run into trouble >>> class C_MI(object): ... __metaclass__ = include(LogOnInitMI) @@ -450,17 +431,18 @@ Traceback (most recent call last): ... TypeError: super(type, obj): obj must be an instance or subtype of type -What's happening here? The situation is clear if you notice that in -this case ``type`` is ``LogOnInitMI`` whereas ``obj`` is an instance -of ``C``, which is not a subclass of ``LogOnInitMI``. That explains the +What's happening here? The situation is clear if you notice that the +``super`` call is actually a call of kind ``super(LogOnInitMI, c)`` +where ``c`` is an instance of ``C``, which is not a +subclass of ``LogOnInitMI``. That explains the error message, but does not explain how to solve the issue. It seems that method cooperation using ``super`` is impossible for TOS classes. Actually this is not the case: single inheritance cooperation is possible and it is enough as we will show in a -minute. But for the moment let me notice that I have argued elsewhere -that cooperative methods are not necessarily a good idea. They are +minute. But for the moment let me notice that I do not think +that cooperative methods are necessarily a good idea. They are fragile and cause all of your classes to be strictly coupled. My usual advice if that you should not use a design based on method cooperation if you can avoid it. @@ -469,11 +451,11 @@ really want method cooperation. The ``strait`` module provide support for those situations via the ``__super`` attribute. Let me explain how it works. When you mix-in a trait ``T`` into a -class ``C``, ``include`` adds an attribute ``_T__super`` -to ``C``, which is a ``super`` object that dispatches to the -attributes of the superclass of ``C``. The important thing is that -there is a well defined superclass, since the trait object system -uses single inheritance only. Since the hierarchy is straight, the +class ``C``, ``include`` adds an attribute ``_T__super`` to ``C``, +which is a ``super`` object that dispatches to the attributes of the +superclass of ``C``. The important thing to keep in mind is that there +is a well defined superclass, since the trait object system uses +single inheritance only. Since the hierarchy is straight, the cooperation mechanism is much simpler to understand than in multiple inheritance. Here is an example. First of all, let me rewrite ``LogOnInit`` and ``RegisterOnInit`` to use ``__super`` instead of @@ -512,12 +494,12 @@ intended for framework writers, so it assumes you can change the source code of your framework if you want. On the other hand, if are trying to re-use a mixin class coming from a third party framework and using ``super``, you will have to rewrite the -parts of it. That is unfortunate, but I cannot make miracles. +parts of it. That is unfortunate, but I cannot perform miracles. You may see ``__super`` as a clever hack to use ``super`` indirectly. Notice that since the hierarchy is straight, there is room for optimization at the core language -level. The ``__super`` trick as implemented in pure Python is a hack leveraging +level. The ``__super`` trick as implemented in pure Python leverages on the name mangling mechanism, and follows closely the famous `autosuper recipe`_, with some improvement. Anyway, if you have two traits with the same @@ -659,7 +641,7 @@ so that it was possible to write something like the following In version 0.5 I decided to remove this feature. Now the plumbing (i.e. the ``__metaclass__`` hook) is exposed to the user, some magic -has been removed and it is easier for the user to write its own +has been removed and it is easier for the user to write her own ``include`` factory if she wants to. Where to go from here? For the moment, I have no clear idea about the @@ -685,7 +667,7 @@ will be happy. Trivia -------------------------------- -``strait`` officially stands for ``Simple Trait`` Object System, however +``strait`` officially stands for Simple Trait object system, however the name is also a pun on the world "straight", since the difference between multiple inheritance hierarchies and TOS hierarchies is that TOS hierarchies are straight. Moreover, nobody will stop you from @@ -698,6 +680,12 @@ thinking that the ``s`` also stands for Simionato ;) .. _here: http://www.ibm.com/developerworks/linux/library/l-pymeta3.html """ +from datetime import datetime +TODAY = datetime.today().isoformat()[:10] +VERSION = '0.5.0' + +__doc__ = __doc__.replace('VERSION', VERSION).replace('TODAY', TODAY) + import time import cPickle as pickle from strait import * diff --git a/mtraits/setup.py b/mtraits/setup.py new file mode 100644 index 0000000..a1c2272 --- /dev/null +++ b/mtraits/setup.py @@ -0,0 +1,26 @@ +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +VERSION = '0.5.0' +setup(name='strait', + version=VERSION, + description='Simple Traits for Python', + long_description="""strait is a simple implementation of trait-based object system for Python +""", + author='Michele Simionato', + author_email='michele.simionato@gmail.com', + url='http://www.phyast.pitt.edu/~micheles/python/strait.html', + license="BSD License", + py_modules = ['strait'], + keywords='', + platforms=['any'], + classifiers=['Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries'], + zip_safe=False) diff --git a/mtraits/strait.py b/mtraits/strait.py index 0489c04..108f9ee 100644 --- a/mtraits/strait.py +++ b/mtraits/strait.py @@ -1,6 +1,6 @@ __all__ = ['include', 'MetaTOS'] -import re, sys, inspect, types, warnings +import inspect, types, warnings class OverridingError(NameError): pass @@ -9,7 +9,8 @@ class OverridingWarning(Warning): pass class Super(object): - # this is needed to fix a shortcoming of unbound super objects + # this is needed to fix a shortcoming of unbound super objects, + # i.e. this is how the unbound version of super should work def __init__(self, thisclass): self.__thisclass__ = thisclass def __get__(self, obj, objcls): @@ -89,10 +90,12 @@ def get_right_meta(metatos, bases): except IndexError: base = object meta = type(base) - if meta in (types.ClassType, type): + if meta in (types.ClassType, type): # is a builtin meta return metatos elif any(issubclass(meta, m) for m in known_metas): return meta + # meta is independent from all known_metas, make a new one with + # __new__ method coming from MetaTOS newmeta = type( '_TOS' + meta.__name__, (meta,), dict(__new__=metatos.__new__)) setattr(newmeta, '_%s__super' % metatos.__name__, Super(newmeta)) |