Почему переменная счетчика сбрасывается до старого значения в той же функции?

Я пытаюсь получить количество всех элементов во вложенных списках. в то время как код проходит через все элементы, счетчик устанавливается только на невложенные элементы списка. что происходит и как исправить этот код?

def countarr(arr,count):
    
    if len(arr) == 0:
        return count

    else:
        
        if type(arr[0]) != list:
            count += 1
            print(arr[0],count)
        else:
            print ("inner")
            countarr(arr[0],count)

    return countarr(arr[1:],count)
    
count=0
arr=[1,2,[[4,5]],8,[7,4,5,6],12,34]
print("Answer: ",countarr(arr, count))

Обратите внимание, что рекомендуется использовать isinstance(x, list), а не type(x) == list

Stef 16.05.2022 20:29

Когда вы делаете рекурсивный вызов countarr(arr[0],count), вы отбрасываете возвращаемое значение этого вызова. Предположительно, вы хотите сделать что-то вроде count = countarr(arr[0],count) или count += countarr(arr[0],count)

Stef 16.05.2022 20:30

Обратите внимание, что когда вы передаете count в качестве параметра в вызове функции, вы фактически передаете копировать из count. При вызове функции создается новая локальная переменная со значением, которое вы передали, и локально эта переменная называется count, но если вы обновляете переменную с именем count внутри рекурсивного вызова, она не обновляет переменную с именем count в текущий вызов.

Stef 16.05.2022 20:32

Большое спасибо. Первоначально я неправильно понял причину, по которой его отбрасывают. Я думал, что вызов countarr(), когда тип arr[0] представляет собой список, отбрасывает обновления, потому что его обновления являются локальными для него. Я думаю, что теперь я вижу правильную причину. Эта внутренняя функция вызывает основную функцию рекурсии, когда arr[0] является списком. Счетчик продолжает обновляться, поскольку он передается в качестве параметра. Но последняя строка внешней рекурсии возвращает переменную внутреннему вызову, но не сохраняет это возвращаемое значение и поэтому отбрасывается. Это правильно? Также областью действия этого сохраненного счетчика является внешняя функция. Верно?

ArNY 17.05.2022 00:51

Я рекомендую вообще не передавать count в качестве параметра, так как это сбивает вас с толку и в данном случае совершенно бесполезно. Вместо этого вы можете сделать: def countarr(arr): if len(arr) == 0: return 0 elif isinstance(arr[0], list): return countarr(arr[0]) + countarr(arr[1:]) else: return 1 + countarr(arr[1:])

Stef 17.05.2022 00:55

Или как вариант: def countarr(arr): count = len(arr); for x in arr: if isinstance(x, list): count += countarr(x); return count (с последним return вне for-цикла, а не внутри). Эта версия на самом деле намного лучше, потому что не использует arr[1:], что неэффективно.

Stef 17.05.2022 00:56
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения текстовых сообщений может быть настолько сложным или простым, насколько вы его сделаете. Как и в любом ML-проекте, вы можете выбрать...
7 лайфхаков для начинающих Python-программистов
7 лайфхаков для начинающих Python-программистов
В этой статье мы расскажем о хитростях и советах по Python, которые должны быть известны разработчику Python.
Установка Apache Cassandra на Mac OS
Установка Apache Cassandra на Mac OS
Это краткое руководство по установке Apache Cassandra.
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
В одном из недавних постов я рассказал о том, как я использую навыки количественных исследований, которые я совершенствую в рамках программы TPQ...
Создание персонального файлового хранилища
Создание персонального файлового хранилища
Вы когда-нибудь хотели поделиться с кем-то файлом, но он содержал конфиденциальную информацию? Многие думают, что электронная почта безопасна, но это...
Создание приборной панели для анализа данных на GCP - часть I
Создание приборной панели для анализа данных на GCP - часть I
Недавно я столкнулся с интересной бизнес-задачей - визуализацией сбоев в цепочке поставок лекарств, которую могут просматривать врачи и...
1
6
29
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Переменные не сбрасываются волшебным образом. Они устанавливаются только на значение, которое вы им даете. Изменение значения локальной переменной не влияет на значение другой переменной в другой функции, даже если она имеет такое же имя. Возьмем следующий пример:

def foo(count):
   print(count)
   count += 1 
   print(count)

def bar()
   count = 0
   print(count)
   foo(count)
   print(count)

bar()

