Почему этот учебник?

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

  1. конкретные вопросы в нишевой области, в которой я хочу продолжить развивать свой опыт
  2. вопросы для начинающих по Python или SQL, на которые я могу быстро ответить (возможно, сэкономлю кому-то много времени)

Первые более обогащают меня лично. Хороший ответ требует времени на исследование и на написание, и я всегда чему-то учусь по ходу дела.

Именно последние вдохновили меня на написание этого.

Через некоторое время вы замечаете закономерности.

Многие начинающие задавать вопросы не «понимают» StackOverflow; на мой взгляд, вам следует написать туда вопрос, когда все остальное не помогло. Он не очень подходит в качестве источника учебных пособий по запросу. Если вы новичок, на ваш вопрос почти наверняка ответили; научиться искать старые вопросы - лучшее использование вашего времени.

В стороне: я не имею в виду это в уничижительном смысле — поиск — это навык, приобретаемый с опытом. И, в конце концов, если вы потратите больше времени на поисксначала , это поможет вам написать более качественный вопроспозже , т. е. больше шансов получить хорошие ответы. , быстро.

Как? Хорошо,почему поиск разочаровывает? Потому что люди писали вопросы некачественно: нечеткие заголовки, минимум деталей, орфографические ошибки, слишком конкретные, когда они могли быть общими. Эти вещи ухудшают возможности поискаиотвечаемость. Искусство поиска предполагает обход таких ограничений.

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

Он не будет коротким, но я надеюсь, что он будет поучительным.

AttributeError: объект «tutorial» не имеет атрибута «get_to_the_point»

Как вы выучили Python? Мой маршрут был окольным.

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

Затем мне пришлось выучить IDL для моего исследовательского проекта магистратуры по астрономии, потому что в то время астрономы все еще были в плену у него.

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

Не будет преувеличением сказать, что за первые шесть месяцев работы в индустрии я узнал о написании хорошего кода гораздо больше, чем за все только что описанные пять лет.

(проблема в том, что я бы провел гораздо больше исследований, если бы раньше знал, как писать хороший код…)

Смотреть. Конечно, приятно завершить какое-то целенаправленное обучение и выполнить что-то осязаемое, что-то реальное. У меня нет с этим проблем! Однако. Если вы никогда, никогда не беспокоитесь об основах, если вы никогда, никогда не возвращаетесь назад и не заполняете пробелы, оставленные вашим безумным стремлением решить сегодняшнюю проблему, вы ограничиваете себя.

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

Почему бы и нет? Их никто никогда не учил!

Короче говоря, учить людей тому, как работать с Python, ожидая более глубокого понимания позже, — это естественный способ сделать это. Но я утверждаю, что можно сделать и наоборот: обучать Python задом наперед.

Это моя первая попытка.

Наконец, немного кода

Когда я раньше преподавал «основы» Python, я сначала сосредоточился на нескольких разных типах вещей, которые понимает Python — числах, тексте, истинности и ложности, — а затем перешел к

  1. присвоение переменной (x=1)
  2. простые выражения (например, сложение, умножение и т. д.)
  3. коллекции (списки, словари, кортежи, наборы)
  4. логика (если…иначе)
  5. функции …

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

Это нормально, но в результате мы показывали им что-то вроде этого.

class Saucepan:
    handle_length = 15  # cm

… было просто запоздалой мыслью.

Что, если мы сначала попробуем научиться этому?

Держите его стильным

Вы слышали о Теории форм Платона? Краткий отчет (хотя и ужасно искаженный для моих собственных целей) мог бы выглядеть так:

  • как узнать дерево?
  • правда, вы уже видели другие деревья, и вам сказали, что некоторые из них были деревьями, но ни одно из этих деревьев не похоже на дуб, который вы только что встретили на прогулке
  • и все же вы успешно обобщаете примеры деревьев, которые вы увидели, и признаете новое в качестве дерева!
  • следовательно, вы должны иметь какое-то более фундаментальное представление о дереве, а все те, что вы видели, являются лишь несовершенными примерами
  • эта «форма дерева», будучи совершенной, более реальна, чем любое из настоящих деревьев
  • вы, вероятно, сталкивались с ним еще до своего рождения, когда ваша душа путешествовала в мире форм, и именно поэтому вы так легко узнаете деревья сейчас, в испорченном физическом царстве… кхм.

Это (по сути) то, как работает Python.

