5 распространенных ошибок начинающих программистов на Python

Original author: pythoncircle.com
  • Translation
В первые дни работы программистом на Python все мы сталкиваемся с разными типами багов в нашем коде, которые после нескольких болезненных часов в StackOverflow оказываются не багом, а фичей Python'а. Ниже приведены 5 самых распространенных ошибок, которые делают большинство начинающих программистов на Python. Давайте немного о них узнаем, чтобы сэкономить несколько часов, задавая вопросы на страницах и в группах в Facebook.

1. Копирование словарей или списков


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

Неправильно:

>>> dict_a = {"name": "John", "address":"221B Baker street"}
>>> dict_b = dict_a

Теперь, если вы меняете или обновляете dict_b, то dict_a тоже будет изменен — и все это благодаря оператору присваивания. Используя этот оператор, вы пытаетесь сказать, что dict_b будет указывать на тот же самый объект, что и dict_a.

>>> dict_b["age"] = 26
>>> dict_b
{'address': '221B Baker street', 'name': 'John', 'age': 26}
>>> dict_a
{'address': '221B Baker street', 'name': 'John', 'age': 26}
>>>

Правильно: использовать методы copy() или deepcopy().


>>> dict_c = dict_b.copy()
>>> dict_c["location"] = "somewhere"
>>> dict_c
{'address': '221B Baker street', 'name': 'John', 'age': 26, 'location': 'somewhere'}
>>> dict_b
{'address': '221B Baker street', 'name': 'John', 'age': 26}
>>> dict_a
{'address': '221B Baker street', 'name': 'John', 'age': 26}
>>>

См. разницу между copy и deepcopy.

2. Ключи словарей


Давайте попробуем добавить значения в словарь:

>>> dict_a = dict()
>>> dict_a
{}
>>> dict_a[1] = "apple"
>>> dict_a[True] = "mango"
>>> dict_a[2] = "melon"

Если мы попробуем вывести словарь на экран, что мы увидим?

>>> dict_a
{1: 'mango', 2: 'melon'}

Что случилось, где ключ True?

Надо помнить, что класс Boolean наследуется от Integer (целых чисел). А целое число, эквивалентное True — это 1; эквивалент False — это 0. Значит, значение по ключу 1 просто переписывается.

>>> isinstance(True, int)
True
>>> isinstance(False, int)
True
>>> True == 1
True
>>> False == 0
True

3. Обновление списков или словарей


Допустим, вы хотите добавить элемент в список.

>>> list_a = [1,2,3,4,5]
>>> list_a = list_a.append(6)
>>> list_a
>>> # prints nothing

Или пытаетесь обновить словарь.

>>> dict_a = {"a" : "b"}
>>> dict_a = dict_a.update({"c" : "d"})
>>> dict_a
>>> # prints nothing

А теперь давайте попробуем упорядочить список.

>>> list_b = [2,5,3,1,7]
>>> list_b = list_b.sort()
>>> list_b
>>> # prints nothing

Почему ничего не выводится, что мы делаем не так?

Большинство методов контейнеров (такие как sort, update, append, add, и т. д.) соптимизированы в целях производительности — и избегают ненужного создания отдельных копий.

Не пытайтесь присвоить возвращаемое значение таких методов в переменную.

Правильно:

>>> list_a = [1,2,3,4,5]
>>> list_a.append(6)
>>> dict_a = {"a" : "b"}
>>> dict_a.update({"c" : "d"})
>>> dict_a
{'c': 'd', 'a': 'b'}
>>> list_a.sort()
>>> list_a
[1, 2, 3, 4, 5, 6]

4. Интернированные (Interned) строки


В некоторых случаях Python пытается переиспользовать существующие неизменяемые объекты. Интернирование строки — это один из таких случаев.

>>> a = "gmail"
>>> b = "gmail"
>>> a is b
True

Здесь мы пытались создать два различных объекта — строки. Но когда мы проверили их на эквивалентность, выяснилось, что они полностью совпадают. Такое происходит из-за того, что Python не создал другого объекта b, а сделал b указывающим на первое значение «gmail».
Все строки длиной 1 являются интернированными. Строки, в которых есть что-то за исключением ASCII-символов, цифр и знака подчеркивания, не будут интернированными.
Давайте проверим.

