[Types-sig] Static Methods instead of Interface construct?

Robin Thomas robin.thomas@starmedia.net
Sat, 24 Mar 2001 22:03:51 -0500


At 06:26 AM 3/22/01 -0500, Clark C. Evans wrote:

>Ok... this is a bit off-the-wall, but here goes, please
>excuse the semi-PEP format, but I find the structure
>helpful for fleshing things out (kinda interesting how
>a bit of structure helps you along, no?)

Geez, Clark. You're writing a lot. Get some sleep!

>Abstract
>
>     So far static methods[0] do not exist in Python
>     This proposal asks that None be allowed when calling a
>     method using the class syntax, e.g.,  the following
>     syntax would be allowed, MyClass.method(None,...).  Of
>     course, it will be no longer guaranteed that a "self"
>     references within a method is an instance, however, with
>     this additional flexibility comes power.

I like the idea, but not the calling syntax.

You know what I like better?

class MyClass:
     def __init__(self): pass
         # an instance method
     def method(i_forgot_to_use_self):
         # another instance method

     def static_method(None):
         # None considered a special name,
         # thus would be called as MyClass.static_method()

     def static_and_argumentless():
         # No arguments means it's just a freaking function,
         # and needs no special treatment

I like this a *lot*, with respect to expressiveness and syntax. None very 
concisely says, "No instance is involved, as you might expect with other 
methods. Other, poorer languages force you to call these kinds of methods 
'static', but it's really just a class method that you call like a 
function. This has been your OOP mini-lesson for today. Thank you for using 
Python."

The implementation details will determine the fitness of the proposal, I 
believe. None in the example is not an object, but a *name*, and the 
function's code object is compiled to expect an argument which it will 
assign to the name None. If all this happens at run-time, this will have to 
happen (in CPython pseudo-code):

def instance_attrlookup(instance, name):
     if instance.__dict__.has_key(name):
         return instance.__dict__[name]
     # let exceptions propagate from class_attrlookup
     attr, klass = class_attrlookup(instance.__class__, name)
     if type(attr) is types.FunctionType:
         # here we go: new run-time operations
         c = attr.func_code
         if not c.co_argcount:
             # function has *no* args, so it can't be a method
             do_something_undefined() or raise Exception
         if c.co_varnames[0] == 'None':
             raise Exception, "method %s not an instance method" % repr(name)
         return boundmethod(attr, klass, instance)

# now...

class staticmethod:
     def __init__(self, func, klass):
         self.im_func = func
         self.__name__ = "%s.%s" % klass.__name__, func.__name__

     def __call__(self, *args, **kw):
         # align argument counts, making sure
         # that None is bound to None object
         args = (None,) + args
         return apply(self.im_func, args, kw)

class unboundmethod:
     # no changes in implementation

# and...

def class_attrlookup(klass, name, recursive=0):
     if klass.__dict__.has_key(name):
         return klass.__dict__[name], klass
     for base in klass.__bases__:
         result = class_attrlookup(base, name, 1)
         if result: return result
     if recursive: return None
     else: raise AttributeError, name

# but...

def class_getattr(klass, name):
     if not klass.__dict__.has_key(name):
         raise AttributeError, name
     attr = klass.__dict__[name]
     if type(attr) is not types.FunctionType: return attr
     c = attr.func_code
     if not c.co_argcount: return attr
     if c.co_varnames[0] == 'None':
         return staticmethod(attr, klass)
     # if a non-static method, give an unbound method
     return unboundmethod(attr, klass)


Note that the side effect is that, in static methods, None is re-bound to 
the None object. Not too wasteful, but if some sucker actually had bound 
the name None to a custom object in the global namespace or in the closure 
namespace, the static method rules prevent him from using it. That doesn't 
seem too bad a thing, since rebinding None is Considered Harmful by some.

As a fringe benefit, None gets local scope in static methods, resulting in 
quicker lookups! And if Python ever promotes None to a keyword, the 
implementation of static methods may change (to a compile-time operation), 
but the expression of them does not.

Marcin's "allow any object, let the errors happen" suggestion attacks the 
reasoning behind the current implementation of method building. In the 
current implementation, if you use a method incorrectly, Python tells you 
that you used it incorrectly, instead of letting some frame way down in the 
call stack make some irrelevant complaint. I value that feature highly; it 
throws the right exception at the right time, without forcing the method 
author to write his own type-checking of self or requiring the execution of 
other frames of code. This may be just a matter of taste.

On a related subject: it seems dorky that bound, unbound, and now static 
methods need to be of different built-in types. It seems to be a matter of 
object value that determines a method's role in life, rather than object type.

class Method:
     def __init__(self, im_func, im_class=None, im_self=None):
         d = self.__dict__
         # assume no need for full type-checking
         d['im_func'] = im_func
         if im_class is None and im_self is not None:
             raise ValueError, "im_class must be a class if im_self is not 
None"
         d['im_class'] = im_class
         d['im_self'] = im_self

     def __setattr__(self, name, value):
         raise AttributeError, "methods are not mutable (but could be)"

     def __call__(self, *args, **kwargs):
         f,c,s = self.im_func, self.im_class, self.im_self
         if c is None:
             # static method
             args = (None,) + args
             return apply(f, args, kwargs)
         if s is None:
             # unbound method
             if not args or not isinstance(args[0], c):
                 raise ValueError, "unbound method instance blah blah"
             return apply(f, args, kwargs)
         # bound method
         args = (s,) + args
         return apply(f ,args, kwargs)

     def __repr__(self):
         f,c,s = self.im_func, self.im_class, self.im_self
         if c is None:
              return "<static method %s>" % repr(f.__name__)
         if s is None:
              return "<unbound method %s>" % repr(f.__name__)
         return "<instance method %s>" % repr(f.__name__)

Thoughts?


--
Robin Thomas
Engineering
StarMedia Network, Inc.
robin.thomas@starmedia.net