summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Drake <dsd@laptop.org>2013-07-09 13:03:36 -0600
committerSimon Feltman <sfeltman@src.gnome.org>2013-07-10 11:24:33 -0700
commita93755ddba9a1761b627583d7b9be63783c2c063 (patch)
tree0dae36903e1a453937f8dee61d330f5e30f9202d
parent7aca95781fc76f3e820e63325ccc07d128a60075 (diff)
downloadpygobject-a93755ddba9a176.tar.gz
Speed up MRO calculationa93755ddba9a176
Optimize gi.type.mro() with the following observations and tricks: 1. Python prepares all the base classes before trying to calculate the MRO of the current one (it first needs to populate __bases__, for example). So we can assume that the base class MRO is already available in __mro__ and this will have been previously calculated (by us, in the case of gi classes). This avoids repeating a lot of MRO-calculating work, and also avoids (re)calculating MROs for inheritance chains that don't have any gi classes in them anyway. 2. With that simplification in place, we can avoid recursion, which is not all that great in Python... 3. ...except in the uncommon case of a Python2 old-style classes, where __mro__ is not available. There doesn't seem to be any existing function to calculate or read MRO of old-style python classes, so just keep doing as before: calculate the C3 MRO of the old-style class via recursion. That behaviour is not really correct, and the recursion is not desirable, so we print a warning here. This makes the "hello world" Sugar app start up approximately 0.5 seconds faster on XO-1.5. https://bugzilla.gnome.org/show_bug.cgi?id=703829
-rw-r--r--gi/types.py30
-rw-r--r--tests/test_gi.py16
2 files changed, 39 insertions, 7 deletions
diff --git a/gi/types.py b/gi/types.py
index 47a81d84..69bb4944 100644
--- a/gi/types.py
+++ b/gi/types.py
@@ -23,6 +23,7 @@
from __future__ import absolute_import
import sys
+import warnings
from . import _gobject
from ._gobject._gobject import GInterface
@@ -309,17 +310,40 @@ class GObjectMeta(_gobject.GObjectMeta, MetaClassHelper):
def mro(C):
- """Compute the class precedence list (mro) according to C3
+ """Compute the class precedence list (mro) according to C3, with GObject
+ interface considerations.
+
+ We override Python's MRO calculation to account for the fact that
+ GObject classes are not affected by the diamond problem:
+ http://en.wikipedia.org/wiki/Diamond_problem
Based on http://www.python.org/download/releases/2.3/mro/
- Modified to consider that interfaces don't create the diamond problem
"""
# TODO: If this turns out being too slow, consider using generators
bases = []
bases_of_subclasses = [[C]]
if C.__bases__:
- bases_of_subclasses += list(map(mro, C.__bases__)) + [list(C.__bases__)]
+ for base in C.__bases__:
+ # Python causes MRO's to be calculated starting with the lowest
+ # base class and working towards the descendant, storing the result
+ # in __mro__ at each point. Therefore at this point we know that
+ # we already have our base class MRO's available to us, there is
+ # no need for us to (re)calculate them.
+ if hasattr(base, '__mro__'):
+ bases_of_subclasses += [list(base.__mro__)]
+ else:
+ warnings.warn('Mixin class %s is an old style class, please '
+ 'update this to derive from "object".' % base,
+ RuntimeWarning)
+ # For old-style classes (Python2 only), the MRO is not
+ # easily accessible. As we do need it here, we calculate
+ # it via recursion, according to the C3 algorithm. Using C3
+ # for old style classes deviates from Python's own behaviour,
+ # but visible effects here would be a corner case triggered by
+ # questionable design.
+ bases_of_subclasses += [mro(base)]
+ bases_of_subclasses += [list(C.__bases__)]
while bases_of_subclasses:
for subclass_bases in bases_of_subclasses:
diff --git a/tests/test_gi.py b/tests/test_gi.py
index 1d56faef..16e51a4b 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -2152,7 +2152,7 @@ class TestPythonGObject(unittest.TestCase):
self.assertTrue(isinstance(GObject, DynamicModule))
def test_subobject_non_vfunc_do_method(self):
- class PythonObjectWithNonVFuncDoMethod:
+ class PythonObjectWithNonVFuncDoMethod(object):
def do_not_a_vfunc(self):
return 5
@@ -2416,9 +2416,17 @@ class TestMRO(unittest.TestCase):
class Mixin:
pass
- # Dynamically create a new gi based class with an old
- # style mixin.
- type('GIWithOldStyleMixin', (GIMarshallingTests.Object, Mixin), {})
+ with warnings.catch_warnings(record=True) as warn:
+ warnings.simplefilter('always')
+
+ # Dynamically create a new gi based class with an old
+ # style mixin.
+ type('GIWithOldStyleMixin', (GIMarshallingTests.Object, Mixin), {})
+
+ if sys.version_info < (3, 0):
+ self.assertTrue(issubclass(warn[0].category, RuntimeWarning))
+ else:
+ self.assertEqual(len(warn), 0)
class TestInterfaceClash(unittest.TestCase):