multimethods: avoid calling caller_locals() in Python 3 (#13238)
Python 3 metaclasses have a `__prepare__` method that lets us save the class's dictionary before it is constructed. In Python 2 we had to walk up the stack using our `caller_locals()` method to get at this. Using `__prepare__` is much faster as it doesn't require us to use `inspect`. This makes multimethods use the faster `__prepare__` method in Python3, while still using `caller_locals()` in Python 2. We try to reduce the use of caller locals using caching to speed up Python 2 a little bit.
This commit is contained in:
		| @@ -24,6 +24,7 @@ | ||||
| depending on the scenario, regular old conditionals might be clearer, | ||||
| so package authors should use their judgement. | ||||
| """ | ||||
|  | ||||
| import functools | ||||
| import inspect | ||||
|  | ||||
| @@ -34,6 +35,24 @@ | ||||
| from spack.spec import Spec | ||||
|  | ||||
|  | ||||
| class MultiMethodMeta(type): | ||||
|     """This allows us to track the class's dict during instantiation.""" | ||||
|  | ||||
|     #: saved dictionary of attrs on the class being constructed | ||||
|     _locals = None | ||||
|  | ||||
|     @classmethod | ||||
|     def __prepare__(cls, name, bases, **kwargs): | ||||
|         """Save the dictionary that will be used for the class namespace.""" | ||||
|         MultiMethodMeta._locals = dict() | ||||
|         return MultiMethodMeta._locals | ||||
|  | ||||
|     def __init__(cls, name, bases, attr_dict): | ||||
|         """Clear out the cached locals dict once the class is built.""" | ||||
|         MultiMethodMeta._locals = None | ||||
|         super(MultiMethodMeta, cls).__init__(name, bases, attr_dict) | ||||
|  | ||||
|  | ||||
| class SpecMultiMethod(object): | ||||
|     """This implements a multi-method for Spack specs.  Packages are | ||||
|        instantiated with a particular spec, and you may want to | ||||
| @@ -149,14 +168,15 @@ class SomePackage(Package): | ||||
|               def install(self, prefix): | ||||
|                   # Do default install | ||||
|  | ||||
|               @when('arch=chaos_5_x86_64_ib') | ||||
|               @when('target=x86_64:') | ||||
|               def install(self, prefix): | ||||
|                   # This will be executed instead of the default install if | ||||
|                   # the package's platform() is chaos_5_x86_64_ib. | ||||
|                   # the package's target is in the x86_64 family. | ||||
|  | ||||
|               @when('arch=bgqos_0") | ||||
|               @when('target=ppc64:') | ||||
|               def install(self, prefix): | ||||
|                   # This will be executed if the package's sys_type is bgqos_0 | ||||
|                   # This will be executed if the package's target is in | ||||
|                   # the ppc64 family | ||||
|  | ||||
|        This allows each package to have a default version of install() AND | ||||
|        specialized versions for particular platforms.  The version that is | ||||
| @@ -202,11 +222,14 @@ def __init__(self, condition): | ||||
|             self.spec = Spec(condition) | ||||
|  | ||||
|     def __call__(self, method): | ||||
|         # Get the first definition of the method in the calling scope | ||||
|         original_method = caller_locals().get(method.__name__) | ||||
|         # In Python 2, Get the first definition of the method in the | ||||
|         # calling scope by looking at the caller's locals. In Python 3, | ||||
|         # we handle this using MultiMethodMeta.__prepare__. | ||||
|         if MultiMethodMeta._locals is None: | ||||
|             MultiMethodMeta._locals = caller_locals() | ||||
|  | ||||
|         # Create a multimethod out of the original method if it | ||||
|         # isn't one already. | ||||
|         # Create a multimethod with this name if there is not one already | ||||
|         original_method = MultiMethodMeta._locals.get(method.__name__) | ||||
|         if not type(original_method) == SpecMultiMethod: | ||||
|             original_method = SpecMultiMethod(original_method) | ||||
|  | ||||
|   | ||||
| @@ -42,6 +42,7 @@ | ||||
| import spack.hooks | ||||
| import spack.mirror | ||||
| import spack.mixins | ||||
| import spack.multimethod | ||||
| import spack.repo | ||||
| import spack.url | ||||
| import spack.util.web | ||||
| @@ -138,7 +139,8 @@ def copy(self): | ||||
|  | ||||
| class PackageMeta( | ||||
|     spack.directives.DirectiveMeta, | ||||
|     spack.mixins.PackageMixinsMeta | ||||
|     spack.mixins.PackageMixinsMeta, | ||||
|     spack.multimethod.MultiMethodMeta | ||||
| ): | ||||
|     """ | ||||
|     Package metaclass for supporting directives (e.g., depends_on) and phases | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Todd Gamblin
					Todd Gamblin