If you are using properties it lets you begin with normal attribute accesses and then it back them up with getters and setters if necessary. So in short properties wins.
Sometimes we need for getters and setters, but even then, we can try other approaches and "hide" them. There are many ways to do this in Python like (getattr, setattr, __getattribute__, etc).
But a very concise way is to use the following:
def set_address(self, value):
if '@' not in value:
raise Exception("This doesn't look like an address") self._address = value
address = property(get_address, set_address)