user3892448
user3892448

Reputation:

_replace does not call __new__ of a named tuple subclass

I have this subclass of a named tuple type:

class User(namedtuple('User', ['first_name'])):
    __slots__ = ()

    def __new__(cls, *args, **kwargs):
        result = super().__new__(cls, *args, **kwargs)
        if not result.first_name:
            raise InvalidUserError({InvalidUserError.EMPTY_FIRST_NAME})
        return result

Creating a new user works as expected:

>>> try: User(first_name='')
... except Exception as e: print(type(e))
<class 'InvalidUserError'>

However, when _replace is used the __new__ method is not invoked:

>>> User(first_name='foo')._replace(first_name='')
User(first_name='')

Is there a way to guarantee invariants with namedtuple? I'm using Python 3.4.

Upvotes: 2

Views: 241

Answers (1)

Sven Marnach
Sven Marnach

Reputation: 601559

Python in general relies on conventions and good documentation rather than on strongly enforcing invariants. Even without _replace(), you could circumvent User.__new__():

>>> class X(tuple): __slots__ = ()
>>> x = C(('',))
>>> x.__class__ = User
>>> x
User(first_name='')

So no, you will never be able to stricly enforce this. Simply avoid using _replace(), or override it with a version that does call User.__new__(), or check the invariants at a different level.

An example implementation of _replace():

def _replace(self, **kwargs):
    return type(self)(**dict(vars(self), **kwargs))

Upvotes: 2

Related Questions