Overriding Python str class setter.. How?

Hi,
I’m coding a script that performs different complex tasks and i want to output status messages regarding the execution of the script.
Rather than doing a “print” statement after each main function call i 've thought it would be nicer to have a string object that automatically prints something each time the variable’s value changes.
So this lead me to write a custom class with a variable and a setter to it like the following:

class Msg(object):
    def __init__(self):
        self._s = None
    
    @property
    def status(self):
        return self._s
    @status.setter
    def status(self, value):
        self._s = value
        call_custom_function()

Now the question: I’ve come to the above solution but i’m wondering if it’s possible to inherit directly from str class and just override the setter… thus not needing an intermediate variable like _s and be able to do something like

msg = Msg()
msg = "hello"

instead of the current:

msg = Msg()
msg.status = "hello"

I’ve searched the web and i haven’t found how the str class works besides the fact that it’s a “sequencer” type just like a list…

This would work in a static typed language, such as C, but not in Python.
String literals are by default str. Hence myvariable = “some-string” will always result in myvariable being of the type str. You can use a cast though. Such as you can cast an integer into a float - e.g. myFloar = float(1) - you could cast the string literal into your own string type on assignment. - e.g. msg = Msg(“hello world”)

Thanks for answering.

I dont know if i quite understand you, maybe it’s because i didnt make myself clear enough.

I want to do something like:

class Msg2(str):
	def __init__(self):
		super(Msg2,self).__init__()
	//function to override the setter
        def str_class_setter(self, value):
                //set the value
                //do other things

In order to do something like:

variable = Msg2()
variable = "hello world"

So whenever i change ‘variable’ s value besides setting the str value i can do other stuff.

[QUOTE=jiceq;29027]Thanks for answering.

I dont know if i quite understand you, maybe it’s because i didnt make myself clear enough.

I want to do something like:

class Msg2(str):
	def __init__(self):
		super(Msg2,self).__init__()
	//function to override the setter
        def str_class_setter(self, value):
                //set the value
                //do other things

In order to do something like:

variable = Msg2()
variable = "hello world"

So whenever i change ‘variable’ s value besides setting the str value i can do other stuff.[/QUOTE]

In that case your first example with the property does exactly that.

I’d probably use _status instead of _s as it is a bit more explicit, but that’s just me.

[QUOTE=R.White;29029]In that case your first example with the property does exactly that.

I’d probably use _status instead of _s as it is a bit more explicit, but that’s just me.[/QUOTE]

But that’s my still unanswered question, is it possible to do it without the intermediate variable ‘_s’ ? by inheriting the class from ‘str’ instead of ‘object’? How would it be in that case? What’s the str class setter method name?

Not really? I mean you need to store the string value somewhere. If you’re not using a property, and just say

msg.status = "thing"

you are still storing your str on the member attribute of status. When you use a property, you’re just accessing a different name, through the getter/setter interface.

If you inherit from str and say overload the constructor to fire off your other function, you end up with something like


do_a_thing = lambda: 'Did a Thing?'
class SillyStr(str):
    def __init__(self, s=''):
        super(SillyStr, self).__init__(s)
        print(do_a_thing())

class Msg(object):
    pass
    
m = Msg()
m.status = SillyStr('Some String') # You should see Did a Thing? printed

m.status = 'stuff and things?' # Nothing will happen.

Which really doesn’t even get close to what you want.
Use a property, it is what they are built for.

what you are trying to do doesn’t really make any sense. to anyone reading your code it would not make sense.

as already recommened, use a property.

Two thoughts.

  • first, you should probably look at the built in logging module. It’s got a lot of functionality and it’s built in (and it’s very easy to turn on and off)
  • second, you probably don’t want to use the ‘set value triggers message’ paradigm. Other readers of your code may not understand why setting values is triggering messages, and the syntax won’t help them.

      logging.info("user said XXX") 

is pretty unambiguous where


     object.message = fred

does not scream “i’m printing a message to a console/ log file /database etc”

It’s always a bad idea to trigger actions with what look like property access, unless the property access is just a getter/setter than makes sure the changes are enacted safely

[QUOTE=R.White;29031]Not really? I mean you need to store the string value somewhere. If you’re not using a property, and just say

msg.status = "thing"

you are still storing your str on the member attribute of status. When you use a property, you’re just accessing a different name, through the getter/setter interface.

If you inherit from str and say overload the constructor to fire off your other function, you end up with something like


do_a_thing = lambda: 'Did a Thing?'
class SillyStr(str):
    def __init__(self, s=''):
        super(SillyStr, self).__init__(s)
        print(do_a_thing())

class Msg(object):
    pass
    
m = Msg()
m.status = SillyStr('Some String') # You should see Did a Thing? printed

m.status = 'stuff and things?' # Nothing will happen.

Which really doesn’t even get close to what you want.
Use a property, it is what they are built for.[/QUOTE]

I appreciate the feedback but your answers though interesting don’t satisfy fully my curiosity.

“you need to store the string value somewhere”. That’s right!!! But instead of using properties and hence an STR intermediate variable, i want to store it using the str class setter method and store it directly using str class variable. I mean, what i’m wondering is if it’s possible to have exactly the same class methods and behaviour that the str class with a difference: being able to customize the str setter class method.

