[Python-ideas] Required to call superclass __init__
Neil Toronto
ntoronto at cs.byu.edu
Wed Nov 14 11:38:05 CET 2007
Since I know you're all dying to see the code... ;)
This works for instance methods, classmethods, staticmethods (if cls or
self is the first parameter, as in __new__), and probably most decorated
methods.
Current Issues I Can Think Of:
- @classmethod overrides that don't redecorate with @classmethod always
raise TypeError (maybe not such a bad thing)
- exceptions can cause entries in the __supercalls__ set to accumulate
unboundedly (can be fixed)
Anyway, here's a concrete implementation. Is the "missed super call"
problem big or annoying enough to warrant having language, runtime, or
library support for a similar solution?
import threading
import types
class _supercall_set(object):
def __init__(self, *args, **kwargs):
# Removing threading.local() should make this faster
# but not thread-safe
self.loc = threading.local()
self.loc.s = set(*args, **kwargs)
def add(self, key): self.loc.s.add(key)
def discard(self, key): self.loc.s.discard(key)
def __contains__(self, key): return self.loc.s.__contains__(key)
def __repr__(self): return self.loc.s.__repr__()
def _unwrap_rewrap(func):
'''For supported types (classmethod, staticmethod, function),
returns the actual function and a function to re-wrap it, if
necessary. Raises TypeError if func's type isn't supported.'''
if isinstance(func, classmethod):
return func.__get__(func).im_func, type(func)
elif isinstance(func, staticmethod):
return func.__get__(func), type(func)
elif isinstance(func, types.FunctionType):
return func, lambda func: func
raise TypeError('unsupported type %s' % type(func))
def super_required(func):
'''
Marks a method as requiring subclass overrides to call it, either
directly or via a super() call. Works with all undecorated
methods, classmethods, staticmethods (fragile: only if 'cls' or
'self' is the first parameter) including __new__, and probably
most other decorated methods. Correct operation is guaranteed only
when the method is in a subclass of require_super.
If a super_required override has a superclass method that is also
super_required, the override will not be required to call the
superclass method, either directly or via a super() call.
The superclass call requirement can be cancelled for a method and
methods of the same name in all future subclasses using the
super_not_required decorator.
The implementation should be as thread-safe as the classes it's
used in. Recursion should work as long as the last, innermost
call calls the superclass method. (It's usually best to avoid it.)
This is not robust to method injection, but then again, what is?
Examples:
class A(require_super):
@super_required
def __init__(self): pass
class B(A):
def __init__(self): pass
b = B() # TypeError: B.__init__: no super call
# B.__init__ needs a super(B, self).__init__()
class C(require_super):
@super_required
@classmethod # order of decorators doesn't matter
def clsmeth(cls): pass
class D(C):
@classmethod
def clsmeth(cls): pass
d = D()
d.clsmeth() # TypeError: D.clsmeth: no super call
# C.clsmeth needs a super(C, cls).clsmeth()
'''
func, rewrap = _unwrap_rewrap(func)
name = func.func_name
def super_wrapper(self_or_cls, *args, **kwargs):
retval = func(self_or_cls, *args, **kwargs)
# Flag that the super call happened
self_or_cls.__supercalls__.discard((id(self_or_cls), name))
return retval
super_wrapper.func_name = func.func_name
super_wrapper.func_doc = func.func_doc
super_wrapper.__super_required__ = True # Pass it down
return rewrap(super_wrapper)
def super_not_required(func):
'''Marks a method as no longer requiring subclass overrides to
call it. This is only meaningful for methods in subclasses of
require_super.'''
func.__super_required__ = False
return func
def _get_sub_wrapper(func, class_name, method_name):
'''Returns a wrapper function that:
1. Adds key to __supercalls__
2. Calls the wrapped function
3. Checks for key in __supercalls__ - if there, raises TypeError'''
def sub_wrapper(self_or_cls, *args, **kwargs):
key = (id(self_or_cls), method_name)
self_or_cls.__supercalls__.add(key)
retval = func(self_or_cls, *args, **kwargs)
if key not in self_or_cls.__supercalls__:
return retval
self_or_cls.__supercalls__.discard(key)
raise TypeError("%s.%s: no super call" %
(class_name, method_name))
sub_wrapper.func_name = func.func_name
sub_wrapper.func_doc = func.func_doc
sub_wrapper.__super_required__ = True # Pass it down
return sub_wrapper
class _require_super_meta(type):
def __new__(typ, cls_name, bases, dct):
# Search through all attributes
for method_name, func in dct.items():
try:
func, rewrap = _unwrap_rewrap(func)
except TypeError:
continue # unsupported type
if hasattr(func, '__super_required__'):
continue # decorated - don't wrap it again
# See if a base class's method is __super_required__
for base in bases:
try:
if getattr(base, method_name).__super_required__:
break
except AttributeError:
pass # not there or no __super_required__
else:
continue # outer loop
# Wrap up the function
newfunc = _get_sub_wrapper(func, cls_name, method_name)
dct[method_name] = rewrap(newfunc)
return type.__new__(typ, cls_name, bases, dct)
class require_super(object):
'''Inheriting from require_super makes super_required and
super_not_required decorators work.'''
__metaclass__ = _require_super_meta
# This will be visible to classes and instances
__supercalls__ = _supercall_set()
More information about the Python-ideas
mailing list