когда я пишу

class Saucepan:
    ...

… Я определяю класс под названием «Кастрюля». Это не настоящая кастрюля (что бы это ни было…), это скорее идея кастрюли. Форма Сотейника, если хотите.

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

  • материал конструкции
  • форма
  • размер
  • и т. д.

В Python мы называем эти «атрибуты»:

class Saucepan:
    material = "Aluminium"
    nonstick = True
    handle_length = 15  # cm

Классы, все вниз

На этом этапе, если бы мы на самом деле делали это с нуля, мне пришлось бы сделать отступление и объяснить некоторые вещи, например «что это за True штука?» Вместо этого я собираюсь предположить, что вы это знаете.

Что, возможно, не пришло вам в голову, так это то, что когда я говорю вам

  • "Aluminium" — это строка символов
  • True — логическое значение
  • 15 — целое число

… Я действительно говорю вам, к какому классу относится каждый из них. Держитесь за эту мысль.

В этом безумии есть метод

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

Определенные специальные атрибуты охватывают эти «вещи, которые вы можете делать». Мы называем их методами. Синтаксис следующий:

class Saucepan:
    def make_spaghetti(self):
        print("Yum!")

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

Однако, думая в обратном направлении, я призываю вас начать думать о функциях (таких как print) как о методах, которые были отделены от какого-либо класса.

Точно так же вы можете думать об обычных переменных как обособленных атрибутах.

Объекты являются предметом

Специальный аргумент self функции make_spaghetti относится к конкретнойкастрюльке, которую вы используете для приготовления спагетти (обратите внимание: не класс, а пример класса).

Лучше поговорим о том, как создать такой пример, который мы называем экземпляром класса («Кастрюли, например, вот эта…»).

Общий термин для экземпляров класса — «объекты». Создать их достаточно просто:

class Saucepan:
    handle_length = 15  # cm
    def make_spaghetti(self):
        print("Yum!")


my_saucepan = Saucepan()

print(my_saucepan.handle_length)  # prints '15'

my_saucepan.make_spaghetti()  # hopefully you can guess what this does

Там. Простая пара скоб, и у нас есть кастрюля, которую мы можем использовать. Ням!

Резюме:

  • my_saucepan это объект
  • handle_length является атрибутом
  • make_spaghetti — это метод, который является особым типом атрибута.
  • Saucepan это класс

Сначала это казалось достаточно простым…

"Ждать!" Я слышу, как вы кричите: «Любая кастрюля, которую я создам с помощью этого класса, будет иметь пятнадцатисантиметровую ручку! Что, если я захочу несколько разных кастрюль, которые не будут одинаковыми?»

Ты прав. Это означает, что мне нужно ввести специальный метод:

class Saucepan:
    def __init__(self, material, nonstick, handle_length):
        self.material = material
        self.nonstick = nonstick
        self.handle_length = handle_length

my_saucepan = Saucepan("Aluminium", True, 15)

print(my_saucepan.material)  # prints 'Aluminium'

Что это __init__? В отличие от других методов, он вызывается, когда мы создаем экземпляр (этот процесс мы называем инициализацией).

Что следует отметить:

  • когда я создал my_saucepan, я передал некоторые аргументы "Aluminium", True, 15, которые отправляются этому специальному методу (self всегда является первым аргументом в определении метода, и он включается неявно, когда мы вызываем метод)
  • некоторые присваивания происходят внутри метода: self.material = material эквивалентно my_saucepan.material = material (дело в том, что он должен работать одинаково для любого экземпляра Saucepan)

Нашими атрибутами в предыдущих примерах были атрибуты класса — они одинаковы для любого экземпляра класса. Их нельзя изменить (по крайней мере, не меняя их для всех Сотейников, везде).

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

Некоторые тематические исследования

Это не полное обсуждение объектно-ориентированного программирования (иначе это длинное руководство было бы еще длиннее), но оно дает нам возможность решать эти проблемы.

Дело не в том, что каждый AttributeError: … object has no attribute … вопрос коренится в непонимании, но их достаточно, чтобы их распаковать.

Эти ошибки, как правило, являются «случай ошибочной идентификации». Либо объект не принадлежал к ожидаемому классу, либо классу не хватало метода или атрибута, которые, по вашему мнению, должны были быть.

Проиллюстрируем на некоторых примерах.

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

Пример с пандами

