Skip to content

Commit 6e27aac

Browse files
committed
Fix bug in super() with metaclasses
1 parent 4657ad2 commit 6e27aac

File tree

2 files changed

+47
-36
lines changed

2 files changed

+47
-36
lines changed

Diff for: src/future/builtins/newsuper.py

+35-36
Original file line numberDiff line numberDiff line change
@@ -60,51 +60,50 @@ def newsuper(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1):
6060
raise RuntimeError('super() used in a function with no args')
6161

6262
try:
63-
# Get the MRO so we can crawl it.
64-
mro = type_or_obj.__mro__
65-
except (AttributeError, RuntimeError): # see issue #160
63+
typ = find_owner(type_or_obj, f.f_code)
64+
except (AttributeError, RuntimeError, TypeError):
65+
# see issues #160, #267
6666
try:
67-
mro = type_or_obj.__class__.__mro__
67+
typ = find_owner(type_or_obj.__class__, f.f_code)
6868
except AttributeError:
69-
raise RuntimeError('super() used with a non-newstyle class')
70-
71-
# A ``for...else`` block? Yes! It's odd, but useful.
72-
# If unfamiliar with for...else, see:
73-
#
74-
# http://psung.blogspot.com/2007/12/for-else-in-python.html
75-
for typ in mro:
76-
# Find the class that owns the currently-executing method.
77-
for meth in typ.__dict__.values():
78-
# Drill down through any wrappers to the underlying func.
79-
# This handles e.g. classmethod() and staticmethod().
80-
try:
81-
while not isinstance(meth,FunctionType):
82-
if isinstance(meth, property):
83-
# Calling __get__ on the property will invoke
84-
# user code which might throw exceptions or have
85-
# side effects
86-
meth = meth.fget
87-
else:
88-
try:
89-
meth = meth.__func__
90-
except AttributeError:
91-
meth = meth.__get__(type_or_obj, typ)
92-
except (AttributeError, TypeError):
93-
continue
94-
if meth.func_code is f.f_code:
95-
break # Aha! Found you.
96-
else:
97-
continue # Not found! Move onto the next class in MRO.
98-
break # Found! Break out of the search loop.
99-
else:
100-
raise RuntimeError('super() called outside a method')
69+
raise RuntimeError('super() used with an old-style class')
70+
except TypeError:
71+
raise RuntimeError('super() called outside a method')
10172

10273
# Dispatch to builtin super().
10374
if type_or_obj is not _SENTINEL:
10475
return _builtin_super(typ, type_or_obj)
10576
return _builtin_super(typ)
10677

10778

79+
def find_owner(cls, code):
80+
'''Find the class that owns the currently-executing method.
81+
'''
82+
for typ in cls.__mro__:
83+
for meth in typ.__dict__.values():
84+
# Drill down through any wrappers to the underlying func.
85+
# This handles e.g. classmethod() and staticmethod().
86+
try:
87+
while not isinstance(meth,FunctionType):
88+
if isinstance(meth, property):
89+
# Calling __get__ on the property will invoke
90+
# user code which might throw exceptions or have
91+
# side effects
92+
meth = meth.fget
93+
else:
94+
try:
95+
meth = meth.__func__
96+
except AttributeError:
97+
meth = meth.__get__(cls, typ)
98+
except (AttributeError, TypeError):
99+
continue
100+
if meth.func_code is code:
101+
return typ # Aha! Found you.
102+
# Not found! Move onto the next class in MRO.
103+
104+
raise TypeError
105+
106+
108107
def superm(*args, **kwds):
109108
f = sys._getframe(1)
110109
nm = f.f_code.co_name

Diff for: tests/test_future/test_super.py

+12
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,18 @@ class Elite(Dangerous):
170170

171171
self.assertEqual(Elite().walk(), 'Defused')
172172

173+
def test_metaclass(self):
174+
class Meta(type):
175+
def __init__(cls, name, bases, clsdict):
176+
super().__init__(name, bases, clsdict)
177+
178+
try:
179+
class Base(object):
180+
__metaclass__ = Meta
181+
except Exception as e:
182+
self.fail('raised %s with a custom metaclass'
183+
% type(e).__name__)
184+
173185

174186
class TestSuperFromTestDescrDotPy(unittest.TestCase):
175187
"""

0 commit comments

Comments
 (0)