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