Laurențiu Andronache
Laurențiu Andronache

Reputation: 584

How do I attach new special methods to an instance, at __init__?

I would have expected the below code to raise AttributeError. What am I doing wrong?

class CrazyClass:
    def __init__(self, a):
        self.a = a
        self.b = 'b'

        def difficult_set_attribute(key, value):
            if key not in self.__dict__:
                raise AttributeError('You may not set a new attribute, unless you do it in the secret way!')

        self.__setattr__ = difficult_set_attribute


test = CrazyClass('a')
test.c = 'c'  # why doesn't this raise AttributeError?

Upvotes: 0

Views: 61

Answers (2)

Laurențiu Andronache
Laurențiu Andronache

Reputation: 584

Solved because of what @kindall said:

class CrazyClass:
    def __init__(self, a):
        self.a = a
        self.b = 'b'

        # the assignment is only done at the first instantiation
        if CrazyClass.__setattr__ is object.__setattr__:
            def difficult_set_attribute(cls, key, value):
                if key not in cls.__dict__:
                    raise AttributeError('You may not set a new attribute, unless you do it in the secret way!')

            CrazyClass.__setattr__ = difficult_set_attribute


test = CrazyClass('a')
test.c = 'this raises AttributeError'

The disadvantage of this solution is that the second instance created will raise AttributeError.

Example:

test = CrazyClass('a')
test_2 = CrazyClass('2')

There's probably a solution to this that's not too complicated. I'm looking for it now.

EDIT: this is my solution, but after reading @kindal's new comment at this answer, I'll implement his solution as well:

class CrazyClass:
    __temporary_allow_setattr = False

    def __init__(self, a):
        self.__temporary_allow_setattr = True
        self.a = a
        self.b = 'b'
        self.__temporary_allow_setattr = False

        if CrazyClass.__setattr__ is object.__setattr__:
            def difficult_set_attribute(cls, key, value):
                if key == '_CrazyClass__temporary_allow_setattr' or cls.__temporary_allow_setattr:
                    cls.__dict__[key] = value
                else:
                    raise AttributeError('You may not set a new attribute, unless you do it in the secret way!')

            CrazyClass.__setattr__ = difficult_set_attribute


test = CrazyClass('a')
test._CrazyClass__temporary_allow_setattr = True
test.c = 'this will succeed'
test_2 = CrazyClass('b')
test_2.c = 'this will fail'

Upvotes: 1

Yuki
Yuki

Reputation: 21

If you just define __setattr__ to raise the error, it would work.

class CrazyClass:
    def __init__(self, a):
        self.a = a
        self.b = 'b'

    def __setattr__(self, key, value):
        if key not in self.__dict__:
            raise AttributeError('You may not set a new attribute, unless you do it in the secret way!')

test = CrazyClass('a')
test.c = 'c'

Upvotes: 0

Related Questions