Replicating the behaviour of setattr and delattr from dataclasses

Viewed 381

Whilst watching Raymond Hettinger's talk from PyCon 2018, Dataclasses: The code generator to end all code generators - PyCon 2018 an example of how dataclasses implement setattr and delattr on frozen dataclasses was provided:

def __setattr__(self, name, value):
    if type(self) is cls or name in ('name', 'address', 'zip_code', 'account_balance', 'active_orders'):
        raise FrozenInstanceError(f'Cannot assign to field {name!r}')
    super(cls, self).__setattr__(name, value)

def __delattr__(self, name):
    if type(self) is cls or name in ('name', 'address', 'zip_code', 'account_balance', 'active_orders'):
        raise FrozenInstanceError(f'Cannot delete field {name!r}')
    super(cls, self).__delattr__(name)

Is it possible to replicate the behaviour of calling super() inside setattr and delattr without dataclasses to emulate immutability?

1 Answers

As @juanpa.arrivillaga pointed out in a comment, you can replicate the behavior using the built-in super() function, however you don't need to pass it any arguments (in Python 3 at least). Example:

class Class:
    class FrozenInstanceError(Exception): pass

    fieldnames = 'name', 'address', 'zip_code', 'account_balance', 'active_orders'

    def __init__(self):
        super().__setattr__('account_balance', 0)  # Bypasses this class' __setattr__().

    def __setattr__(self, name, value):
        if isinstance(self, Class) and name in self.fieldnames:
            raise self.FrozenInstanceError(f'Cannot assign to field {name!r}')
        super().__setattr__(name, value)

    def __delattr__(self, name):
        if isinstance(self, Class) and name in self.fieldnames:
            raise self.FrozenInstanceError(f'Cannot delete field {name!r}')
        super().__delattr__(name)


if __name__ == '__main__':

    inst = Class()
    print(inst.account_balance)  # -> 0
    try:
        inst.account_balance = 1000000
    except Exception as exc:
        print(exc)  # -> Cannot assign to field 'account_balance'
    else:
        print('Error: Somehow assigned value to "account_balance" field')

    try:
        inst.something_else = 42  # OK, not a field name.
    except Exception as exc:
        print(exc)  # -> 'Error: Wasn't allowed to create "something_else" field'
    else:
        print('Successfully created "something_else" attribute.')  # Prints.

    try:
        del inst.account_balance
    except Exception as exc:
        print(exc)  # -> Cannot delete field 'account_balance'
    else:
        print('Error: Somehow deleted protected "account_balance" field')

    try:
        del inst.something_else
    except Exception as exc:
        print(exc)  # -> 42
    else:
        print('Successfully deleted "something_else" attribute.')  # Prints.

Related