|
(accidently posted this as a comment to the previous post)
Perhaps like this:
import types
class Meta(type): ....def __init__(cls, name, bases, dict): ........super(Meta, cls).__init__(name, bases, dict) ........for fun in dict.values(): ............if isinstance(fun, types.FunctionType): ................print "Adding method " + fun.__name__
class Foo(object): ....__metaclass__ = Meta ....def bar(self): pass
# --> Adding method bar
Not as nice as the Ruby version, but then Ruby seems to explicitely provide the method_added hook.
julien |
09.09.05 - 2:03 pm | #
|
That works, although it's not exactly the same... all the methods are created first, then the messages are displayed after that. Not sure if there are non-trivial situations where this matters, though.
However, it doesn't work when you add a method to the class after it has been defined (which is common in Ruby, but less common in Python). I suppose that case can be caught by the __setattr__ hack.
Hans Nowak |
Homepage |
09.09.05 - 2:48 pm | #
|
I think julien's solution is probably the best that can be done in Python. The metaclass trick doesn't work because python has to read in the class before it can check the __metaclass__ variable to see what the metaclass is. By this point, all the methods have been defined, so the metaclass can only find out about them through introspection after the fact.
It's hard to imagine scenarios where the Python version couldn't do the same thing as the Ruby version. The only thing I can think of is a situation where you need to know the order in which the methods were defined. The python dictionary will scramble the order, so that is not recoverable.
The data models in django get around this by having the fields (whose order needs to be preserved) do some accounting in the background as you declare them. Not too far really from the decorator hack you mention.
Stan Seibert |
Homepage |
09.09.05 - 2:49 pm | #
|
You might also find class advisors that are used in Zope 3 interesting. James Henstridge recently wrote a very nice exposition on those:
http://blogs.gnome.org/view/jame...sh/2005/09/08/
0
Marius Gedminas |
Homepage |
09.09.05 - 3:15 pm | #
|
I think that you can also use sys.settrace to detect function definitions as soon as they're executed. Details are left as an excercise for the reader. Phillip J. Eby has implemented something very much like this in PEAK: http://dirtsimple.org/2004/11/us...-22-and-
23.html
Marius Gedminas |
Homepage |
09.09.05 - 3:26 pm | #
|
[comment moved here from #822, HN]
Here's a solution that will approximate the behavior you want.
When the Python interpreter evaluates a method in a class definition, the class object itself doesn't actually exist yet, as it appears to in Ruby.
Evaluating a class definition results in a 3-tuple: the class name, a tuple of its base classes, and a dictionary of name->attribute pairs. That 3-tuple is passed to the __new__ method of the metaclass, which is the built-in 'type' object by default.
With a custom metaclass, you can alter the makeup of the class object, including trapping method definitions, like this:
class Meta(type): def __new__(cls, name, bases, dct): for k,v in dct.iteritems() : if type(v) == FunctionType: print "Adding method %s" % k return type.__new__(cls, name, bases, dct)
def __setattr__(cls, k, v): if type(v) == FunctionType: print "Adding method %s" % k return type.__setattr__(cls, k, v)
class Foo(object): __metaclass__ = Meta
def __init__(self, foo): self.foo = foo
def bar(self): pass
x = Baz("foo") Baz.bazooey = lambda: True print Baz.bazooey
--
This will print:
Adding method bar Adding method __init__ Adding method bazooey
You need the meta __setattr__ function to capture new method definitions that occur after the class has been defined.
Hope this helps!
-Andy Gross
Andy Gross |
Homepage |
09.09.05 - 5:55 pm | #
|
The reason this doesn't work exactly as done in the Ruby example is that in Python the functions are all defined and created before the class. They are not added to the class, the class is initialized with a dictionary of all its methods and other attributes, which are all defined before hand.
Calvin M Spealman |
Homepage |
09.10.05 - 12:09 pm | #
|
It would probably be best to define a new 'magic' method in a metaclass like this(sorry, this will be in multiple posts, because of size limit):
import types
class MetaMethodChanger(type):
....def __new__(cls, name, bases, class_dict): ........changer = class_dict.get('__new_methods__') ........if isinstance(changer, types.FunctionType): ............class_dict['__new_methods__'] = staticmethod(changer) ............for name, obj in class_dict.items(): ................if isinstance(obj, types.FunctionType): ....................new_name, new_meth = changer(obj, name, obj.func_defaults) ....................del class_dict[name] ....................class_dict[new_name] = new_meth ........return type.__new__(cls, name, bases, class_dict)
....def __setattr__(cls, name, obj): ........changer = cls.__dict__.get('__new_methods__') ........if isinstance(changer, types.FunctionType): ............if isinstance(obj, types.FunctionType): ................name, obj = changer(obj, name, obj.func_defaults) ........type.__setattr__(cls, name, obj)
Žiga Seilnacht |
09.13.05 - 3:57 am | #
|
Then you can define a mix-in class:
class MethodChanger(object):
....__metaclass__ = MetaMethodChanger
....def __new_methods__(func, name, defaults): ........if not isinstance(func, types.FunctionType): ............raise TypeError ........if not isinstance(name, types.StringType): ............raise TypeError ........if defaults is not None: ............defaults = tuple(defaults) ............if not len(defaults) <= func.func_code.co_argcount: ................raise TypeError ........func.func_name = name ........func.func_defaults = defaults ........return name, func
and finaly:
class YourWish(MethodChanger):
....def __new_methods__(func, name, defaults): ........print name ........return MethodChanger.__new_methods__(func, name, defaults)
....def foo(self):pass
....def bar(self, x, y, z, a=1, b='SPAM', c=None):pass
or even:
class Example(MethodChanger):
....def __new_methods__(func, name, defaults): ........argcount = func.func_code.co_argcount ........argnames = func.func_code.co_varnames[:argcount] ........if defaults is None: ............print 'nn == method %s == nnarguments:' % name ............for arg in argnames: ................print arg ........else: ............defaultcount = len(defaults) ............without_defaults = argnames[:defaultcount] ............with_defaults = argnames[defaultcount + 1:] ............print 'nn == method %s == nnarguments:' % name ............for arg in without_defaults: ................print arg ............for arg, default_value in zip(with_defaults, defaults): ................print arg, '=', default_value ........return MethodChanger.__new_methods__(func, name, defaults)
....def foo(self):pass
....def bar(self, x, y, z, a=1, b='SPAM', c=None):pass
Žiga Seilnacht |
09.13.05 - 4:03 am | #
|
Sorry, two bugs in MetaMethodChanger; updated version:
class MetaMethodChanger(type):
....def __new__(cls, class_name, bases, class_dict): ........changer = class_dict.get('__new_methods__') ........if isinstance(changer, types.FunctionType): ............class_dict['__new_methods__'] = staticmethod(changer) ............for name, obj in class_dict.items(): ................if isinstance(obj, types.FunctionType): ....................new_name, new_meth = changer(obj, name, obj.func_defaults) ....................del class_dict[name] ....................class_dict[new_name] = new_meth ........return type.__new__(cls, class_name, bases, class_dict)
....def __setattr__(cls, name, obj): ........changer = getattr(cls, '__new_methods__', None) ........if isinstance(changer, types.FunctionType): ............if isinstance(obj, types.FunctionType): ................name, obj = changer(obj, name, obj.func_defaults) ........type.__setattr__(cls, name, obj)
Žiga Seilnacht |
09.13.05 - 4:58 am | #
|