Рекурсия внутри класса в python

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

Если нам нужно пройти по бинарному дереву в режиме предварительного порядка, один из способов сделать это (указан здесь http://interactivepython.org/courselib/static/pythonds/Tree/TreeTraversals.html) — использовать что-то вроде:

def preorder(self):
     print(self.key)
     if self.leftChild:
        self.leftChild.preorder()
     if self.rightChild:
        self.rightChild.preorder()

Я не понимаю, почему можно делать что-то вроде self.leftChild.preorder(). Если кто-то может указать мне в правильном направлении, я буду очень признателен.

Обновлено:

Как было предложено в комментариях, я публикую полный код, над которым работаю. Я получил это от firecode:

post_ordered_list = []
class BinaryTree:

    def __init__(self, root_data):
        self.data = root_data
        self.left_child = None
        self.right_child = None

    def postorder(self):
        if self.left_child:
            self.left_child.postorder()
        if self.right_child:
            self.right_child.postorder()
        post_ordered_list.append(self.data)
        return post_ordered_list


    def get_right_child(self):
        return self.right_child

    def get_left_child(self):
        return self.left_child

    def set_root_val(self, obj):
        self.data = obj

    def get_root_val(self):
        return self.data

Вам, вероятно, потребуется включить определение класса в свой вопрос, чтобы мы поняли смысл одного из его методов.

Reedinationer 18.04.2019 01:52

Добавлен весь класс

Emin Mammadov 18.04.2019 01:55

Можно поконкретнее, что тебе не понятно? Почему self.leftChild.preorder() невозможно?

Blorgbeard 18.04.2019 01:58

да. Почему self.leftChild.preorder() работает? LeftChild — это атрибут экземпляра, верно? Как это связано с методом предварительного заказа?

Emin Mammadov 18.04.2019 02:03

То, что не показано в вашем коде, но почти наверняка верно, это то, что self.leftChild и self.rightChild сами являются экземплярами BinaryTree, поэтому у них также есть метод preorder().

Mark 18.04.2019 02:08

@MarkMeyer, ты хочешь сказать, что вне класса есть что-то вроде left_child=BinaryTree(data)?

Emin Mammadov 18.04.2019 03:03

@EminMammadov да, код не показывает, как добавляются дочерние узлы, но в большинстве реализаций они также будут деревьями. Это не обязательно должно быть за пределами класса, но оно не отображается в классе в вопросе.

Mark 18.04.2019 03:07
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
7
110
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Рекурсия — это то, что трудно понять, когда вы только начинаете программировать. Одна из вещей, которую вы должны понять, заключается в том, что если вы правильно настроили базовые случаи (например, если self.left, если self.right и т. д.), вы можете использовать функцию, над которой работаете, как если бы она уже работает.

Давайте подумаем о примере подсчета всех узлов в дереве с помощью функции countNodes(): Скажем, у нас есть узел x, который находится где-то в дереве. Когда x вызывается для подсчета всех узлов своего поддерева, как он это делает? Помните, как я говорил, что мы должны сделать вид, что функция countNodes() существует и что она уже работает? Что ж, давайте так и сделаем. X должен считать себя, потому что это узел. Поэтому счет пока равен 1. После того, как он посчитал себя, он должен посчитать все узлы слева и все узлы справа. Таким образом, чтобы подсчитать узлы в дереве, начиная с любого узла x, мы вызываем countNodes() для левого и countNodes() для правого.

Таким образом, код будет выглядеть так:

def countNodes(self):
    count = 1
    if self.left: #Checking to see if we have a left subtree
        count += countNodes(self.left)
    if self.right: #Checking to see if we have a right subtree
        count += countNodes(self.right)
    return count

Итак, теперь, когда мы лучше понимаем рекурсию, давайте вернемся к вашему примеру. Мы хотим пройти по дереву в режиме предварительного заказа. Так что это по определению означает, что мы должны сначала посетить наш узел. Затем мы смотрим, есть ли у нас левый узел для посещения. Если это так, мы обходим левые узлы в порядке предварительного порядка. Если у нас есть правильная вершина, мы также обходим все дерево в предварительном порядке.

Основные выводы из рекурсии:

  1. Убедитесь, что у вас есть хорошие базовые случаи (например, если node.right == null)

  2. Найдите закономерность повторения. Вы можете использовать функцию в функции, как если бы она уже была построена, если у вас есть правильно работающие базовые случаи.

Надеюсь, это поможет!

Редактировать:

Чтобы объяснить ту часть себя, на которую я не ответил. Self представляет любой объект, вызываемый методом. Итак, если у нас есть узел x, с которым мы хотим выполнить предварительный обход, мы будем использовать x.preorder(). Тогда в функции это будет выглядеть так:

def preorder(self): #self is whatever calls this function
    print(self.value) #whaver you want to print
    if self.left:
       self.left.preorder() #now in this function call, self will be self.left
   if self.right:
       self.right.preorder() #same idea here


root.preorder() #calling the function. Now self will be root.

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

Спасибо за ответ. Я понимаю часть рекурсии. Я не совсем понимаю часть ООП. Почему я могу сделать что-то вроде self.left_child.postorder()? Я не могу понять, используя атрибут экземпляра, который, как я понимаю, является переменной (я могу передать любое целое число, которое захочу, и назначить его для left_child) для доступа к методу. В моем понимании эти . означают, что я обращаюсь к методу внутри класса. Итак, если это будет что-то вроде tree= BinaryTree(), а затем tree.post_order(), нет проблем.

Emin Mammadov 18.04.2019 02:20

Ох, ну ладно. Теперь я вижу, где неразбериха. По сути, python поначалу сбивает с толку то, что такое self. Если у нас есть узел с именем x, который мы хотим обойти в режиме предварительного заказа, мы говорим x.preorder(). x — это объект, и мы говорим, что хотим вызвать предварительный порядок на узле x. Self — это в основном объект, вызвавший метод. В данном случае это будет х. Итак, self.left относится к x.left, self.right относится к x.right. Поскольку x вызывает функцию, x становится self. Я надеюсь, что это проясняет некоторую путаницу.

Nathaniel 18.04.2019 02:24

Итак, self.left_child действительно создает объект?

Emin Mammadov 18.04.2019 02:53

Нет, это не так. Он вызывает переменную-член, которую разделяет каждый объект узла. У каждого узла есть левый и правый узлы, которые к нему присоединены. Может быть, это может помочь. micropyramid.com/blog/understand-self-and-в этом-метод-в-pyt‌​хон-класс/

Nathaniel 18.04.2019 04:39

Рекурсия состоит из двух шагов: базового случая и рекурсивного случая. Базовый случай останавливает рекурсию, но, насколько я понимаю из вашего поста, вы ищете, почему работает рекурсивный случай. Ключом к рекурсивному случаю является то, что следующая рекурсия меньше, чем та, которая ее вызвала.

Ваша функция предварительного заказа принимает корень в качестве параметра. В первый раз, когда вы вызываете предварительный заказ, вы используете self в качестве параметра, что означает, что вы берете все дерево. Если у корня есть левый дочерний элемент, то следующим шагом будет рассмотрение левого поддерева. Корень левого поддерева является левым дочерним элементом текущего корня, поэтому он передается в качестве параметра. Вы продолжаете рекурсию вниз по левой стороне, пока есть левый дочерний элемент. Рекурсия останавливается, когда нет левого дочернего узла, и код продолжается до правого дочернего оператора if для этого последнего узла.

Я мог бы продолжить, но не без путаницы. (Надеюсь, я еще не запутался!) Проверьте эта страница в бинарном поиске, это очень похожее действие.

Спасибо за ваше объяснение. Я понимаю часть рекурсии. Моя путаница лежит в области ООП. Насколько я понимаю, self.left_child — это атрибут экземпляра. Если бы это был объект скажем tree=BinaryTree(), то я бы понял, что могу сделать что-то вроде tree.postorder(); Я использую свой экземпляр для доступа к одному из методов, в данном случае postorder(). Однако я использую атрибут экземпляра (в моем понимании это переменная, которая получит значение, которое будет передано ей), и я не понимаю, как я могу использовать атрибут экземпляра для доступа к методу внутри этого класса.

Emin Mammadov 18.04.2019 02:15

left_child и right_child также имеют тип BinaryTree. Видеть типы в Python немного сложнее, чем в некоторых других языках, таких как Java или C++. Тип переменной определяется тем, как вы ее используете, поэтому именно то, что вы вызываете postorder() для left_child и right_child, определяет, что эти переменные являются BinaryTree.

Shoikana 18.04.2019 14:38
Ответ принят как подходящий

В Python classInstance.methodName(params...) эквивалентно вызову метода methodName некоторого класса, где classInstance передается как self:

  1. Определите, какой класс является classInstance, который вы можете получить с помощью type(classInstance) или classInstance.__class__ (ищите код создания экземпляра classInstance = Class(...)).
  2. Определите, определяет ли класс methodName с self в качестве первого параметра. self будучи первым параметром, указывает, что это метод экземпляра, а не метод класса или статический метод (см. ниже объяснение ссылки). Если есть такой метод, то вызовите его с self равным classInstance, то есть эквивалентным вызовом будет Class.methodName(classInstance, params...).
  3. Если класс не определяет methodName, найдите ближайшего предка, который определяет, следуя документации для наследование, и сделайте то же самое. В этом случае эквивалентный вызов будет выглядеть так: ParentClass.methodName(classInstnace, params...).

Вот пример:

n4 = BinaryTree(4)
n5 = BinaryTree(5)
n3 = BinaryTree(3)
n3.left_child = n5
n3.right_child = n4
"""
  3
 /  \
4    5
"""

n3.postorder()
# result [5, 4, 3]

При звонке n3.postorder()

  • self это n3
  • self.leftchild это n5
  • self.rightchild есть n4.

Когда выполняется n3.postorder(), следующей строкой будет self.left_child.postorder(), и она вызывает метод postorder с self.leftchild (т. е. n5) в качестве первого параметра (т. е. «я») postorder. Следовательно, в контексте этого рекурсивного вызова:

  • self это n5
  • self.leftchild это None
  • self.rightchild есть None.

Затем вызов этого метода завершается и выполняется следующая строка self.right_child.postorder(), и это то же самое, что и выше, но с n4, поскольку мы возвращаемся в исходный контекст, где self.right_child — это n4.

Подробная документация доступна по адресу: https://docs.python.org/3/tutorial/classes.html

Разница между экземпляром, классом и статическими методами: https://julien.danjou.info/guide-python-static-class-abstract-methods/

В самом первом предложении вы classInstance.methodName(params...), что имеет смысл. Однако left_child не является экземпляром класса, верно? Итак, почему я могу сделать self.left_child.postorder()? Если бы было как-то так BinaryTree.postorder(self.left_child)), то я бы понял (я так решил проблему, но глядя на решения и выложенные ответы, похоже, что все используют другой метод)

Emin Mammadov 18.04.2019 02:27

Обновлен ответ, чтобы включить наследование. left_childявляется экземпляр класса. Вы можете использовать self.left_child.postorder(), потому что self.left_child — это n5, а n5 имеет тип BinaryTree, поскольку n5 было определено с помощью n5 = BinaryTree(5).

ELinda 18.04.2019 06:16

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