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 //