Repository: cocotb/cocotb

Issue:
Monitor/BusMonitor kwargs
Currently BusMonitor and Monitor don't accept additional kwargs, meaning that you can't as easily instantiate and pass down arguments with a basic **kwargs. Adding **kwargs to the end of the definition would make life easier.


Discussion:
Can you give an example of what you want to be able to do here?
This is more a consistency issue as the drivers base classes pass kwargs in while the monitors don't. As long as thiggs are consistent so that writing classes is the same style, it makes things look better in higher level code 
> the drivers base classes pass kwargs

Can you link to the bit of code you're talking about?
Ah, I see:
```
    def __init__(self, entity, name, clock, **kwargs):
        self.log = SimLog("cocotb.%s.%s" % (entity._name, name))
        Driver.__init__(self)
        self.entity = entity
        self.clock = clock
        index = kwargs.get("array_idx")
        self.bus = Bus(self.entity, name, self._signals,
                       self._optional_signals, array_idx=index)

        # Give this instance a unique name
        self.name = name if index is None else "%s_%d" % (name, index)
```

I would consider this a bug in `BusDriver` - it should be spelt

```diff
 def __init__(self, entity, name, clock, **kwargs):
         Driver.__init__(self)
         self.entity = entity
         self.clock = clock
-        index = kwargs.get("array_idx")
+        index = kwargs.pop("array_idx")
+        if kwargs:
+            bad_arg = next(iter(kwargs))
+            raise TypeError('__init__() got an unexpected keyword argument {!r}'.format(bad_arg))
         self.bus = Bus(self.entity, name, self._signals,
                        self._optional_signals, array_idx=index)
```
Actually, [I caught this bug in review](https://github.com/potentialventures/cocotb/pull/708#discussion_r241969404), but the review was so unwieldy it got missed.

Resolving patch:
diff --git a/cocotb/drivers/__init__.py b/cocotb/drivers/__init__.py
--- a/cocotb/drivers/__init__.py
+++ b/cocotb/drivers/__init__.py
@@ -39,6 +39,7 @@
 from cocotb.bus import Bus
 from cocotb.log import SimLog
 from cocotb.result import ReturnValue
+from cocotb.utils import reject_remaining_kwargs
 
 
 class BitDriver(object):
@@ -220,11 +221,14 @@ class BusDriver(Driver):
     _optional_signals = []
 
     def __init__(self, entity, name, clock, **kwargs):
+        # emulate keyword-only arguments in python 2
+        index = kwargs.pop("array_idx", None)
+        reject_remaining_kwargs('__init__', kwargs)
+
         self.log = SimLog("cocotb.%s.%s" % (entity._name, name))
         Driver.__init__(self)
         self.entity = entity
         self.clock = clock
-        index = kwargs.get("array_idx")
         self.bus = Bus(self.entity, name, self._signals,
                        self._optional_signals, array_idx=index)
 
diff --git a/cocotb/utils.py b/cocotb/utils.py
--- a/cocotb/utils.py
+++ b/cocotb/utils.py
@@ -511,6 +511,31 @@ def __exit__(self, *excinfo):
         pass
 
 
+def reject_remaining_kwargs(name, kwargs):
+    """
+    Helper function to emulate python 3 keyword-only arguments.
+
+    Use as::
+
+        def func(x1, **kwargs):
+            a = kwargs.pop('a', 1)
+            b = kwargs.pop('b', 2)
+            reject_remaining_kwargs('func', kwargs)
+            ...
+
+    To emulate the Python 3 syntax::
+
+        def func(x1, *, a=1, b=2):
+            ...
+    """
+    if kwargs:
+        # match the error message to what python 3 produces
+        bad_arg = next(iter(kwargs))
+        raise TypeError(
+            '{}() got an unexpected keyword argument {!r}'.format(name, bad_arg)
+        )
+
+
 if __name__ == "__main__":
     import random
     a = ""
diff --git a/cocotb/wavedrom.py b/cocotb/wavedrom.py
--- a/cocotb/wavedrom.py
+++ b/cocotb/wavedrom.py
@@ -30,6 +30,7 @@
 import cocotb
 from cocotb.bus import Bus
 from cocotb.triggers import RisingEdge, ReadOnly
+from cocotb.utils import reject_remaining_kwargs
 
 
 class Wavedrom(object):
@@ -140,7 +141,9 @@ class trace(object):
     """
 
     def __init__(self, *args, **kwargs):
-        self._clock = kwargs.get("clk", None)
+        # emulate keyword-only arguments in python 2
+        self._clock = kwargs.pop("clk", None)
+        reject_remaining_kwargs('__init__', kwargs)
 
         self._signals = []
         for arg in args:


Test patch: