Избежать global и nonlocal можно несколькими способами:
Эти подходы способствуют более читаемому, поддерживаемому и тестируемому коду.
Использование `global` и `nonlocal` часто считается нежелательным, так как они могут усложнить понимание и отладку кода, делая его менее предсказуемым. Вот несколько стратегий, позволяющих избежать их использования при работе с несколькими уровнями областей видимости в Python:
Самый предпочтительный и наиболее явный способ. Вместо изменения переменных в объемлющих областях видимости, функции принимают нужные данные в качестве аргументов и возвращают измененные значения. Эти значения затем присваиваются переменным в вызывающем контексте.
     
 def outer_function(initial_value):
  def inner_function(value):
   return value + 1  # Возвращаем измененное значение
  new_value = inner_function(initial_value) # Передаем и получаем значение
  return new_value
 result = outer_function(5)
 print(result) # Output: 6
     
    
   Классы позволяют инкапсулировать данные и методы внутри одного объекта. Атрибуты класса доступны для всех методов этого класса, что избавляет от необходимости использовать `global` или `nonlocal`.
     
 class Counter:
  def __init__(self, initial_value=0):
   self.count = initial_value
  def increment(self):
   self.count += 1
  def get_count(self):
   return self.count
 my_counter = Counter(5)
 my_counter.increment()
 print(my_counter.get_count()) # Output: 6
     
    
   Если необходимо изменить значение переменной, находящейся в объемлющей области видимости, можно передать в функцию изменяемый объект (например, список или словарь). Изменения, внесенные в этот объект внутри функции, будут видны и в вызывающем контексте.
     
 def outer_function(data):
  def inner_function(data):
   data[0] += 1  # Изменяем элемент списка
  inner_function(data)
 my_list = [5]
 outer_function(my_list)
 print(my_list[0]) # Output: 6
     
    
    Важно: Этот метод следует использовать с осторожностью, так как он может сделать код менее понятным, если не документировать его должным образом. Изменение изменяемых объектов может быть неочевидным для тех, кто не знаком с деталями реализации.
Фабричная функция возвращает другую функцию, захватывая переменные из своего окружения в замыкании. Это позволяет избежать явного использования `nonlocal`.
     
 def create_incrementer(initial_value):
  def incrementer():
   nonlocal initial_value  # Требуется nonlocal
   initial_value += 1
   return initial_value
  return incrementer
 # Альтернатива без nonlocal (но менее читаемо):
 def create_incrementer_no_nonlocal(initial_value):
  state = {'value': initial_value}
  def incrementer():
   state['value'] += 1
   return state['value']
  return incrementer
 increment = create_incrementer(5)
 print(increment()) # Output: 6
 print(increment()) # Output: 7
     
    
    В примере с `create_incrementer_no_nonlocal`, переменная `state` (словарь) захватывается замыканием, и ее состояние изменяется внутри `incrementer`. Это позволяет избежать использования `nonlocal`, но обычно считается менее читаемым, чем версия с `nonlocal` (если ваша цель, наоборот, избежать `global`, а не `nonlocal`, то этот вариант может быть полезным).
В большинстве случаев, передача данных как аргументы и возврат значений является наиболее чистым и понятным подходом. Использование классов также хорошо подходит для организации данных и логики. Использование изменяемых объектов и фабричных функций может быть полезным в определенных ситуациях, но требует более тщательного проектирования и документирования.