“what you are trying to do doesn’t really make any sense. to anyone reading your code it would not make sense”. I disagree, i find it much cleaner to inherit directly from str class and customize one of the methods than using a class that inherits from object and use “one extra variable”. Why use an extra str variable in a class when you can inherit from str and store the value in the prepared-for-this str class internal variable?

“you probably don’t want to use the ‘set value triggers message’ paradigm.” Hi Theodox, IMHO it’s useful mainly for outputting info regarding the state and progress of the script. In my case, right now, for debugging purposes. I have a sequence of calls to main functions which in turn are divided into other secondary methods. Rather than making a call to logging.info(“function output”) after each function call it’s far easier to recur to this paradigm: you only write it once. Besides, currently it’s just outputting to the console but later it might output it to a graphical widget.

Anyways, i hope i dont look like a stubborn for insisting on this. I know it’s not a big deal but it’s much more curiosity on the language than any other thing. Maybe it’s not a good practice, maybe it is, as everything, I think if used coherently it might make sense.


msg = Msg()
msg = "hello"

to anyone who write python code, these lines mean this:

  1. Create an instance of the class Msg and assign it to msg
  2. msg is now the string “hello”

Your instance of the Msg class has been overwritten. Thats why it makes no sense. Why create an isntance of a class just to over-write it with a string.

[QUOTE=rgkovach123;29037]


msg = Msg()
msg = "hello"

to anyone who write python code, these lines mean this:

  1. Create an instance of the class Msg and assign it to msg
  2. msg is now the string “hello”

Your instance of the Msg class has been overwritten. Thats why it makes no sense. Why create an isntance of a class just to over-write it with a string.[/QUOTE]

exactly. It would work in C++ though, where you could overload the assignment (=) operator of the type/class Msg of which msg would be an instance. But Python isn’t C++.

[QUOTE=rgkovach123;29037]


msg = Msg()
msg = "hello"

to anyone who write python code, these lines mean this:

  1. Create an instance of the class Msg and assign it to msg
  2. msg is now the string “hello”

Your instance of the Msg class has been overwritten. Thats why it makes no sense. Why create an isntance of a class just to over-write it with a string.[/QUOTE]

Ahhhh now i see and understand its nonsense. I was being mistaken by the fact that i come from C++ and i didn’t expect msg Msg class object to be replaced by an str object when assigning “hello”… but instead call the overloaded setter method from my original Msg() class.

Thanks for pointing this out to me, finally.

Regards.

In case it wasn’t pointed out clearly, python strings are immutable objects. You can’t mess with them at initialization time, you’d have to override new to customize creation time behavior. Strings can have instance methods – and string-derived types can have extra data – but you have to create them as instances: you can’t override the assignment ‘equals’ because the owning variable doesn’t know what you’re trying to do to it. You can override other operators (+, -, etc ) but not assignment – in a python AST assignment is a separate kind of statement distinct from operators.

As for the usage pattern, I see what you want to do but the pattern you seem to be proposing has potential maintenance problems down the road. Logging assignments is a perfectly legit operation, it’s just that by assigning to a non-obvious class that only does assignments, in a language without type annotations, you’re inviting misuses down the road. If somebody sees


some_value = 'something'

in one part of the code they may assume its safe to try


if some_value == 'something' 

elsewhere – meaning you have to property implement equality (extra work) or just fail an innocent looking operation (angry coworker)

If you want a loggable property you can do something like this:


class LogProperty (object):
    
    """print a log message when this property is assigned """

    def __init__(self, backing_attribute, default  = None):
        self.backing_attribute = backing_attribute
        self.default = default

    def __get__(self, instance, _):
        if not hasattr(instance, self.backing_attribute):
            setattr(instance, self.backing_attribute, self.default)
        return getattr(instance, self.backing_attribute)
        

    def __set__(self, instance, value):
        setattr(instance, self.backing_attribute, value)
        print "set %s.%s to %s" % (instance, self.backing_attribute, value)


class Example(object):
    
    log_string= LogProperty('_log_str', 'hello world')
    log_int = LogProperty('_log_int', 99)
    
    
test = Example()
print test.log_string

// hello world //

test.log_string = "bye!"

// set <__main__.Example object at 0x000000000E6989B0>._log_str to bye! //


print test.log_string == "bye!"

// Result: True //

Since the descriptor object that handles the get-set operations is a separate class, you can use this in all sorts of other code. In practice using a python logger is better than prints, since you can leave the code in place and control the verbosity in a standardized way – but this at least makes it pretty easy to re-use the logging functionality in lots of places with minimal boilerplate and it preserves the reader’s expectation that a = “b” implies a == “b”. A metaclass that automatically assigns these properties can make it less verbose but that’s not wise until you’re really comfortable with the class constructor pipeline – it injects a level of magic that’s really powerful but will surprise novice or intermediate coders

In a similar vein you can also write a decorator that will automatically log function calls or return values:


def logged(fn):
     def auto_log(*args, **kwargs):
           print "calling", fn
           result = fn(*args, **kwargs)
           print "returned: ", result
           return result
     return auto_log
      
@logged
def test_function (arg):
  return 'goodbye'


test_function('hello')

// calling <function test_function at 0x000000000E69AF28>//
// returned:  goodbye // 

Hi Theodox,
didnt think of a decorator in the first place… but i admit it looks like the best choice to me for my current purposes.

Thanks for the insight.