Welcome, guest | Sign In | My Account | Store | Cart

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

Python, 35 lines
 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

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.

4 comments

Marek Baczynski 19 years, 3 months ago  # | flag

try...finally? Is

finally:
    del frame

really needed? If so, why?

Phillip J. Eby 19 years, 3 months ago  # | flag

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.

Ikkei Shimomura (author) 19 years, 3 months ago  # | flag

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

Ikkei Shimomura (author) 19 years, 3 months ago  # | flag

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.