Consider this fragment of c++:
class C {
public:
const T &get_t() const { return t; }
void set_t(const T &value) { t = value; }
private:
T t;
};
In this example, member "t" is effectively part of C's public interface, because of the accessor functions. Often, though, the accessor functions are more complicated, performing calculations, managing concurrency, etc. In these cases, access to "t" is encapsulated with the desired operations within the accessor functions.
It's not possible in C++ to encapsulate the access operations with other operations with direct data member access, so we use accessor functions. In fact, we use accessor functions even when simple access is all that's necessary because
if you mix direct access with accessor functions, it's difficult to remember which members use which sort of access, and
it's hard to predict whether you might need your accessors to do more in the future. If you do, and you used accessors, you can change your accessors without changing your public interface (API).
Through properties, Python provides consistency of access using the natural, direct-access syntax, as well as the ability to add functionality to data member access, though (hidden) getters and setters.
Further, data privacy is achieved by convention in python using leading underscores on member names to signal to users that a particular member is not part of the public interface of a class. Users violate the convention, but then they are free to keep both pieces if it breaks. Python programmers call this "We're all adults here" programming.
class C:
def get_t(self):
return self._t
def set_t(self, t):
self._t = t
t = property(get_t, set_t)
In this example, _t is conventionally private, and accessed through its property interface, but still gets share a natural, direct access syntax with other data members.