Olá, Gabriel! Espero que esteja bem.
A sua dúvida é bastante pertinente e é comum entre quem está aprendendo Python e a Orientação a Objetos. Vamos lá, vou tentar esclarecer para você.
O @property é um decorador em Python que permite que você use um método como se fosse um atributo, ou seja, sem precisar chamá-lo com parênteses. No seu exemplo, você criou uma propriedade disponivel que retorna 'disponível' se o atributo _disponivel for True e 'indisponível' se for False. Isso permite que você escreva objeto.disponivel em vez de objeto.disponivel().
Por outro lado, o método __str__ é um método especial em Python que retorna uma representação em string do objeto. Quando você tenta imprimir o objeto usando a função print, o Python chama automaticamente o método __str__ do objeto.
Então, qual é a diferença entre eles e quando usar cada um?
O @property é útil quando você quer acessar um atributo de um objeto, mas quer que algo seja calculado ou verificado cada vez que o atributo é acessado. No seu exemplo, você quer verificar o valor de _disponivel e retornar uma string correspondente.
O método __str__ é útil quando você quer uma representação em string do objeto como um todo, não apenas de um atributo específico. No exercício que você mencionou, você está usando __str__ para retornar uma string que representa o veículo, incluindo a marca, o modelo e se está ligado ou desligado.
Então, em resumo, você usaria @property quando quiser que um atributo tenha algum comportamento especial quando for acessado, e __str__ quando quiser uma representação em string do objeto como um todo.
Espero ter ajudado e bons estudos!