Pārlūkot izejas kodu

cache PropertyHolder.property_list and use O(1) property name lookups

qubesuser 6 gadi atpakaļ
vecāks
revīzija
d183ab1c18
1 mainītis faili ar 46 papildinājumiem un 14 dzēšanām
  1. 46 14
      qubes/__init__.py

+ 46 - 14
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))