Если функция в результате своей работы должна изменить или сгенерировать какое-то значение, то чаще всего лучше делать это через return. То есть, операции внутри функции по сути инкапсулированы в неё. Если ваша функция будет менять внутри себя что-то глобальное, вы можете нарваться на ошибки, вам будет сложно отслеживать изменения. Пример, когда функция меняет что-то глобально, если представить на местах комментов много разного кода, станет понятно, что уследить, что там происходит с а очень сложно. А если таких функций несколько сделать? =)
a = 0
# здесь ещё код, не связанный с а
a = 1
def changeA():
a = 3
a = 4
# и здесь код, не связанный с а
changeA()
# снова код, не связанный с а
a = 5
# опять код не про а
# и вдруг вам надо а срочнааа
print(a)
Поэтому лучше всего в функции использовать а, но не менять её напрямую, а отдавать результат использования, который можно присвоить а (или даже b)), вызвав функцию. И пример ниже, как делать лучше.
Сложно отследить это в таком вот маленьком контексте, но когда речь о кодовой базе проекта, это играет довольно большую роль, в том числе облегчает чтение кода. В этом случае всё более очевидно становится. В общем, важно контролировать внешнее состояние. Особенно это пригодится при изучении многопоточки=)) если есть вопросы - задавайте, всё же мне кажется, что пример не прям наглядный получился, но я постаралась передать саму суть, для чего это нужно и почему хорошо именно так=) (Но естественно, есть и другие кейсы, это не гвоздями прибитое правило=) )
a = 0
# здесь ещё код, не связанный с а
a = 1
def changeA():
return a + 3
a = 4
# и здесь код, не связанный с а
a = changeA()
# снова код, не связанный с а
a = 5
# опять код не про а
# и вдруг вам надо а срочнааа
print(a)