Возьми этотAttributeError: 'DataFrameGroupBy' object has no attribute 'to_csv'.

Популярная библиотека pandas может сбивать с толку из-за интенсивного использования цепочек методов.

Представьте себе это:

class SelfReplicatingSaucepan:
    def clone(self):
        return SelfReplicatingSaucepan()

first_saucepan = SelfReplicatingSaucepan()

final_saucepan = first_saucepan.clone().clone().clone().clone()

Мы можем сделать эту цепочку сколь угодно длинной, потому что каждый из них возвращает новый экземпляр SelfReplicatingSaucepan. Они решаются слева направо:

final_saucepan = first_saucepan.clone().clone().clone().clone()
final_saucepan = second_saucepan.clone().clone().clone()
final_saucepan = third_saucepan.clone().clone()
final_saucepan = fourth_saucepan.clone()
final_saucepan = fifth_saucepan

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

В случае связанного вопроса автор был сбит с толку, потому что метод DataFrame.groupby не возвращает объект DataFrame вообще; он возвращает объект DataFrameGroupBy, совершенно другой класс с другими методами.

Вооружившись приведенными выше подробностями, автор может сделать вывод, что им нужно использовать один из этих методов для обратного преобразования в DataFrame… который действительно имеет метод to_csv. Тогда правильно будет проконсультироваться с документацией.

Пример с numpy

Во-вторых, давайте рассмотрим этотAttributeError: 'numpy.ndarray' object has no attribute 'append'.

В этом случае автор взял некоторый код и попытался заменить встроенный Python list, который имеет метод append, более производительным numpy N-мерным массивом ndarray.

К сожалению, ndarray заранее выделяют свою память и не так гибки; у них нет метода append.

Кроме того, в библиотеке NumPy действительно есть функция append, которая создает совершенно новый массив вместо изменения существующего, new = append(array, new_values). При этом в принципе NumPyмогпредоставить ndarray метод append, возвращающий новый массив. Это позволит использовать цепочку методов wont_work = array.append(new_values). Возможно, разработчики посчитали, что это может ввести в заблуждение.

Я бы посоветовал этому автору просмотреть документацию ndarray и/или выполнить поиск дополнить массив numpy, что даст отличные и полезные результаты.

Неожиданный пример NoneType

Возможно, наиболее распространенными из всех являются ошибки AttributeError: 'NoneType' object has no attribute .... Обычно в день публикуется несколько постов.

Краткое отступление: в Python есть специальное значение None, которое вы можете рассматривать как пример класса NoneType. Если функция или метод не возвращает ничего, неявно возвращается None. Если вы еще этого не видели:

def some_function():
    print("I return nothing!")

result = some_function()

if result is None:
    print("Function returned nothing!")

Точно так же словари Python имеют метод get, который безопасно извлекает значение для определенного ключа. По умолчанию, если ключ отсутствует, полученное значение будет None:

my_dictionary = {"key1": "value1", "key2": "value2"}

k1_val = my_dictionary["key1"]  # the standard way to retrieve a value

k2_val = my_dictionary.get("key2")  # the safe way

k3_val = my_dictionary.get("key3")  # won't throw an error

if k3_val is None:
    print("my_dictionary did not contain 'key3'")

Короче говоря, None — это встроенный в Python способ представления отсутствующего значения.

Поэтому, когда вы сталкиваетесь с ошибками AttributeError: 'NoneType' object has no attribute '...', обычно объект, с которым вы ожидали работать, просто отсутствует.

Чтобы отладить ошибку, вы должны посмотреть на строку, где вызывается «отсутствующий» метод, и найти объект, который должен был его иметь. Например, если этот код выбросил AttributeError: 'NoneType' object has no attribute 'cool_method'

my_object = something_that_returns_an_object()

my_object.cool_method()

…тогда я бы знал, что something_that_returns_an_object на самом деле дал мне None, а не то, что я ожидал. Затем я мог бы исследовать эту часть кода.

Заключение

И так мы доходим до конца.

Это небольшой эксперимент. Мне интересно узнать, оказался ли этот подход полезным для вас.

Я открыт для идеи написать больше подобных статей в будущем, и у меня есть идея, как можно было бы объединить серию из них, но, конечно, я хочу быть уверенным, что эта длинная, более абстрактная трактовка действительно что-то добавит!

Итак, если вам понравилось, дайте мне знать ниже.