[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