Рекурсия такая же. Каждый вызов рекурсивной функции имеет свои собственные копии локальных переменных точно так же, как foo() и bar() имеют переменную с именем count, которые не зависят друг от друга.

Чтобы исправить рекурсивную функцию, просто верните значение count из каждой возможной if...else ветви. Вы уже делаете это в двух ветвях, но вы печатаете значение только в двух других ветвях и не возвращаете его.

Спасибо. Что меня смущает, так это то, что, как вы видите из распечаток, счетчик сохраняется, когда он входит во вложенный список и через рекурсивные вызовы, но сбрасывается при выходе из вложенного списка. Я учу себя программированию и был бы признателен, если бы вы могли дать несколько советов относительно того, почему это происходит.

ArNY 16.05.2022 20:17

@ArNY Я не уверен, что означает «входит во вложенный список». Вы не можете «войти» в список. Или, по крайней мере, это не обычное использование слова «войти» в сообществе программистов. Чтобы поговорить об этом более конкретно, я предлагаю отредактировать ваш вопрос, включив в него вывод вашей программы, а затем сформулировать свой вопрос в зависимости от того, какие части этого вывода вас смущают. Это сделает ваш вопрос более конкретным.

Code-Apprentice 16.05.2022 20:20

@ArNY С учетом сказанного я предлагаю вам больше узнать о так называемой «переменной области видимости» в python. Понимание того, почему мой пример дает такой результат, — это первый шаг. Затем наблюдение за тем, как та же концепция применяется к вашей рекурсивной функции, поможет вам понять, что происходит.

Code-Apprentice 16.05.2022 20:21

@ArNY В вашем рекурсивном примере каждый вызов countarr() имеет свою собственную переменную count. Когда вы return переходите к определенному countarr() кадру, его count будет иметь то же значение, которое было до того, как вы вызвали его рекурсивно. Значение count не «сбрасывается». Скорее есть несколько переменных count, каждая из которых имеет свое значение.

Code-Apprentice 16.05.2022 20:24

Спасибо. Это помогает. Я буду читать больше об этом.

ArNY 16.05.2022 20:39

я вижу что происходит

ArNY 16.05.2022 21:09
Ответ принят как подходящий

count — это переменная типа int. При передаче int в качестве параметра функции в python передается только значение, а не фактическая переменная. Таким образом, когда рекурсивный вызов «изменяет count», он фактически изменяет копия переменной, а не оригинал. Итак, оригинал не обновляется.

Кроме того, когда вы выполняете рекурсивный вызов: countarr(arr[0]), вы не используете возвращаемое значение этого вызова. Вы можете просто добавить count = перед рекурсивным вызовом, чтобы получить возвращаемое значение рекурсивного вызова:

def countarr0(arr,count):
    if len(arr) == 0:
        return count
    else: 
        if not isinstance(arr[0], list):
            count += 1
        else:
            count = countarr0(arr[0], count)
    return countarr0(arr[1:], count)
    
arr=[1,2,[[4,5]],8,[7,4,5,6],12,34]
print("Answer: ",countarr0(arr, 0))
# Answer:  11

Однако передача аргумента count рекурсивному вызову не очень полезна. Рекурсивному вызову не нужно знать текущее значение count. Рекурсивный вызов должен просто подсчитать количество элементов во вложенном подсписке, и мы добавим его возвращаемое значение к нашему текущему подсчету:

def countarr1(arr):
    if len(arr) == 0:
        return 0
    else:
        count = 0
        if isinstance(arr[0], list):
            count += countarr1(arr[0])
        else:
            count += 1
        count += countarr1(arr[1:])
        return count

arr=[1,2,[[4,5]],8,[7,4,5,6],12,34]
print("Answer: ",countarr1(arr))
# Answer:  11

Обратите внимание, что использование рекурсии для навигации по подспискам — хорошая идея; но использование рекурсии для перебора списка - плохая идея в python. В питоне рекурсия намного медленнее, чем for-циклы; кроме того, когда вы пишете arr[1:], создается совершенно новая копия массива! Это ужасно неэффективно. Вместо этого мы можем использовать цикл для перебора списка:

def countarr2(arr):
    count = 0
    for x in arr:
        if isinstance(x, list):
            count += countarr2(x)
        else:
            count += 1
    return count

arr=[1,2,[[4,5]],8,[7,4,5,6],12,34]
print("Answer: ",countarr2(arr))
# Answer:  11

Другие вопросы по теме