2

I think the answer will be no, but if there is a chance, it would make my life a lot easier. Consider the following code:

class SchemaString(unicode):
    _schema = dict()

    def validate(self):
        errors = []
        # some validation function using class propertie _schema, not important
        return sorted(list(set(errors)))

s = SchemaString("Hello")
e0 = s.validate()

What I would like is that the following still works:

s = "World"
e1 = s.validate()
>> AttributeError: 'str' object has no attribute 'validate'

Thus my question is if it is possible that after new assignment still same object is used, such that I can still use the method 'validate'. In other words, is there some internal assigning function used when using '=' in python and can this assignment function be overwritten?

F.Wessels
  • 121
  • 9
  • 1
    Doesn't look very difficult to try out yourself - let us know what happens? – barny Jun 09 '17 at 14:15
  • 3
    The answer is no, you can't override assignment. Besides the point, using a class for this seems unnecessary; subclassing `str`, `unicode`, `dict`, `tuple` and friends is also almost always wrong in subtle ways. Why not use a simple free standing function? You can `functools.partial` the schema state into it, have a function `validate_foobar = functools.partial(validate, foobar_schema)` and then `validate_foobar(s)` – user2722968 Jun 09 '17 at 14:22
  • 1
    Possible duplicate of [Is it possible to overload Python assignment?](https://stackoverflow.com/questions/11024646/is-it-possible-to-overload-python-assignment) – Akshat Mahajan Jun 09 '17 at 15:55

1 Answers1

1

You can't override "top level" assignment. But if you can have a root object and work with variables inside a namespace object, then yes, you only have to write the __setattr__ method for that object. It can be a rather simple class, and you can make an instance that is a single letter, so it does not cause negative interference on reading your code - on the contrary, it is easy to perceive that something special is going on:

class NS(object):
    def __setattr__(self, name, value):
        if name not in self.__dict__:
            # Normal, first time assignment:
            super(NS, self).__setattr__(name, value)
            return
        cls = type(self.__dict__[name])
        instance = cls(value)
        super(NS, self).__setattr__(name, instance)

n = NS()

And on the ineteractive prompt:

In [110]: class MyString(str): pass
     ...: 
     ...: n.s = MyString()
     ...: 
     ...: n.s = "bla"
     ...: type(n.s)
     ...: 
Out[110]: __main__.MyString

Without resorting to a container object, no, there is no way to override assignments - the under-the-hood generated code for that is completely different - as the bytecode sets the variables directly either in the "global" dictionary for the current frame, or in fast-variable caches if the code is running inside a function - meaning that not even the locals dictionary is touched. That is, it is possible to think of an observer-pattern that takes care of changes in the global dictionary, and takes action when a global variable is changed in a module (if your application is running in a custom event loop, for example) - but that would be too hacky, and would not work for global variables anyway. The namespace object is orders of magnitude simpler.

That said, if you are using Python3, (which you should - and then for one thing - stop worrying about "unicode" or "str" for text) - you could also override the assignment operator in the body of a class: for that you have to create a custom metaclass with a __prepare__ method that would create a mapping object in which the logic you want would be on the __setitem__ method. In this case, Python code just calls the __setitem__ method on this dictionary-like object when it finds an assignment statement.

jsbueno
  • 77,044
  • 9
  • 114
  • 168