-2

If I create an object:

class eggs(object):
    """ This wasn't needed """

    def __setattr__(self, name, value):
        print name, value

I can understand that if I do:

class eggs(object):
    """ This wasn't needed """

    def __setattr__(self, name, value):
        print name, value


if __name__ == "__main__":
    foo = eggs()
    foo.bar = 5
    print foo

I get:

bar 5
<__main__.eggs object at 0x8f8bb0c>

However when I do:

if __name__ == "__main__":
    foo = eggs()
    foo = 5
    print foo

I get:

5

My question is what "magic" method is called when foo = 5 is called?

For example I do do:

class eggs(object):
    """ This wasn't needed """
    def __init__(self):
        self.bacon = False

    def __setattr__(self, name, value):
        if not name == "bacon": 
            raise Exception("No way Hosay")
        if value not in [True, False]:
            raise Exception("It wasn't supposed to be like this")
        super(eggs, self).__setattr__(name, value)
        print "Variable became {0}".format(value)




if __name__ == "__main__":
    foo = eggs()
    foo.bacon = True
    foo.bacon = 5 > 4
    foo.bacon = True or False
    # ETC

Which returns:

Variable became False
Variable became True
Variable became True
Variable became True

I want to do that without bacon.

Noelkd
  • 7,686
  • 2
  • 29
  • 43
  • 4
    Aren't you just reassigning the variable? You are no longer setting the attribute of `foo`, but giving `foo` a whole new value, of a completely different type. It isn't even of class `eggs` after you do that. There isn't really any magic method, it's just basic programming logic. – anon582847382 Apr 16 '14 at 18:19
  • I am reassigning the variable, but I want an object that does something when you try to change it? If that makes sense? I feel like it should be doable and I'm thinking about it wrong. – Noelkd Apr 16 '14 at 18:22
  • 2
    That's impossible. The assignment operator rebinds names; it doesn't let the object formerly bound to that name do anything (well, except indirectly, by decrementing its reference count and therefore possibly calling its `__del__` method). – Wooble Apr 16 '14 at 18:27
  • @Noelkd You're from C++ background, right? In most languages (Python included) variables and objects are distinct, and assignment works with to the variable rather than an object referred to by the variable. C++ is a bit of an outlier- a C++ variable of object type actually "contains" the object rather than refer to it, and `operator=` is a part of the object's interface. – Kos Apr 16 '14 at 18:30
  • The "magic" methods are called `__get__` and `__set__`, but things do not work exactly as you want. See the Python documentation on descriptors – yorodm Apr 16 '14 at 18:33
  • I believe if you wrap `__init__` and `__del__` with your choice of decorator, you'll get the desired effect. If I've read your comments (and edits) correctly. –  Apr 16 '14 at 18:57
  • Interesting stuff, glad I asked the question. – Noelkd Apr 16 '14 at 18:58

3 Answers3

2

You've misunderstood the way that variables work in python - they are just a reference to an object, and reassignment just overwrites the reference with another reference.

As a simplified explanation, imagine that variable assignment works like assigning members of a dictionary. If we call that dictionary variables, we could rewrite

foo = 5

as

variables["foo"] = 5

Now, you want the current value of variables["foo"] (if it even exists) to be notified of the assignment, which would mean that the previous statement would become analoguous to the following:

variables["foo"].__somemethod__() #call magic method to notify reassignment
variables["foo"] = 5 # set new value

While you could actually implement this exact behaviour with a dict subclass, this is simply not the way that variable assignment in CPython is implemented. The object that is referenced by a variable is not in any way notified of the fact that it is not referenced by variables["foo"] anymore. All that happens is that the reference count of the previously referenced object (if there was any) is decremented by one.

As the answer by Stick demonstrates, there is a __del__ method which is called when the object is garbage collected, which may or may not be enough for your actual use case. But when this is called is actually up to the gc so it can exhibit some funky behaviour; see e.g. this answer for a discussion of some quirks. Furthermore, before python3.4 it was not even guaranteed that any referenced objects are still alive when __del__ is invoked, this has now been fixed: http://legacy.python.org/dev/peps/pep-0442/.

If you really need to be notified of a reference being overwritten (and I'd doubt that there isn't a better way to solve your actual problem), consider using a subclassed or monkeypatched dict for all references you want to track. You would need to overwrite the __setattr__ method to perform the actions you want to happen when a reference is overwritten.

Community
  • 1
  • 1
l4mpi
  • 5,103
  • 3
  • 34
  • 54
  • To that end, a `WeakValueDictionary` might be really clutch –  Apr 16 '14 at 21:53
  • +1 for explicitly stating that "All that happens is that the reference count of the previously referenced object (if there was any) is decremented by one". I needed that clarification for a loop where I'm creating objects and assigning them to the same variable in each iteration. – Stephen K. Karanja Oct 31 '15 at 13:46
1

If you want to be weird you can override __del__ but that's a whole can of worms typically.

>>> class A(object):
...     def __del__(self):
...             print "oh noooo!"
... 
>>> Stick = A()
>>> Stick = 5
oh noooo!
>>> Stick
5
>>> 

EDIT: It sounds like you want to know when the object is created or deleted as well?

Perhaps just a decorator will suffice?

>>> def talk(func):
...     def inner(*args, **kwargs):
...             print "I'm doing things!"
...             return func(*args, **kwargs)
...     return inner
... 
>>> class A(object):
...     @talk
...     def __init__(self):
...             pass
...     @talk
...     def __del__(self):
...             pass
... 
>>> Stick = A()
I'm doing things!
>>> del Stick
I'm doing things!
>>> Stick
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'Stick' is not defined
>>>

This works for instantiation and deletion, as well as reassignment of the variable.

And of course -- if you need different methods when __init__ and __del__ are called, you'll want to use different decorators. Just put your "actual work" in the nested function.

def wrapper(func):
    def inner(*args, **kwargs):
        #your code here!
        #your code here as well!
        return func(*args, **kwargs)
    return inner

Just be leery of goofballs and whatnot when mucking around with __del__, but I think this is a relatively sane way to do it.

  • 1
    This will only work under very specific restrictions, namely that 1. the gc immediately discards an object when it's unreferenced and 2. the object is only ever referenced once. E.g. if you do `a1 = A(); b = a1; a = 0` then `__del__` will of course not be called after reassigning `a`... – l4mpi Apr 16 '14 at 19:01
  • 1
    Sure, but... it's totally weird to ever do this anyway, right :/ –  Apr 16 '14 at 19:03
0

You should check out Descriptors, they're the closest thing to what you want

yorodm
  • 4,359
  • 24
  • 32
  • 2
    Can't see how that's relevant, can you explain? – Kos Apr 16 '14 at 18:31
  • Well, for starters, they do have a "magic" method which is called on assigment. – yorodm Apr 16 '14 at 18:43
  • On **variable** assignment? You'll have to quote the specific phrase of the official docs for me to believe this. AFAIK they only have one for attribute assignemnt, everything else would be black magic in CPython. – l4mpi Apr 16 '14 at 18:50
  • Agreed, that's why I said: "the closest" – yorodm Apr 16 '14 at 18:53
  • Then you've misunderstood the question, OP specifically asks about variable assignment and not attribute access... – l4mpi Apr 16 '14 at 18:58
  • AFAICT wrapping `__init__` and `__del__` works for what the OP expects –  Apr 16 '14 at 18:58