ActiveState Code

Recipe 361167: A meta-class that provides class behavior like Ruby


It brings Ruby-like class behavior. When a class is declared, it extends the old class if the class name exists already.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import new
import inspect

class RubyMetaClass(type):
    """
    """
    def __new__(self, classname, classbases, classdict):
        try:
            frame = inspect.currentframe()
            frame = frame.f_back
            if frame.f_locals.has_key(classname):
                old_class = frame.f_locals.get(classname)
                for name,func in classdict.items():
                    if inspect.isfunction(func):
                        setattr(old_class, name, func)
                return old_class
            return type.__new__(self, classname, classbases, classdict)
        finally:
            del frame

class RubyObject(object):
    """
    >>> class C:
    ...   def foo(self): return "C.foo"
    ...
    >>> c = C()
    >>> print c.foo()
    C.foo
    >>> class C(RubyObject):
    ...   def bar(self): return "C.bar"
    ...
    >>> print c.bar()
    C.bar
    """
    __metaclass__ = RubyMetaClass

Discussion

This meta-class helps adding methods to class which declared already. I wrote this for a practice of how to use meta-class and frame object. Meta-class hooks the class declaring, and it modifies class in caller's scope through frame object.

known issue: - Thise class names should become a general word that explain the role. I just named 'Ruby...' for a demo of ruby-like class rule. - Others may be confused by the different class rule. Most of python users expects the new class declaration overrides an old class.

Comments

  1. 1. At 5:26 a.m. on 31 dec 2004, Marek Baczynski said:

    try...finally? Is

    finally:
        del frame
    

    really needed? If so, why?

  2. 2. At 10:25 a.m. on 31 dec 2004, Phillip J. Eby said:

    This metaclass will not co-operate with other metaclasses, nor is it subclassable. ...because it expects __new__ to be called from the frame where the function is defined. But, if you subclass this metaclass, __new__ will be called via 'super()' in the subclass, so this will inspect the wrong frame.

    This problem isn't fixable within a metaclass; the only way to fix it is to use an explicit metaclass that wraps the real metaclass, or conversely to use a "class advisor" function (see PyProtocols' 'protocols.advice' module, or Zope 3's 'zope.interface.advice' module). Such advisor functions can identify the correct frame before the class is even constructed, and then get a callback with the constructed class.

    A class advisor isn't inherited, so you have to use it in each class you want to be updateable, but the approach is combinable with other metaclasses and advisors, while the technique shown here will not work correctly with other metaclasses.

  3. 3. At 7:12 a.m. on 2 jan 2005, Ikkei Shimomura (the author) said:

    Ans: It's for GC. I read so that in the documentation, http://docs.python.org/lib/inspect-stack.html

  4. 4. At 7:12 a.m. on 2 jan 2005, Ikkei Shimomura (the author) said:

    with other metaclasses? I did not know how to do it. Thanks for the information, pyprotocols and zope's code, I had not ever seen them. Both projects has interesting codes I have to learn.

    About the stack frame scope, that was as I expected. But, I haven't seen the exception case, when the '__new__' is called by 'super', Can I see the minimum code ?

    Sub-classing, I've tested was ...

    class C: def foo(self): print "C.foo method is called" class C(RubyObject): def bar(self): print "C.bar method is called" class D(C): pass d = D() class D(RubyObject): def baz(self): print "D.baz method is called" d.foo() d.bar() d.baz()

    and it worked in this case.

    But I am not sure it with other metaclasses. about multi meta-classes, and how it works. When I declared '__metaclass__' with subclass of RubyObject, it just shown this error:

    TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

    This is seem another problem.

Sign in to comment