Pass the keyword argument only for __new __ () and never continue until __init __ ()?

Part 1

I have a setup where I have a set of classes that I want to mock, I thought that in cases where I want to do this, I pass the keyword argument mock

to the constructor and to __new__

intercept this and instead put back the mocked version of this object.

It looks like this ( Edited keyword search 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

      

Then I call the constructor like this:

ro = RealObject(mock = bool)

      

The problem I am having here is that I am getting the following error when bool

there is False

:

TypeError: __init__() got an unexpected keyword argument 'mock'

It works if I add the mock

keyword to as an argument __init__

, but I ask if this can be avoided. I even deduce mock

from kwargs

dict

.

It's also a design question. Is there a better way to do this? (of course!) I wanted to try to do it this way, without using a factory or superclass or anything else. But still, should I use a different keyword? __call__

?

Part 2 based on jsbueno answer

So, I wanted to extract the metaclass and function __new__

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:

  • If MockableMetaclass

    and are Mockable

    not in the same module as the class RealObject

    , it eval

    will raise NameError

    if I have provided mock = True

    .
  • If mock = False

    , the code goes into infinite recursion that ends up impressive RuntimeError: maximum recursion depth exceeded while calling a Python objec

    . I assume this is because the RealObject

    superclass is not object

    , but instead Mockable

    .

How can I fix these problems? is my approach wrong? Should I Mockable

be a decorator instead ? I tried this but it doesn't seem to work as the __new__

instance is read-only.

+3


source to share


2 answers


This assignment for __metaclass__

: -)

The code responsible for both calling __new__

and __init__

creating a new Python style object lies in the method __call__

for the class metaclass. (or semantically equivalent to this).

In other words - when you do:

RealObject()

- what is really called is a method RealObject.__class__.__call__

. Because without declaring an explicit metaclass, metaclass type

, it is called type.__call__

.



Most of the recipes associated with metaclasses are related to subclassing a method __new__

- automating actions when creating a class. But by overriding __call__

, we can take action when the class is instantiated.

In this case, all that is needed is to remove the "mock" 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(object):
    __metaclass__ = MetaMock
    ...

      

+9


source


The subclass is very important because it __new__

always passes arguments to a constructor call to a method __init__

. If you add the subclass via the class decorator as a mixin, you can intercept the argument mock

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 for a subclass method __new__

is to return RealObject(cls, *args, **kwargs)

; in this case, because the returned object is not an instance of the subclass. However, in this case, the check isinstance

will fail.

+1


source







All Articles