Przeglądaj źródła

pygrass: Add update parameters method to Module (#1712)

This adds a new update method which can update the parameters (and generally attributes) of the Module.
The current practice is to use the function call operator to do the update. This seems to go against
the function call operator semantics as the module is not executed unless explicit `run_=True` is passed.
It is also inconsistent with using the function call operator without parameters which runs the module unconditionally.

This PR is not changing the current behavior, but it adds a better way of expressing the same idea. The modification of parameters can now be done using a method called _update_. The name _update_ is based on update function in Python dict since this seems to be a similar case.

The change in doctest and in _grass.benchmark_ (original motivation for this change) shows how the new usage looks like. _grass.benchmark_ update of documentation shows also how clear is the interface with update compared to the very specific original interface, i.e., it is easier to create another class which will behave same way as Module in this context.
Vaclav Petras 3 lat temu
rodzic
commit
4daf1023e0

+ 18 - 5
python/grass/benchmark/runners.py

@@ -23,7 +23,13 @@ import grass.script as gs
 def benchmark_nprocs(module, label, max_nprocs, repeat):
     """Benchmark module using values of nprocs up to *max_nprocs*.
 
-    *module* is an instance of PyGRASS Module class.
+    *module* is an instance of PyGRASS Module class or any object which
+    has a *update* method taking *nprocs* as a keyword argument,
+    a *run* which takes no arguments and executes the benchmarked code,
+    and attribute *time* which is set to execution time after the *run*
+    function returned. Additionally, the object should be convertible to *str*
+    for printing.
+
     The module is executed  used to generate range of values from 1 up to *max_nprocs*.
     *repeat* sets how many times the each run is repeated.
     So, the module will run ``max_nprocs * repeat`` times.
@@ -36,7 +42,10 @@ def benchmark_nprocs(module, label, max_nprocs, repeat):
     (list of *nprocs* values used), and *label* (the provided parameter as is).
     """
     term_size = shutil.get_terminal_size()
-    print(module.get_bash())
+    if hasattr(module, "get_bash"):
+        print(module.get_bash())
+    else:
+        print(module)
 
     min_avg = float("inf")
     min_time = 1
@@ -49,7 +58,8 @@ def benchmark_nprocs(module, label, max_nprocs, repeat):
         time_sum = 0
         measured_times = []
         for _ in range(repeat):
-            module(nprocs=nprocs).run()
+            module.update(nprocs=nprocs)
+            module.run()
             print(f"{module.time}s")
             time_sum += module.time
             measured_times.append(module.time)
@@ -79,7 +89,10 @@ def benchmark_nprocs(module, label, max_nprocs, repeat):
 def benchmark_resolutions(module, resolutions, label, repeat=5, nprocs=None):
     """Benchmark module using different resolutions.
 
-    *module* is an instance of PyGRASS Module class.
+    *module* is an instance of PyGRASS Module class or any object
+    with attributes as specified in :func:`benchmark_nprocs`
+    except that the *update* method is required only when *nprocs* is set.
+
     *resolutions* is a list of resolutions to set (current region is currently
     used and changed but that may change in the future).
     *repeat* sets how many times the each run is repeated.
@@ -109,7 +122,7 @@ def benchmark_resolutions(module, resolutions, label, repeat=5, nprocs=None):
         measured_times = []
         for _ in range(repeat):
             if nprocs:
-                module(nprocs=nprocs)
+                module.update(nprocs=nprocs)
             module.run()
             print(f"{module.time}s")
             time_sum += module.time

+ 31 - 26
python/grass/pygrass/modules/interface/module.py

@@ -445,34 +445,30 @@ class Module(object):
     >>> colors.returncode
     0
 
-    Multiple run test
+    Run many times and change parameters for each run
 
-    >>> colors = Module("r.colors", map="test_a",
-    ...                                            color="ryb", run_=False)
+    >>> colors = Module("r.colors", map="test_a", color="ryb", run_=False)
     >>> colors.get_bash()
     'r.colors map=test_a color=ryb offset=0.0 scale=1.0'
     >>> colors.run()
     Module('r.colors')
-    >>> colors(color="gyr")
-    Module('r.colors')
+    >>> colors.update(color="gyr")
     >>> colors.run()
     Module('r.colors')
-    >>> colors(color="ryg")
-    Module('r.colors')
-    >>> colors(stderr_=PIPE)
-    Module('r.colors')
+    >>> colors.update(color="ryg")
+    >>> colors.update(stderr_=PIPE)
     >>> colors.run()
     Module('r.colors')
     >>> print(colors.outputs["stderr"].value.strip())
     Color table for raster map <test_a> set to 'ryg'
-    >>> colors(color="byg")
-    Module('r.colors')
-    >>> colors(stdout_=PIPE)
-    Module('r.colors')
+    >>> colors.update(color="byg")
+    >>> colors.update(stdout_=PIPE)
     >>> colors.run()
     Module('r.colors')
     >>> print(colors.outputs["stderr"].value.strip())
     Color table for raster map <test_a> set to 'byg'
+    >>> colors.get_bash()
+    'r.colors map=test_a color=byg offset=0.0 scale=1.0'
 
     Often in the Module class you can find ``*args`` and ``kwargs`` annotation
     in methods, like in the __call__ method.
@@ -624,6 +620,27 @@ class Module(object):
             self.run()
             return self
 
+        self.update(*args, **kargs)
+
+        #
+        # check if execute
+        #
+        if self.run_:
+            #
+            # check reqire parameters
+            #
+            if self.check_:
+                self.check()
+            return self.run()
+        return self
+
+    def update(self, *args, **kargs):
+        """Update module parameters and selected object attributes.
+
+        Valid parameters are all the module parameters
+        and additional parameters, namely: run_, stdin_, stdout_, stderr_,
+        env_, and finish_.
+        """
         #
         # check for extra kargs, set attribute and remove from dictionary
         #
@@ -632,7 +649,7 @@ class Module(object):
                 self.flags[flg].value = True
             del kargs["flags"]
 
-        # set attributs
+        # set attributes
         for key in ("run_", "env_", "finish_", "stdout_", "stderr_", "check_"):
             if key in kargs:
                 setattr(self, key, kargs.pop(key))
@@ -660,18 +677,6 @@ class Module(object):
             else:
                 raise ParameterError("%s is not a valid parameter." % key)
 
-        #
-        # check if execute
-        #
-        if self.run_:
-            #
-            # check reqire parameters
-            #
-            if self.check_:
-                self.check()
-            return self.run()
-        return self
-
     def get_bash(self):
         """Return a BASH representation of the Module."""
         return " ".join(self.make_cmd())