Defining a method final
Jeff Epler
jepler at unpythonic.net
Tue Jun 11 12:20:40 EDT 2002
The following code uses a metaclass to attempt to enforce "final"
methods. Warning: Use of metaclasses has exploded brains. Accessing
directly through __dict__ or object.__setattr__ can bypass these
protections.
def finalmethod(f):
"""Marks a method as a final method. Use like classmethod(), etc. Only
works in subclasses of FinalMethods:
class C(FinalMethods):
def f(x): ...
f = finalmethod(f)"""
f.__final__ = 1
return f
class FinalMethodsMeta(type):
"""Metatype for FinalMethods"""
def __init__(cls, name, bases, dict):
super(FinalMethodsMeta, cls).__init__(name, bases, dict)
for base in cls.__mro__:
if base is cls: continue
for attrname in base.__dict__:
attr = base.__dict__[attrname]
if hasattr(attr, "__final__") and attr.__final__:
func = getattr(cls, attrname).im_func
print attrname, attr, func
if func is not attr:
raise ValueError, \
"Subclasses of %s may not override the method %s" \
% (base.__name__, attrname)
def __setattr__(cls, name, attr):
oldval = getattr(cls, name, None)
if hasattr(oldval, "__final__") and oldval.__final__:
raise ValueError, "final methods may not be overridden by assignment to class"
super(FinalMethodsMeta, cls).__setattr__(name, attr)
class FinalMethods:
"""Subclasses of FinalMethods may define a method to be a finalmethod().
This probably only works with instancemethods, not classmethods or
staticmethods. A subclass may not override a finalmethod"""
__metaclass__ = FinalMethodsMeta
def __setattr__(self, name, attr):
oldval = getattr(self, name, None)
if hasattr(oldval, "__final__") and oldval.__final__:
raise ValueError, "final methods may not be overridden by assignment to instance"
super(FinalMethods, self).__setattr__(name, attr)
def _test():
class c1(FinalMethods):
def f(x): pass
f = finalmethod(f)
def g(x): pass
class c2(FinalMethods):
def g(x): pass
g = finalmethod(g)
# Test that FinalMethods classes can be subclassed when they don't
# interfere with each others' final methods
class c3(c2, c1): pass
assert c3.f.im_func is c1.f.im_func
# Test that a FinalMethods class can be subclassed when the added
# methods do not interfere with the super's final methods
class c4(c1):
def g(x): pass
# Test that when a final method is concealed by a non-final method,
# it blows up
try:
class c5(c1, c2): pass
except ValueError, detail:
print "Caught expected ValueError:", detail
else:
print "Did not catch expected error"
print c5.g, c1.g, c2.g
raise RuntimeError
# Test that when a final method is overridden in the subclass, it
# blows up
try:
class c5(c1):
def f(x): pass
except ValueError, detail:
print "Caught expected ValueError:", detail
else:
print c5.f, c1.f
print "Did not catch expected error"
raise RuntimeError
# Test that when a final method is overridden in the sub-subclass,
# it blows up
try:
class c6(c3):
def f(x): pass
except ValueError, detail:
print "Caught expected ValueError:", detail
else:
print c6.f, c3.f
print "Did not catch expected error"
raise RuntimeError
# Test that assigning to non-final attributes works (class)
class c7(c1): pass
c7.g = lambda: None
# Test that assigning to final attributes fails (class)
try:
class c7(c3): pass
c7.f = None
except ValueError, detail:
print "Caught expected ValueError:", detail
else:
print "Did not catch expected error"
raise RuntimeError
# Test that assigning to non-final attributes works (instance)
i = c1()
i.g = None
# Test that assigning to final attributes fails (instance)
try:
i = c1();
i.f = None
except ValueError, detail:
print "Caught expected ValueError:", detail
else:
print "Did not catch expected error"
raise RuntimeError
print "seems ta work"
if __name__ == '__main__': _test()
More information about the Python-list
mailing list