что, вкратце, делает декоратор property:
class property(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, obj, type = None):
return self.fget(obj)
т.о., property реализован через дескриптор, в котором есть только getter
в случае вашего примера, с точки зрения внутренней архитектуры python, разница будет такова:
obj = Person()
# obj.full_name_2()
Person.__dict__['full_name_2'](obj)
# obj.full_name_1
Person.__dict__['full_name_1'].__get__(obj)
зачем это нужно? для себя я вижу пока что только одну причину - отделить свойства класса от методов, позволив обращаться к "вычисляемым" свойствам не как к функциям, а как к атрибутам