I understand what would like to have, but that's not how setters / getters behave in any language I know. Besides you can use the existing system to easily modify it so that it works like you want (using subclasses), if it were the way you described it would require more of a workaround to get the current behavior.
Do you really think that classes are clunky compared to other languages? I mean these 6 lines are sufficient to solve your problem:
class ObservableVariable:
signal valueChanged
var value setget set_value
func set_value(newVal):
value = newVal
emit_signal("valueChanged", newVal)
You will have to access the variable using "myVar.value = ..." instead of "myVar = ...", but that's no more clunky than nullable types in C#, weakrefs in Godot etc.
Here is a short example in case it's not clear how to use this:
extends Node
var myVar = ObservableVariable.new()
func _ready():
myVar.connect("valueChanged", self, "test")
myVar.value = 5;
func test(val):
print("value changed to " + str(val))
class ObservableVariable:
signal valueChanged
var value setget set_value
func set_value(newVal):
value = newVal
emit_signal("valueChanged", newVal)
But my bet is that if you use this pattern you will find that there's a good reason why the setget-feature is not built like this. Sooner or later you will have situations where you don't want to trigger callbacks when the variable is changed, and that's where it's likely easier to just call the setter internally, so you still have the option to access the variable directly and bypass the callback.