summaryrefslogtreecommitdiff
path: root/mtraits
diff options
context:
space:
mode:
authormichele.simionato <devnull@localhost>2008-08-17 16:16:10 +0000
committermichele.simionato <devnull@localhost>2008-08-17 16:16:10 +0000
commitbfc23a91358f8f9d38ad2c44353cf65d078981db (patch)
tree1bec5f72ddf0dcf59ca7d5f79af9b3ae1a890e09 /mtraits
parent31d64515be4345ce176793d232adc7301f400d79 (diff)
downloadmicheles-bfc23a91358f8f9d38ad2c44353cf65d078981db.tar.gz
Committed release 0.3 of mtrait
Diffstat (limited to 'mtraits')
-rw-r--r--mtraits/doc.py113
-rw-r--r--mtraits/mtrait.py48
2 files changed, 112 insertions, 49 deletions
diff --git a/mtraits/doc.py b/mtraits/doc.py
index fe231f5..b15d96d 100644
--- a/mtraits/doc.py
+++ b/mtraits/doc.py
@@ -392,14 +392,31 @@ 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; 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 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.
-
+by hand; however, in practice 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 works
+its magic by changing the metaclass of the original class. Usually the
+metaclass is changed to ``TOSMeta``, the basic class of the Trait
+Object System. However in general not all TOS classes are
+instances of ``TOSMeta``. A class is a TOS class if it satisfies
+a given interface: the ``mtrait`` module provides a ``isTOSclass``
+utility function which performs the check:
+
+$$isTOSclass
+
+The check is *intentionally* loose, i.e. you can fool it by
+setting a fake ``__traits__`` attribute: it is there to
+prevent accidental mistakes, not to give any guarantee.
+On the other hand,
+the ``include`` class decorator ensures that the metaclass of the
+decorated class is a subclass of the metaclass of the undecorated
+class and that it adds the proper ``__traits__``,
+``__getattribute__``, ``__getstate__`` and ``__mixins__`` attributes,
+doing the same job as ``TOSMeta`` even if it is not necessarily a
+subclass of ``TOSMeta``.
+
+``TOSMeta`` 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
@@ -415,7 +432,7 @@ at the base class. Notice that is necessary to override
level, to be able to manage both instance attributes and class
attributes.
-``MetaTOS`` also adds a suitable ``__getstate_``
+``TOSMeta`` also adds a suitable ``__getstate_``
method so that your objects stay pickleable if they were originally
pickleable (adding ``__getattribute__`` without adding ``__getstate__`` would
break pickle). Notice that the original class should not define
@@ -450,24 +467,24 @@ $$MyMetacls
.. code-block:: python
- >>> class Base:
- ... __metaclass__ = MyMetacls
- ... include(Pack)
- ...
- >>> print type(Base)
- <class 'noconflict._TOSMetaMyMetacls'>
+$$Base2
The ``include`` decorator automatically generates the right metaclass
-which avoids the dreaded `metaclass conflict`_, a daughter of
-``TOSMeta`` and ``MyMetacls``:
+which avoids the dreaded `metaclass conflict`_, a subclass of ``MyMetacls``:
.. code-block:: python
- >>> print type(Base).__bases__
- (<class 'mtrait.TOSMeta'>, <class '__main__.MyMetacls'>)
+ >>> print type(Base2).__mro__
+ (<class 'mtrait._TOSMetaMyMetacls'>, <class '__main__.MyMetacls'>, <type 'type'>, <type 'object'>)
+
+ >>> Base2.__traits__
+ <Traits Pack bound to <class '__main__.Base2'>>
+
+ >>> Base2.greetings
+ 'hello!'
The name is automatically generated from the name of the base
-metaclasses; moreover, a register of the generated metaclasses
+metaclass; 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.
@@ -478,7 +495,7 @@ to give a look at the implementation, which is pretty small.
Why multiple inheritance is forbidden
----------------------------------------------------------
-As I said, the mother metaclass of the Trait Object System ``MetaTOS`` forbids
+As I said, the mother metaclass of the Trait Object System ``TOSMeta`` forbids
multiple inheritance and if you try to multiple inherit from a TOS
class and another class you will get a ``TypeError``:
@@ -502,15 +519,14 @@ 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
-in terms of simplicity: for instance, ``super`` becomes trivial, since
-each class has a single superclass.
+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 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,
@@ -580,9 +596,35 @@ that explains why you inherit from them.
.. _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
+
+Discussion of some design decisions and future work
--------------------------------------------------------
+The decision of having TOS classes which are not instances of TOSMeta
+has been a hard one. That was my original idea in version 0.1 of this
+module; however in version 0.2 I changed my mind and I made all TOS
+classes instances of TOSMeta. That implies that if your original class
+has a nontrivial metaclass, then the TOS class must inherit both from
+the original metaclass *and* ``TOSMeta``, i.e. multiple inheritance
+and cooperation of methods are required at the metaclass level. I did
+not like it, since I am arguing here that you can do everything without
+multiple inheritance and cooperative methods; moreover using multiple
+inheritance at the metaclass level means that one has to solve the
+`metaclass conflict`_ in a general way. I did so, by using my own
+cookbook recipe, and all my tests passed.
+
+Neverthess, at the end, in version 0.3 I decided to go
+back to the original design. The metaclass conflict recipe is too
+complex, and I see it as a code smell (*if the implementation is hard
+to explain, it's a bad idea*), just another indication that multiple
+inheritance is a bad idea. On the other hand, in the original
+design it is possible to add the features of ``TOSMeta`` to the
+original metaclass by subclassing it with *single* inheritance
+and thus avoiding the conflict. The price to pay is that the
+TOS class is no more an instance of ``TOSMeta``, but this is
+not an issue: the important thing is that TOS classes perform
+the dispatch on their traits as ``TOSMeta`` would dictate.
+
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
@@ -668,6 +710,13 @@ class Mixin(object):
class MyMetacls(type):
"A do-nothing metaclass for exemplification purposes"
+ def __new__(mcl, name, bases, dic):
+ dic['greetings'] = 'hello!'
+ return super(MyMetacls, mcl).__new__(mcl, name, bases, dic)
+
+class Base2:
+ __metaclass__ = MyMetacls
+ include(Pack)
def test_getattr():
try:
@@ -693,7 +742,7 @@ def test_multi_include():
x = D(1)
x.a
x.cm()
- x.sm()
+ #x.sm()
print type(B), type(C)
def test_Trait_pickle():
diff --git a/mtraits/mtrait.py b/mtraits/mtrait.py
index 971d086..bd00556 100644
--- a/mtraits/mtrait.py
+++ b/mtraits/mtrait.py
@@ -1,5 +1,4 @@
import re, sys, inspect, types, warnings
-from noconflict import get_noconflict_metaclass
class OverridingError(NameError):
pass
@@ -197,10 +196,13 @@ class TraitContainer(object):
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])
+ "Return True if there are no bases or all bases are old-style"
+ return not bases or set(map(type, bases)) == set([types.ClassType])
+
+def isTOSclass(cls):
+ "True if cls satisfies the TOS interface"
+ return hasattr(cls, '__traits__')
class TOSMeta(type):
"""
@@ -211,14 +213,15 @@ class TOSMeta(type):
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
+ 4. provides the basic empty __traits__ attribute and __mixins__.
"""
def __new__(mcl, name, bases, dic):
+ dic = dic.copy()
if len(bases) > 1:
raise TypeError(
'Multiple inheritance of bases %s is forbidden for TOS classes'
% str(bases))
- elif not bases or oldstyle(bases): # ensure new-style class
+ elif oldstyle(bases): # ensure new-style class
bases += (object, )
for meth in ('__getattribute__', '__getstate__'):
if meth in dic:
@@ -239,15 +242,29 @@ class TOSMeta(type):
mixins = basemixins + mixins
check_overridden(mixins, exclude=set(dic))
dic['__traits__'] = TraitContainer.from_(mixins)
- return super(TOSMeta, mcl).__new__(mcl, name, bases, dic)
+ # since TOS hierarchies are single-inheritance, I don't need super
+ return mcl.__base__.__new__(mcl, name, bases, dic)
__getattribute__ = __cls_getattribute__
-def new(name, bases, dic, mixins, leftmetas):
- "Returns a class akin to objcls, but meta-enhanced with metas"
- metacls = get_noconflict_metaclass(bases, leftmetas, ())
+known_metas = set([types.ClassType, type, TOSMeta])
+
+def new(mcl, name, bases, dic, mixins):
+ "Returns a class akin to objcls, but meta-enhanced with mcl or typ"
+ # there is only one base because of the single-inheritance constraint
+ try:
+ base = bases[0]
+ except IndexError:
+ base = object
+ typ = mcl or type(base)
+ if typ in (types.ClassType, type):
+ typ = TOSMeta
+ elif typ not in known_metas:
+ typ = type('_TOSMeta' + typ.__name__, (mcl,), dict(
+ __new__=TOSMeta.__new__, __getattribute__= __cls_getattribute__))
+ known_metas.add(typ)
dic['__mixins__'] = mixins
- return metacls(name, bases, dic)
+ return typ(name, bases, dic)
def include(*mixins):
"Class decorator factory"
@@ -257,15 +274,12 @@ def include(*mixins):
# usage as a Python < 2.6 class decorator
mcl = frame.f_locals.get("__metaclass__")
def makecls(name, bases, dic):
- if mcl:
- return new(name, bases, dic, mixins, (TOSMeta, mcl))
- else:
- return new(name, bases, dic, mixins, (TOSMeta,))
+ return new(mcl, name, bases, dic, mixins)
frame.f_locals["__metaclass__"] = makecls
else:
# usage as a Python >= 2.6 class decorator
def _include(cls):
- return new(cls.__name__, cls.__bases__, cls.__dict__.copy(),
- mixins, (TOSMeta,))
+ return new(cls.__class__, cls.__name__, cls.__bases__,
+ cls.__dict__.copy(), mixins)
_include.__name__ = 'include_%s>' % '_'.join(m.__name__ for m in mixins)
return _include