From d183ab1c183581e42223399f3644d27186900dd6 Mon Sep 17 00:00:00 2001 From: qubesuser Date: Thu, 9 Nov 2017 03:15:37 +0100 Subject: [PATCH] cache PropertyHolder.property_list and use O(1) property name lookups --- qubes/__init__.py | 60 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/qubes/__init__.py b/qubes/__init__.py index 33e3d03d..c6264246 100644 --- a/qubes/__init__.py +++ b/qubes/__init__.py @@ -496,7 +496,7 @@ class PropertyHolder(qubes.events.Emitter): propvalues = {} - all_names = set(prop.__name__ for prop in self.property_list()) + all_names = self.property_dict() for key in list(kwargs): if not key in all_names: continue @@ -509,8 +509,6 @@ class PropertyHolder(qubes.events.Emitter): if self.xml is not None: # check if properties are appropriate - all_names = set(prop.__name__ for prop in self.property_list()) - for node in self.xml.xpath('./properties/property'): name = node.get('name') if name not in all_names: @@ -518,6 +516,39 @@ class PropertyHolder(qubes.events.Emitter): 'property {!r} not applicable to {!r}'.format( name, self.__class__.__name__)) + # pylint: disable=too-many-nested-blocks + @classmethod + def property_dict(cls, load_stage=None): + '''List all properties attached to this VM's class + + :param load_stage: Filter by load stage + :type load_stage: :py:func:`int` or :py:obj:`None` + ''' + + # use cls.__dict__ since we must not look at parent classes + if "_property_dict" not in cls.__dict__: + cls._property_dict = {} + memo = cls._property_dict + + if load_stage not in memo: + props = dict() + if load_stage is None: + for class_ in cls.__mro__: + for name in class_.__dict__: + # don't overwrite props with those from base classes + if name not in props: + prop = class_.__dict__[name] + if isinstance(prop, property): + assert name == prop.__name__ + props[name] = prop + else: + for prop in cls.property_dict().values(): + if prop.load_stage == load_stage: + props[prop.__name__] = prop + memo[load_stage] = props + + return memo[load_stage] + @classmethod def property_list(cls, load_stage=None): '''List all properties attached to this VM's class @@ -526,14 +557,15 @@ class PropertyHolder(qubes.events.Emitter): :type load_stage: :py:func:`int` or :py:obj:`None` ''' - props = set() - for class_ in cls.__mro__: - props.update(prop for prop in class_.__dict__.values() - if isinstance(prop, property)) - if load_stage is not None: - props = set(prop for prop in props - if prop.load_stage == load_stage) - return sorted(props) + # use cls.__dict__ since we must not look at parent classes + if "_property_list" not in cls.__dict__: + cls._property_list = {} + memo = cls._property_list + + if load_stage not in memo: + memo[load_stage] = sorted(cls.property_dict(load_stage).values()) + + return memo[load_stage] def _property_init(self, prop, value): '''Initialise property to a given value, without side effects. @@ -588,9 +620,9 @@ class PropertyHolder(qubes.events.Emitter): if isinstance(prop, qubes.property): return prop - for p in cls.property_list(): - if p.__name__ == prop: - return p + props = cls.property_dict() + if prop in props: + return props[prop] raise AttributeError('No property {!r} found in {!r}'.format( prop, cls))