Search code examples
pythonvariables

Can I "hook" variable reassignment?


Python is a language in which the following is possible:

>>> class A(object):
...  def __eq__(self, _):
...   return True
... 
>>> a = A()
>>> a == "slfjghsjdfhgklsfghksjd"
True
>>> a == -87346957234576293847658734659834628572384657346573465
True
>>> a == None
True
>>> a is None
False

Other things, like gt and lt are also "overloadable"1 in this way, and this is a great feature, in my opinion.

I'm curious if the = assignment operator is also "overloadable"1 in a similar kind of fashion, or if I'd have to recompile Python into NotPython to do this.

(As far as I know, classes and objects don't implement some __assign__ method; that's implemented with the C bytecode runner/compiler.)

Specifically, I want to implement an LL(k) parser by iterating on a token list, without using an iterator such that I can change the iterator's index arbitrarily to jump to a given token.

The catch is that on a given cycle of the loop, if I've already arbitrarily modified the index in preparation for the next cycle (something I'll surely do a lot) the last thing I want to do is mess up that variable by adding one to it as if it hadn't been changed.

Probably the easiest, simplest solution to this is to have a flag that gets set on jumps and which is reset to False every cycle, but this can and will introduce tiny hiding bugs I'd like to avoid ahead of time. (See here for what I mean -- this is the pathetic iterative LL(1) parser I'm rewriting, and I want its reincarnation to be somewhat maintainable and/or readable.)

The flag solution:

idx = 0
while True:
  jmpd = False
  # maybe we jumped, maybe we didn't; how am I to know!?!?
  if jmpd == False:
    idx += 1 

Great! One big drawback: at each possible branch resulting in an arbitrary change in the index, I have to set that flag. Trivial, perhaps (obviously in this example), but to me it seems like a harbinger of unreadable, bug-friendly code.

What's the best way, without using a second variable, to test if a value's changed over time?

If your answer is, "there isn't one, just be quiet and use a flag variable", then I congratulate you for promoting bad code design. /s


1Yeah, I know it's not technically operator overloading; have you a better, more quickly understandable term?


Solution

  • You can use another magic method to intercept assignment of the attribute. It's called __setattr__(). Here is an example of how to use it:

    In [2]: class Test(object):
        def __setattr__(self, name, value):
            print(name, "changed to", value)
            super().__setattr__(name, value)
       ...:
    
    In [3]: t = Test()
    
    In [4]: t.name = 4
    name changed to 4
    
    In [5]: t.name = 5
    name changed to 5