I have a setup where I have a set of classes that I want to mock, my idea was that in the cases where I want to do this I pass a mock
keyword argument into the constructor and in __new__
intercept this and instead pass back a mocked version of that object.
It looks like this (Edited the keyword lookup after @mgilsons suggestion):
class RealObject(object):
def __new__(cls, *args, **kwargs):
if kwargs.pop('mock', None):
return MockRealObject()
return super(RealObect, cls).__new__(cls, *args, **kwargs)
def __init__(self, whatever = None):
'''
Constructor
'''
#stuff happens
I then call the constructor like this:
ro = RealObject(mock = bool)
The issue I have here is that I get the following error when bool
is False
:
TypeError: __init__() got an unexpected keyword argument 'mock'
This works if I add mock
as a keyword argument to __init__
but what I am asking if this is possible to avoid. I even pop the mock
from the kwargs
dict
.
This is also a question about the design. Is there a better way to do this? (of course!) I wanted to try doing it this way, without using a factory or a superclass or anything. But still, should I use another keyword maybe? __call__
?
So I wanted to extract the metaclass and the __new__
function into a separate module. I did this:
class Mockable(object):
def __new__(cls, *args, **kwargs):
if kwargs.pop('mock', None):
mock_cls = eval('{0}{1}'.format('Mock',cls.__name__))
return super(mock_cls, mock_cls).__new__(mock_cls)
return super(cls, cls).__new__(cls,*args, **kwargs)
class MockableMetaclass(type):
def __call__(self, *args, **kwargs):
obj = self.__new__(self, *args, **kwargs)
if "mock" in kwargs:
del kwargs["mock"]
obj.__init__(*args, **kwargs)
return obj
And I have defined in a separate module the classes RealObject
and MockRealObject
.
I have two problems now:
MockableMetaclass
and Mockable
are not in the same module as the RealObject
class the eval
will raise a NameError
if I provide mock = True
.mock = False
the code will enter into an endless recursion that ends in an impressive RuntimeError: maximum recursion depth exceeded while calling a Python objec
. I'm guessing this is due to RealObject
's superclass no longer being object
but instead Mockable
.How can I fix these problems? is my approach incorrect? Should I instead have Mockable
as a decorator? I tried that but that didn't seem to work since __new__
of an instance is only read-only it seems.
In Python, we can pass a variable number of arguments to a function using special symbols. There are two special symbols: *args (Non Keyword Arguments) **kwargs (Keyword Arguments)
Keyword-only arguments are another attribute of Python functions that have been available since Python 3.0. These arguments are specified using the '*' marker. They prompt the user to state the keyword used in the already defined function when making a call to the same function.
__new__ is static class method, while __init__ is instance method. __new__ has to create the instance first, so __init__ can initialize it. Note that __init__ takes self as parameter. Until you create instance there is no self . Now, I gather, that you're trying to implement singleton pattern in Python.
Unexpected keyword argument %r in %s call. Description: Used when a function call passes a keyword argument that doesn't correspond to one of the function's parameter names.
This is a job for the metaclass! :-)
The code responsible to call both __new__
and __init__
when instantiating a Python new-style object lies in the __call__
method for the class metaclass. (or the semantically equivalent to that).
In other words - when you do:
RealObject()
- what is really called is the RealObject.__class__.__call__
method.
Since without declaring a explicit metaclass, the metaclass is type
, it is type.__call__
which is called.
Most recipes around dealing with metaclasses deal with subclassing the __new__
method - automating actions when the class is created. But overriding __call__
we can take actions when the class is instantiated, instead.
In this case, all that is needed is to remove the "mock" keyword parameter, if any, before calling __init__
:
class MetaMock(type):
def __call__(cls, *args, **kw):
obj = cls.__new__(cls, *args, **kw)
if "mock" in kw:
del kw["mock"]
obj.__init__(*args, **kw)
return obj
class RealObject(metaclass=MetaMock):
...
A subclass is pretty much essential, since __new__
always passes the arguments to the constructor call to the __init__
method. If you add a subclass via a class decorator as a mixin then you can intercept the mock
argument in the subclass __init__
:
def mock_with(mock_cls):
class MockMixin(object):
def __new__(cls, *args, **kwargs):
if kwargs.pop('mock'):
return mock_cls()
return super(MockMixin, cls).__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
kwargs.pop('mock')
super(MockMixin, self).__init__(*args, **kwargs)
def decorator(real_cls):
return type(real_cls.__name__, (MockMixin, real_cls), {})
return decorator
class MockRealObject(object):
pass
@mock_with(MockRealObject)
class RealObject(object):
def __init__(self, whatever=None):
pass
r = RealObject(mock=False)
assert isinstance(r, RealObject)
m = RealObject(mock=True)
assert isinstance(m, MockRealObject)
The alternative is for the subclass __new__
method to return RealObject(cls, *args, **kwargs)
; in that case, since the returned object isn't an instance of the subclass. However in that case the isinstance
check will fail.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With