>>> a = "@gmail"
>>> b = "@gmail"
>>> a is b
False

Также надо запомнить, что == отличается от оператора is. Оператор == проверяет, являются ли значения эквивалентными или нет, тогда как оператор is проверяет, ссылаются ли обе переменные на один и тот же объект.

>>> a = "@gmail"
>>> b = "@gmail"
>>> a is b
False
>>> a == b
True

Так что помните про это, когда используете неизменяемые строки или операторы == и is.

5. Аргументы по умолчанию вычисляются один раз


Рассмотрим пример:

def func(a, lst=[]):
    lst.append(a)
    return lst
print(func(1))
print(func(2))

Как вы думаете, что будет выведено после двух принтов?

Давайте запустим код.

>>> def func(a, lst=[]):
... lst.append(a)
... return lst
... 
>>> print(func(1))
[1]
>>> print(func(2))
[1, 2]

Почему во втором случае выводится [1, 2]? Не должно ли это быть просто [2]?

Итак, подвох в том, что аргументы по умолчанию вычисляются только один раз.При первом вызове функции — func(1) — список оценили и поняли, что он пустой. Значит, к нему можно добавить 1. Но при втором вызове — func(2) — в списке уже есть один элемент, поэтому выводится [1, 2].

Бонус: Не надо смешивать пробелы и табы. Просто не надо.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 69

    +2
    Хорошо, спасибо за информацию, единственное что не совсем очевидно это последний пример.
    То есть когда мы объявляем список lst=[], он создаётся один раз и при каждом вызове функции он обращается к нему, а если мы будем создавать его внутри функции, то он будет каждый раз его создавать? Хотелось бы понять его видимость)
      +1
      Речь идет только про параметры по умолчанию. Если внутри функции создается локальный объект, то при следующем вызове он создастся вновь. Вот пример:
      def get_lst(a):
          lst = []
          lst.append(a)
          return lst
      get_lst(1)
      [1]
      get_lst(2)
      [2]
      

      Вы же про это спрашивали, верно?
        +2
        Следовало бы добавить, как правильно использовать аргументы по умолчанию (если нужно передавать что-то, изменяемое, например список, экземпляр класса или словарь)

        def func(a, lst=None):
            if lst is None: 
                lst = []
            lst.append(a)
            return lst
        print(func(1))
        print(func(2))
        print(func(3, [1]))
        ...
        [1]
        [2]
        [1, 3]
          +3
          Также можно добавить, что иногда None может быть валидным значением параметра, тогда такой вариант не позволит отличить получение значения по умолчанию или явное указание None для параметра при вызове функции. Тогда можно использовать специальное дефолтное значение для параметра:
          DEFAULT_VAL = object()
          def func(a, lst=DEFAULT_VAL):
              if lst is DEFAULT_VAL: 
                  lst = []
              elif lst is None:
                  # some None handling
                  pass
              lst.append(a)
              return lst
          
            +2
            спасибо за подробный разбор. Мне кажется это тот случай когда багу назвали фичей.
          +5

          Следовало бы добавить первопричину такого поведения: Питон неотложно исполняет инструкции, соответственно, когда он натыкается на


          def f(val = []):
              print(id(val))

          то создаётся объект f типа function и записываются вычисленные значения аргументов в аттрибуты f.__defaults__ и f.__kwdefaults__.


          Это легко проверить:


          >>> f()
          140694137408008
          
          >>> print(id(f.__defaults__[0]))
          140694137408008
            0
            Теперь понятнее, спасибо
          0
          Я немного идиот во внутреннем устройстве питона, но как я понимаю неочевидное поведение с параметром по умолчанию возникает потому что muttable объект создаётся на этапе импорта модуля, а не вызова функции.
            0
            Del. Написали выше точнее
          0
          del.
            +5
            > 1. Копирование словарей или списков
            > 3. Обновление списков или словарей
            > 4. Интернированные (Interned) строки

            Это же объясняется у Лутца сразу же в первой паре сотен страниц. Поэтому первая ошибка — не читать учебник, а думать, что по статьям в интернете всему научишься.
            Тут не нужна статья, чтобы догадаться, что процедура вставки элемента в список новый список не вернет, почему она вообще должна это сделать?
            И списки можно копировать через срезы:
            b = a[:]

            Маленькие строки и числа кэшируются. is проверяет на указание на одну область памяти, это и есть эквивалентность, а не то, что вы указали под этим словом.
              +1
              процедура вставки элемента в список новый список не вернет, почему она вообще должна это сделать?

              В некоторых языках коллекции по-умолчанию иммутабельны

                0
                Для читаемости можно
                b = a.copy()
                  0
                  а вот через срезы гораздо быстрее
                    0
                    В новых версиях питона так же или даже быстрее. Пруфы по той же ссылке)
                –3
                >>list_a = list_a.append(6)

                Присваивание лишнее. С update у dict тоже самое.
                  +3
                  То, что статья про распространенные ошибки, вас не смутило?
                    0
                    Да я потом уже увидел, поплавило меня что-то, голова уже несколько дней болит, извиняйте
                  +6
                  Так как довольно часто (если не везде) используется NumPy, то в третий пункт для списков я бы добавил, что при случае с np.array() фокус с методом np.append() происходит как раз в обратную сторону, т.е. для добавления элемента в список нужно присваивать переменной именно возвращаемое значение метода, т.к. простой вызов метода np.append() не добавит значение в существующую переменную, а создаст новый объект:
                  >>> a = np.array([1,2,3])
                  >>> a
                  array([1,2,3])
                  >>> np.append(a, 4)
                  array([1,2,3,4])
                  >>> a
                  array([1,2,3])
                  >>> a = np.append(a, 4)
                  >>> a
                  array([1,2,3,4])
                  

                  Бывает, что на автомате пишешь и забываешь про это, а потом ищешь, где же ты потерял значения.
                    +3
                    TL/DR: проблема не в вас, а в NumPy.

                    Стандартные библиотеки Python обычно построены с учётом принципа CQS. Python-программисты обычно привыкают к нему. Это позволяет создавать простые и интуитивно понятные API.

                    NumPy нарушает этот принцип без видимых на то причин, он непитоничен, поэтому им трудно пользоваться.
                      –7

                      А зачем давать ссылку на английском, если есть на русском?

                        +5
                        Мне так проще.
                          –4

                          Интересно, а хоть один из минусующих нормально ответить на мой вопрос сможет, или все, на что они способны, — по тихому анонимно сливать мой комментарий по делу? Если вы отвечаете за себя, конечно.

                            +3
                            Я минусовал по той причине, что ваш комментарий не помощь, а критика на пустом месте, засоряющая эфир. Если бы вы оставили комментарий «вот ссылка для тех, кому удобнее читать по-русски», были бы плюсы.
                          +1

                          Размер массивов numpy, можно сказать, неизменяемый. Это не динамический контейнер как список в Python. Поэтому функция np.append возвращает новый массив (так же как любые другие функции или методы, завязанные на размерности массива). Да, вы можете сделать resize для существующего массива, но это будет перераспределение памяти под массив, и только если на массив нет других ссылок.


                          numpy вполне себе питоничен и им легко пользоваться когда вы работаете с матрицами или многомерными массивами и выполняете над ними векторизованные операции.

                            +2
                            У иммутабельных типов в Python нет метода 'append'. Он есть у мутабельных. Называть так метод, создающий новый объект, как минимум неконсистентно. А по-честному, так это просто сбивает с толку пользователей.

                            NumPy вообще-то непитоничен и в более простых вещах, например, не придерживается PEP-8 в именованиях методов и параметров, и насыщен сокращениями вплоть до полной нечитаемости. Откройте справочник и убедитесь.

                            Я не уверен, зачем это так сделано, но полагаю, что это должно было как-то помочь пользователям MatLab или Fortran перейти на Python. Если можете, просветите меня в этом вопросе. Но говорить, что NumPy питоничен − это просто отрицать очевидное.
                              0
                              не придерживается PEP-8 в именованиях методов и параметров, и насыщен сокращениями вплоть до полной нечитаемости. Откройте справочник и убедитесь.

                              Вот вообще не вижу, где numpy в именовании методов не придерживается PEP-8? Вот вижу, например, как стандартный logging и unittest не придерживаются :)


                              Сокращения, вы имеете в виду разные математические функции? Это общеупотребительные устоявшиеся названия. Вы же не ругаете Python за то, что там есть len, pow, min, max и модуль math, в котором есть sin, cos ну и т. д.


                              Я не вижу в чём numpy непитоничен, для меня это точно так же не очевидно. Непитоничность очевидна для logging и unittest, которые скопированы с Java библиотек, очевидна для обёрток, которые тупо сгенерированы каким-нибудь SWIG или скажем для PyQt/PySide, который копирует API Qt без всяких pythonic-фишек сверху.

                                +2
                                То есть все эти asarray, infstr, nditer, ma − это всё общепринятые математические символы?

                                А то, что функции NumPy игнорируют возможности Python по обращению с аргументами и требуют всё приводить к единственному аргументу-последовательности вместо args − это тоже питонично? Вот это, например, как следствие: github.com/numpy/numpy/issues/6555?
                                  0

                                  Я соглашусь, что в numpy есть косяки с api и с поддержкой iterable. Ну вот, например, самый явный:


                                  np.zeros(4, 5)  # TypeError: data type not understood
                                  np.zeros((4, 5))  # ok
                                  
                                  np.random.rand(4, 5)  # ok
                                  np.random.rand((4, 5))  # TypeError: 'tuple' object cannot be interpreted as an integer

                                  Неконсистентность есть, конечно.


                                  Или вот, например:


                                  a = np.array([1, 2, 3])
                                  if a:
                                      pass
                                  # ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
                                  
                                  a = np.array([])
                                  if a:
                                      pass
                                  DeprecationWarning: The truth value of an empty array is ambiguous. Returning False, but in future this will result in an error. Use `array.size > 0` to check that an array is not empty.

                                  Ну и iterable, конечно


                                  g = (_ for _ in range(10))
                                  np.array(g)
                                  Out[62]: array(<generator object <genexpr> at 0x0000017750CCC048>, dtype=object)

                                  То есть все эти asarray, infstr, nditer, ma − это всё общепринятые математические символы

                                  Нет, конечно :)
                                  Но в Python тоже есть str, iter, asyncio, io, sys, enum и т. д.


                                  Смысл придираться к подобным сокращениям, если в рамках предметной области ясно о чём идёт речь.

                                  0
                                  logging и unittest действительно чужеродны, но они хотя бы понятны. Потому что они скопированы с Java.

                                  То, что NumPy не просто чужероден, а выглядит как жертва обфускации, я могу объяснить только тем, что он, возможно, скопирован с Fortran.
                                    0

                                    logging и unittest монстроузны и избыточны именно потому что просто скопированы из Java. :)


                                    Сравните их с пакетами loguru и pytest. При том, что pytest не только более простой и изящный в использовании (и плевать, что там под капотом дикая черная магия), но ещё и более мощный с точки зрения функциональности и рсширяемости.

                                      0
                                      Я сам вовсю пользуюсь pytest, you're preaching to the choir. Просто как по мне, так лучше многословность Джавы, чем вот эта ситуация, когда буквально каждый идентификатор в библиотеке − это самобытное сокращение.
                                        0

                                        На ваш взгляд, насколько готовая к проду библиотека loguru?

                                          0

                                          Я использую loguru в новых проектах и пока не сталкивался с какими-то проблемами. Для небольших и экспериментальных проектов её даже настраивать не надо, просто используешь и всё работает. Вполне себе production ready библиотека. Она очень простая в отличие от замороченного logging, но в то же время умеет всё или почти всё, что умеет logging. Также может конфигурироваться через конфиг.


                                          Кстати, на счет конфигов, вот такие вещи меня просто ставят в тупик в logging:


                                          Warning
                                          
                                          The fileConfig() function takes a default parameter, disable_existing_loggers, which defaults to True for reasons of backward compatibility. This may or may not be what you want, since it will cause any non-root loggers existing before the fileConfig() call to be disabled unless they (or an ancestor) are explicitly named in the configuration. Please refer to the reference documentation for more information, and specify False for this parameter if you wish.
                                0

                                И ещё на счёт CQS в Python.


                                It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both.

                                Привет, dict.setdefault :)

                                0
                                хорошее замечание
                                0
                                Если правильно понимаю, можно уточнить про ключи (пункт 2). «Эквивалентно» для True и 1 значит то, что методы __hash__ у них возвращают 1 (для предотвращения коллизий, насколько мне известно, в словаре ещё производится сравнение ключей по __eq__, что для 1 и True возвращает также True). Получается, можно написать класс вида

                                class Deceiver:
                                    def __hash__(self):
                                        return 1
                                    def __eq__(self, everything):
                                        return True
                                


                                и он будет вытеснять ключ int(1), хоть класс и не наследуется от int:

                                a = {1: 1, 2: 2}  # a = {1: 1, 2: 2}
                                a[Deceiver()] = 100  # a = {1: 100, 2: 2}
                                
                                  +2
                                  Кто-нибудь на практике встречал использование a[True] и a[1] в одном месте? Боюсь, что начинающий в этом не допустит ошибки, т.к. просто не будет подходящей ситуации.
                                    0
                                    никогда не было необходимости написать a[True]
                                      +1
                                      Я, конечно, фантазирую, но допускаю ситуации вида:
                                      keys = read_keys_from_json_from_other_service() #[0, 1, True, 2]
                                      res = dict()
                                      for key in keys:
                                          value = str(key)
                                          # Some additional code
                                          res[key] = value

                                      В результате получим, что в res будет {0: '0', 1: 'True', 2: '2'}

                                    0
                                    append, update это методы, который вызываются у инстанса объекта, о каком присваивании может вообще идти речь? тут дело не в новичке питона, а новичке в IT и программировании в частности.
                                      +3
                                      Вы неправы. В других ЯП, например, JavaScript, есть много API, построенных на том, что методы всегда возвращают ссылку на объект, для которого были вызваны. Этот приём называется method chaining. Программисты, привыкшие к нему, пытаются применить его в Python − чаще всего безуспешно, в Python это не принято.

                                      Гвидо не любит цепочки вызовов: I find the chaining form a threat to readability.
                                      0
                                      Был бы очень благодарен, если бы кто-нибудь рассказал как правильно работать с импортами. Делать модуль глобальным иногда плохая практика, а просто так импортировать модуль из родительской директории питон не даёт. Не нужно напоминать, что это тоже плохая практика, это всегда trade-off, просто дайте рецепт. Java, C++, Ruby, и практически все популярные языки позволяют это делать.
                                      • UFO just landed and posted this here
                                          0

                                          В свое время больно обжегся на таком:


                                          correct = [ [1] * 3 for i in range(0, 3)]
                                          correct[0][0] = 10
                                          print(correct) # [ [10, 1, 1], [1, 1, 1], [1, 1, 1] ]
                                          
                                          incorrect = [[1] * 3] * 3
                                          incorrect[0][0] = 10
                                          print(incorrect) # [ [10, 1, 1], [10, 1, 1], [10, 1, 1] ]

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

                                          • UFO just landed and posted this here
                                            +1
                                            1. Такая проблем может возникнуть и людей пришедших из древнючих языков (вроде Бейсика или Перла), где присваивание идет по значению.


                                            2. Имхо это легаси Перла и косяк дизайна Питона. Булевы значения никакого отношения к целочисленным значениям не имеют, и то что True == 1 — это нифига не очевидная особенность языка. Впрочем все равно лучше чем у прародителя — true/false вообще отсутствуют, а в качестве false используется 0, '' или undef, а в качестве true — все остальное.


                                            3. Лишь вопрос API. Например такой подход к построению API не позволяет делать чейнинг операций:


                                              a = []
                                              a.append('A').append('B').append('C') #=> ['A', 'B', 'C']

                                            4. А есть еще и языки где строчки вообще иммутабельные.


                                            5. Скорее косяк Питона — тут ничего удивительного что у людей возникают вопросы.


                                              Например в Ruby:


                                              def foo(a = [])
                                                a << "world"
                                              end
                                              
                                              foo()
                                              => ["world"]
                                              foo()
                                              => ["world"]

                                              JavaScript:


                                              > function foo(bar = []) { bar.push("world"); return bar; }
                                              > foo()
                                              ["world"]
                                              > foo()
                                              ["world"]

                                              0

                                              Насчет 4 — Строки и в python считаются иммутабельным типом.
                                              В остальном же, ИМХО, приведенные ответы на пункты из статьи являются всего лишь субъективными (абсолютно валидными при этом) дополнениями, так что не вижу необходимости комментировать.

                                                0
                                                древнючих языков (вроде Бейсика или Перла)
                                                К сведению: разработка перла началась в 1987-м, а питона в 1989-м
                                                0

                                                < del >

                                                  0
                                                  Спасибо, очень полезная статья. Прошёл в своё время через все эти ошибки. Исключая, конечно, True и 1. Это, действительно, маловероятно.
                                                    +2
                                                    Питон прекрасный язык, но есть два класса людей, между которыми лежит пропасть: (грубо говоря) те, которые писали питон, и все остальные. Это очень эффективный инструмент, когда ты знаешь, что там под капотом. Проблема в том, что он всё чаще используется для обучения: ничего не надо знать, учись программированию на питоне! Я своими глазами видел тысячи человеко-часов, потраченных на дебаг именно подобных ошибок. Причём большинство из приведённых в статье ошибок основаны на том, что в питоне всё идёт через указатели (ссылки). Но есть и куча других проблем, например, scope:

                                                    for i in range(10):
                                                      pass
                                                    print(i)
                                                    


                                                    WTF?! Почему я могу достучаться до переменной i вне блока, её объявившего?

                                                    Ну или вот такое:

                                                    def foo():
                                                        print(a)
                                                    
                                                    a = 1
                                                    foo()
                                                    


                                                    Этот код просто выведет 1 в консоль. А вот этот не скомпилируется:

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


                                                    А почему? А потому что по умолчанию scope в питоне глобальный, если переменная только на чтение, и локальный если на запись… Те, кому это не было очевидно с первого же взгляда, не забывайте отмечаться в комментариях :)

                                                    Ещё примеры подобных языков?
                                                      0

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


                                                      Про "не скомпилируется" — у питона чётко прописан механизм доступа к переменным. Когда в функции foo() происходит чтение переменной a, то интерпретатору не надо задумываться откуда происходит чтение. Сначала идёт поиск в локальном контексте, потом в глобальном.


                                                      А вот при присваивании всё сложнее:


                                                      def foo():
                                                          a = a + 1

                                                      С чего интерпретатор должен считать что при присваивании значения переменной a, её область видимости глобальна? Отстутствие a в локальном контексте — не причина так считать. Ведь есть ещё и вложенные функции! Например:


                                                      def foo():
                                                          a = 20
                                                      
                                                          def foo_inside():
                                                              a = a + 1
                                                      
                                                          foo_inside()

                                                      Здесь будет та же ошибка, хотя а не глобальна, а лежит вне локального контекста foo_inside().


                                                      Поэтому и существуют операторы global и nonlocal явным образом задающие контекст переменной.

                                                        0
                                                        Насколько я понял, на мой вопрос: «Почему у питона не такое поведение, как у большинства других языков?» вы ответили фразой «потому что так у питона».

                                                        Обратите внимание, я не критиковал сам инструмент, зачем мне на молоток ругаться. Я критикую его применение, т.к. насаживать гайки на резьбовые шпильки молотком не очень хорошо. Если ближе к делу, то в той стране, где я сейчас живу, питон в обязательном порядке преподаётся в школах, но про подобные неочевидные нюансы, разумеется, умалчивают. А потом бывшие школьники пытаются использовать тот инструмент, который, как им кажется, они освоили, и тратят феноменальное время на преодоление неочевидных косяков. Нехорошо.
                                                          0

                                                          Ну так по вашей логике, человек пришедший скажем из Питона в C, будет также недоумевать, а с чего это


                                                          #include "stdio.h"
                                                          
                                                          int main() {
                                                              for (int i = 0; i < 10; i++);
                                                              printf("%d", i);        
                                                              return 0;
                                                          }

                                                          не компилируется?


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


                                                          А учителям и ученикам можно порекомендовать соответствующий раздел туториала :)

                                                            +2
                                                            Если что-то не компилируется, то это просто прекрасно — компилятор нашёл ошибку. Хуже, когда компилируется, а с питоном, к сожалению, это случается чаще.

                                                            У меня вообще складывается мнение, что на программирование на питоне нужно выдавать корочки по типу водительского удостоверения только после сдачи экзаменов по всем подобным тонким моментам.

                                                            А для программирования на плюсах корочки по устройству памяти, неявным преобразованиям всего подряд в целочисленный тип и по UB :)
                                                        0
                                                        >> WTF?! Почему я могу достучаться до переменной i вне блока, её объявившего?
                                                        100 лет не держал в руках VB, но там вроде то же самое было. VB6, VBA.
                                                        Иногда было удобно.
                                                          0
                                                          WTF?! Почему я могу достучаться до переменной i вне блока, её объявившего?

                                                          Ну потому что цикл не создает никакого «блока», где объявляются переменны (в отличие от def или class).


                                                          for i in range(10):
                                                              a = 2
                                                          print(a)
                                                            0
                                                            Да, ответ таков, но он неочевиден, в чём и смысл моего сообщения.
                                                              0

                                                              А вот если мы возьмём не цикл, а list comprehensions, то поведение вообще будет разное между Py2 и Py3.


                                                              Py2:


                                                              >>> a = [i for i in range(10)]
                                                              >>> i
                                                              9

                                                              Py3:


                                                              >>> a = [i for i in range(10)]
                                                              >>> i
                                                              NameError: name 'i' is not defined

                                                              В Python 3 поправили область видимости для list comprehensions.


                                                              А как вам новый синтаксис из Py3.8 (PEP572)?


                                                              if (n := 15) > 10:
                                                                  n += 5
                                                              print(n)
                                                              20

                                                              И тоже n оказывается доступна вне if. :)

                                                          0
                                                          Или пытаетесь обновить словарь.
                                                          >>> dict_a = {"a" : "b"}
                                                          >>> dict_a = dict_a.update({"c" : "d"})
                                                          >>> dict_a
                                                          >>> # prints nothing


                                                          Я не питонист и вообще мимокрокодил. Беглая проверка показала что в результате у нас объект типа NoneType. Есть реальные сценарии когда про такой код можно сказать «да, так и задумывалось»? Или то что тут питон не плюётся ошибкой — просто причуда языка?
                                                            0

                                                            Язык, который «плюётся ошибкой» на правильный, но делающий не то что вам нужно код — пока что недостижимый уровнь.

                                                              0

                                                              Просто все функции, которые ничего не возвращают, возвращают None. :)


                                                              Если у вас функция возвращает, например: typing.Optional[str] (None или str), то на вас лежит ответственность проверять результат на None. Если функция просто ничего не возвращает, то, естественно, не надо проверять её результат и удивляться, что там None, а не ошибка.

                                                              –2
                                                              >>> dict_a[True] = "mango"


                                                              Неужели это — распространенная ошибка?
                                                              Могу только надеяться, что она может быть распространена в стране, которая начинается на «И» и заканчивается на «я».
                                                                0

                                                                Испания?

                                                                  0

                                                                  Я вот тоже в замешательстве. Может Италия или Индонезия?

                                                                  +1
                                                                  Ирландия?
                                                                  0
                                                                  Но цепочки вызовов хотя бы отчасти позволяют обходить урезанные возможности лямбд. Ещё цепочечные функции удобны в векторных и матричных вычислениях. Там читаемость, ИМХО, напротив, повышается.
                                                                    0
                                                                    Я думал, что Питон из JS не брал ничего…

                                                                    Only users with full accounts can post comments. Log in, please.