diff options
author | michele.simionato <devnull@localhost> | 2008-08-17 16:16:10 +0000 |
---|---|---|
committer | michele.simionato <devnull@localhost> | 2008-08-17 16:16:10 +0000 |
commit | bfc23a91358f8f9d38ad2c44353cf65d078981db (patch) | |
tree | 1bec5f72ddf0dcf59ca7d5f79af9b3ae1a890e09 /mtraits | |
parent | 31d64515be4345ce176793d232adc7301f400d79 (diff) | |
download | micheles-bfc23a91358f8f9d38ad2c44353cf65d078981db.tar.gz |
Committed release 0.3 of mtrait
Diffstat (limited to 'mtraits')
-rw-r--r-- | mtraits/doc.py | 113 | ||||
-rw-r--r-- | mtraits/mtrait.py | 48 |
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 |