О Python


Python — это интерпретируемый, объектно-ориентированный язык программирования высокого уровня с динамической семантикой. Встроенные структуры данных высокого уровня в сочетании с динамической типизацией и динамическим связыванием делают его очень привлекательным для БРПС (быстрой разработки прикладных средств), а также для использования в качестве скриптового и связующего языка для подключения существующих компонентов или сервисов. Python поддерживает модули и пакеты, тем самым поощряя модульность программы и повторное использование кода.

О данной статье


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

Имея это в виду, в этой статье представлен «топ-10» тонких, трудных для обнаружения ошибок, которые могут допустить даже продвинутые разработчики Python.

Ошибка № 1: неправильное использование выражений в качестве значений по умолчанию для аргументов функций


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

>>> def foo(bar=[]):        # bar - это необязательный аргумент 
                            # и по умолчанию равен пустому списку.
...    bar.append("baz")    # эта строка может стать проблемой...
...    return bar

Распространенная ошибка в данном случае — это думать, что значение необязательного аргумента будет устанавливаться в значение по умолчанию каждый раз, как функция будет вызываться без значения для этого аргумента. В приведенном выше коде, например, можно предположить, что повторно вызывая функцию foo() (то есть без указания значения для агрумента bar), она всегда будет возвращать «baz», поскольку предполагается, что каждый раз, когда вызывается foo () (без указания аргумента bar), bar устанавливается в [ ] (т. е. новый пустой список).

Но давайте посмотрим что же будет происходить на самом деле:

>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]

А? Почему функция продолжает добавлять значение по умолчанию «baz» к существующему списку каждый раз, когда вызывается foo(), вместо того, чтобы каждый раз создавать новый список?

Ответом на данный вопрос будет более глубокое понимание того, что творится у Python «под капотом». А именно: значение по умолчанию для функции инициализируется только один раз, во время определения функции. Таким образом, аргумент bar инициализируется по умолчанию (т. е. пустым списком) только тогда, когда foo() определен впервые, но последующие вызовы foo() (т. е. без указания аргумента bar) продолжат использовать тот же список, который был создан для аргумента bar в момент первого определения функции.

Для справки, распространенным «обходным путем» для этой ошибки является следующее определение:

>>> def foo(bar=None):
...    if bar is None:		# or if not bar:
...        bar = []
...    bar.append("baz")
...    return bar
...
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]

Ошибка № 2: неправильное использование переменных класса


Рассмотрим следующий пример:

>>> class A(object):
...     x = 1
...
>>> class B(A):
...     pass
...
>>> class C(A):
...     pass
...
>>> print A.x, B.x, C.x
1 1 1

Вроде все в порядке.

>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1

Ага, все как и ожидалось.

>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3

Что за черт?! Мы же только изменили A.x. Почему же C.x тоже изменилось?

В Python переменные класса обрабатываются как словари и следуют тому, что часто называют Порядком разрешения методов (MRO). Таким образом, в приведенном выше коде, поскольку атрибут x не найден в классе C, он будет найден в его базовых классах (только A в приведенном выше примере, хотя Python поддерживает множественное наследование). Другими словами, C не имеет своего собственного свойства x, независимого от A. Таким образом, ссылки на C.x фактически являются ссылками на A.x. Это будет вызывать проблемы, если не обрабатывать такие случаи должным образом. Так что при изучении Python обратите особое внимание на аттрибуты класса и работу с ними.

Ошибка № 3: неправильное указание параметров для блока исключения


Предположим, что у вас есть следующий кусок кода:

>>> try:
...     l = ["a", "b"]
...     int(l[2])
... except ValueError, IndexError:  # To catch both exceptions, right?
...     pass
...
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
IndexError: list index out of range

Проблема здесь заключается в том, что выражение except не принимает список исключений, указанных таким образом. Скорее, в Python 2.x выражение «except Exception, e» используется для привязки исключения к необязательному второму заданному второму параметру (в данном случае e), чтобы сделать его доступным для дальнейшей проверки. В результате в приведенном выше коде исключение IndexError не перехватывается выражением except; скорее, вместо этого исключение заканчивается привязкой к параметру с именем IndexError.

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

>>> try:
...     l = ["a", "b"]
...     int(l[2])
... except (ValueError, IndexError) as e:  
...     pass
...
>>>

Ошибка № 4: непонимание правил области видимости Python


Область видимости в Python основана на так называемом правиле LEGB, которое является аббревиатурой Local (имена, назначенные любым способом внутри функции (def или lambda), и не объявленные глобальными в этой функции), Enclosing (имя в локальной области действия любых статически включающих функций (def или lambda), от внутреннего к внешнему), Global (имена, назначенные на верхнем уровне файла модуля, или путем выполнения global инструкции в def внутри файла), Built-in (имена, предварительно назначенные в модуле встроенных имен: open, range, SyntaxError ,...). Кажется достаточно просто, верно? Ну, на самом деле, есть некоторые тонкости в том, как это работает в Python, что подводит нас к общей более сложной проблеме программирования на Python ниже. Рассмотрим следующей пример:

>>> x = 10
>>> def foo():
...     x += 1
...     print x
...
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment

В чем проблема?

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

Таким образом, многие удивляются, когда получают UnboundLocalError в ранее работающем коде, когда он модифицируется путем добавления оператора присваивания где-нибудь в теле функции.

Эта особенность особенно сбивает разработчиков с толку при использовании списков. Рассмотрим следующий пример:

>>> lst = [1, 2, 3]
>>> def foo1():
...     lst.append(5)   # Это работает нормально...
...
>>> foo1()
>>> lst
[1, 2, 3, 5]

>>> lst = [1, 2, 3]
>>> def foo2():
...     lst += [5]      # ... а вот это падает!
...
>>> foo2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment

А? Почему foo2 падает, в то время как foo1 работает нормально?

Ответ такой же, как в предыдущем примере, но, по распространенному мнению, здесь ситуация более тонкая. foo1 не применяет оператор присваивания к lst, тогда как foo2 — да. Помня, что lst + = [5] на самом деле является просто сокращением для lst = lst + [5], мы видим, что мы пытаемся присвоить значение lst (поэтому Python предполагает, что он находится в локальной области видимости). Однако значение, которое мы хотим присвоить lst, основано на самом lst (опять же, теперь предполагается, что он находится в локальной области видимости), который еще не был определен. И мы получаем ошибку.

Ошибка № 5: изменение списка во время итерации по нему


Проблема в следующем куске кода должна быть достаточно очевидной:

>>> odd = lambda x : bool(x % 2)
>>> numbers = [n for n in range(10)]
>>> for i in range(len(numbers)):
...     if odd(numbers[i]):
...         del numbers[i]  # BAD: Deleting item from a list while iterating over it
...
Traceback (most recent call last):
  	  File "<stdin>", line 2, in <module>
IndexError: list index out of range

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

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

>>> odd = lambda x : bool(x % 2)
>>> numbers = [n for n in range(10)]
>>> numbers[:] = [n for n in numbers if not odd(n)]  # ahh, the beauty of it all
>>> numbers
[0, 2, 4, 6, 8]

Ошибка № 6: непонимание того, как Python связывает переменные в замыканиях


Рассмотрим следующий пример:

>>> def create_multipliers():
...     return [lambda x : i * x for i in range(5)]
>>> for multiplier in create_multipliers():
...     print multiplier(2)
...

Вы можете ожидать следующий вывод:

0
2
4
6
8

Но на самом деле вы получите вот что:

8
8
8
8
8

Сюрприз!

Это происходит из-за поздней привязки в Python, которое заключается в том, что значения переменных, используемых в замыканиях, ищутся во время вызова внутренней функции. Таким образом, в приведенном выше коде всякий раз, когда вызывается какая-либо из возвращаемых функций, значение i ищется в окружающей области видимости во время ее вызова (а к тому времени цикл уже завершился, поэтому i уже был присвоен конечный результат — значение 4).

Решение этой распространенной проблемы с Python будет таким:

>>> def create_multipliers():
...     return [lambda x, i=i : i * x for i in range(5)]
...
>>> for multiplier in create_multipliers():
...     print multiplier(2)
...
0
2
4
6
8

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

Ошибка № 7: создание циклических зависимостей модуля


Допустим, у вас есть два файла, a.py и b.py, каждый из которых импортирует другой, следующим образом:

В a.py:

import b

def f():
    return b.x
	
print f()

В b.py:

import a

x = 1

def g():
    print a.f()

Сначала попробуем импортировать a.py:

>>> import a
1

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

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

Итак, возвращаясь к нашему примеру, когда мы импортировали a.py, у него не было проблем с импортом b.py, поскольку b.py не требует, чтобы что-либо из a.py было определено во время его импорта. Единственная ссылка в b.py на a — это вызов a.f(). Но этот вызов в g() и ничего в a.py или b.py не вызывает g(). Так что все работает прекрасно.

Но что произойдет, если мы попытаемся импортировать b.py (без предварительного импорта a.py, то есть):

>>> import b
Traceback (most recent call last):
  	  File "<stdin>", line 1, in <module>
  	  File "b.py", line 1, in <module>
    import a
  	  File "a.py", line 6, in <module>
	print f()
  	  File "a.py", line 4, in f
	return b.x
AttributeError: 'module' object has no attribute 'x'

Ой-ой. Это не хорошо! Проблема здесь в том, что в процессе импорта b.py он пытается импортировать a.py, который, в свою очередь, вызывает f(), который пытается получить доступ к b.x. Но b.x еще не было определено. Отсюда исключение AttributeError.

По крайней мере, одно из решений этой проблемы довольно тривиально. Просто измените b.py, чтобы импортировать a.py в g():

x = 1

def g():
    import a	# This will be evaluated only when g() is called
    print a.f()

Теперь, когда мы его импортируем, все нормально:

>>> import b
>>> b.g()
1	# Printed a first time since module 'a' calls 'print f()' at the end
1	# Printed a second time, this one is our call to 'g'

Ошибка № 8: пересечение имен с именами модулями стандартной библиотеки Python


Одна из прелестей Python — это множество модулей, которые поставляются «из коробки». Но в результате, если вы сознательно не будете за этим следить, можно столкнуться с тем, что имя вашего модуля может быть с тем же именем, что и модуль в стандартной библиотеке, поставляемой с Python (например, в вашем коде может быть модуль с именем email.py, который будет конфликтовать со модулем стандартной библиотеки с таким же именем).

Это может привести к серьезным проблемам. Например, если какой-нибудь из модулей будет пытаться импортировать версию модуля из стандартной библиотеки Python, а у вас в проекте будет модуль с таким же именем, который и будет по ошибке импортирован вместо модуля из стандартной библиотеки.

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

Ошибка № 9: неспособность учесть различия Python 2 и Python 3


Рассмотрим следующий файл foo.py:

import sys

def bar(i):
    if i == 1:
        raise KeyError(1)
    if i == 2:
        raise ValueError(2)

def bad():
    e = None
    try:
        bar(int(sys.argv[1]))
    except KeyError as e:
        print('key error')
    except ValueError as e:
        print('value error')
    print(e)

bad()

На Python 2 он отработает нормально:

$ python foo.py 1
key error
1
$ python foo.py 2
value error
2

Но теперь давайте посмотрим как он будет работать в Python 3:

$ python3 foo.py 1
key error
Traceback (most recent call last):
  File "foo.py", line 19, in <module>
    bad()
  File "foo.py", line 17, in bad
    print(e)
UnboundLocalError: local variable 'e' referenced before assignment

Что здесь только что произошло? «Проблема» в том, что в Python 3 объект в блоке исключения недоступен за его пределами. (Причина этого заключается в том, что в противном случае объекты в этом блоке будут сохраняться в памяти до тех пор, пока сборщик мусора не запустится и не удалит ссылки на них оттуда).

Один из способов избежать этой проблемы — сохранить ссылку на объект блока исключения за пределами этого блока, чтобы он оставался доступным. Вот версия предыдущего примера, которая использует эту технику, тем самым получая код, который подходит как для Python 2, так и для Python 3:

import sys

def bar(i):
    if i == 1:
        raise KeyError(1)
    if i == 2:
        raise ValueError(2)

def good():
    exception = None
    try:
        bar(int(sys.argv[1]))
    except KeyError as e:
        exception = e
        print('key error')
    except ValueError as e:
        exception = e
        print('value error')
    print(exception)

good()

Запустим его в Python 3:

$ python3 foo.py 1
key error
1
$ python3 foo.py 2
value error
2

Ураааа!

Ошибка № 10: неправильное использование метода __del__


Допустим, у вас есть вот такой файл mod.py:

import foo

class Bar(object):
   	    ...
    def __del__(self):
        foo.cleanup(self.myhandle)

И вы пытаетесь сделать вот такое из другого another_mod.py:

import mod
mybar = mod.Bar()

И получите ужасный AttributeError.

Почему? Потому что, как сообщается здесь, когда интерпретатор отключается, глобальные переменные модуля все имеют значение None. В результате в приведенном выше примере, в момент вызова __del__, имя foo уже было установлено в None.

Решением этой «задачи со звездочкой» будет использование atexit.register(). Таким образом, когда ваша программа завершает выполнение (то есть при нормальном выходе из нее), ваши handle'ы удаляются до того, как интерпретатор звершает работу.

С учетом этого, исправление для приведенного выше кода mod.py может выглядеть примерно так:

import foo
import atexit

def cleanup(handle):
    foo.cleanup(handle)


class Bar(object):
    def __init__(self):
        ...
        atexit.register(cleanup, self.myhandle)

Подобная реализация обеспечивает простой и надежный способ вызова любой необходимой очистки после обычного завершения программы. Очевидно, что решение о том, как поступить с объектом, который связан с имненем self.myhandle, остается за foo.cleanup, но, думаю, идею вы поняли.

Заключение


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

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

Комментарии (76)


  1. rSedoy
    06.09.2019 16:26

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


  1. Drakosh
    06.09.2019 16:45
    -1

    Практически все эти глюки избегаются тестированием и использованием анализаторов типа pylint и flake8.


  1. CheY
    06.09.2019 19:53

    Если честно, то всё это со стороны других языков (может, кроме JS) выглядит как ошибки в проектировании или реализации языка. Ну, то есть, поведение крайне неочевидно. Причём неочевидно в очень простых местах (типа мутабельных значений по умолчанию в аргументах). Если язык в самых базовых моментах требует от тебя понимания своей работы изнутри — что-то с этим языком сильно не так.
    Имхо, конечно. И разумеется это не перечеркивает некоторые плюсы питона.


    1. rSedoy
      06.09.2019 20:05

      Вот уже точно не ошибки в реализации, когда понимаешь как это реализовано, понимаешь почему это так.


    1. khim
      07.09.2019 10:44
      +1

      «Мутабельные значения по умолчанию в аргументах» — это ни разу не простое место и используются они редко.

      А то, что это ошибка в дизайне — это да. Любой язык, которым пользуются люди несовершенен, потому что если вы будете менять язык несовместимым образом каждый раз, когда такая «бяка» будет обнаруживаться — то быстро потеряла всех разработчиков…


      1. Pand5461
        07.09.2019 12:53

        Надо сказать, что Python2 к Python3 всё равно поменялся несовместимым образом, так что возможность "со второго раза сделать правильно" была, но не использована.
        Пример с генератором лямбд — он самый неочевидный для меня. В лямбде, значит, i связывается по ссылке. А в выражении for i in range(5) — по значению?! Почему тогда [[i] for i in range(3)] возвращает [[0], [1], [2]], а не [[2], [2], [2]]?


        1. Pand5461
          07.09.2019 16:23

          А, хотя в лямбде же лексическое связывание, а не по ссылке и не по значению.


          1. Pand5461
            07.09.2019 23:45

            А ещё с одной стороны — вопрос семантики и того, что в питоне тело цикла не создаёт новую область видимости.
            В Julia:


            function create_multipliers_1(n::Integer)
                let m=[]
                    let itr=0:n-1
                        _next = iterate(itr)
                        while _next ? nothing
                            i = _next[1]
                            push!(m, x->i*x)
                            _next = iterate(itr, _next[2])
                        end
                    end
                    m
                end
            end
            
            function create_multipliers_2(n::Integer)
                let m=[]
                    let itr=0:n-1, i=nothing
                        _next = iterate(itr)
                        while _next ? nothing
                            i = _next[1]
                            push!(m, x->i*x)
                            _next = iterate(itr, _next[2])
                        end
                    end
                    m
                end
            end
            
            foreach(m->print(m(2), " "), create_multipliers_1(5)) # 0 2 4 6 8
            foreach(m->print(m(2), " "), create_multipliers_2(5)) # 8 8 8 8 8

            Выражение for i in itr в Julia семантически эквивалентно тому, как развёрнуто в первой функции.


            1. resetme
              08.09.2019 23:40

              А ещё с одной стороны — вопрос семантики и того, что в питоне тело цикла не создаёт новую область видимости.

              Возможно из-за того, что у цикла может быть секция else. Могу ошибаться.


              1. Pand5461
                09.09.2019 01:10

                Тут, можно сказать, есть более глубокая проблема (или не совсем проблема, это кто как видит): в питоне вообще единственный способ создать вложенную область видимости — это написать функцию. Никаких там let, фигурных скобочек или чего-то ещё. Честно — понятия не имею, создаёт это проблемы питонистам или нет. В Julia удобнее, когда каждый блок создаёт локальную область видимости, но больше с точки зрения оптимизации кода компилятором.


              1. Pand5461
                09.09.2019 11:10

                До меня дошло, что не так с лямбдами. Проблема больше в том, что Гвидо давным-давно не упёрся и не сказал "не бывать тут лямбдам". В итоге они есть, но со списковыми включениями неправильно работают.
                Суть: включения позиционируются как замена map. Но [<expr> for x in collection] не соответствует семантически map(lambda x: <expr>, collection), когда <expr> — это лямбда! Включение захватит x лексически, а map — по значению.


        1. resetme
          08.09.2019 23:22

          Потому что в Py3:
          >> type(range(3))
          <class 'range'>


          А при итерировании этот класс возвращает значение.

          Про лямбду сами выше ответили :)


      1. CheY
        07.09.2019 12:59

        «Простое место» не в плане того, как оно работает на самом деле, а как оно преподносится сферическом новичку в вакууме. То есть если взять гипотетический курс «Python для вашего маркетолога», то там, когда дойдут до функций, расскажут про чудесные позиционные аргументы, keyword-аргументы и покажут пример мегаудобной фичи со значениями по умолчанию. И всё выглядит красиво и понятно, но потом тебе говорят, что если поставить дефолтным значением список, то всё будет плохо. А если не список, а множество (круглые скобочки вместо квадратных), то всё будет ок. А потом начинают жесть про мутабельность. А сверху присыпят тем, что передавая что угодно в функцию ты никогда не можешь быть уверен, что это что-то останется без изменений. И это чуть ли не в самом начале освоения языка, который все называют очень простым и понятным, что даже гуманитарии из маркетинга осваивают его.


        1. VovanZ
          07.09.2019 16:56

          А если не список, а множество (круглые скобочки вместо квадратных), то всё будет ок.

          Это не так, множество изменяемо, поэтому будет ровно та же самая проблема.


          1. khim
            07.09.2019 17:23

            Множество, конечно, изменяемо, только вот «круглые скобочки» создают не множество, а «набор» (tuple).

            Набор неизменяем, но это не значит, что с ним не будет проблем — вы же можете включить в набор изменяемые элементы!


            1. VovanZ
              07.09.2019 18:21
              +1

              Вот только в том комментарии написано "множество", а не "кортеж" или "tuple", а слово "набор" в таком контексте я вообще слышу впервые.


              1. CheY
                07.09.2019 19:23

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


        1. khim
          07.09.2019 17:32

          И это чуть ли не в самом начале освоения языка, который все называют очень простым и понятным, что даже гуманитарии из маркетинга осваивают его.
          Мне это тоже всегда казалось странным, но, практика показывает, что освоить функциональные языки, где всех этих проблем нету, для «гуманитариев из маркетинга» сложнее, чем Python.

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

          Для меня, как для человека с математическим образованием, всё это выглядит, мягко скажем, дико… но вот почему-то имеено так устроен этот мир… я просто принял это к сведению, так как понять — почему он именно так устроен, я не в состоянии…


  1. SignallerK
    07.09.2019 06:12

    Эм, я питоном не пользуюсь, но вот если это тело функции то разве поведение кода не очевидно?

    >>> def foo(bar=[]):        # bar - это необязательный аргумент 
                                # и по умолчанию равен пустому списку.
    ...    bar.append("baz")    # эта строка может стать проблемой...
    ...    return bar
    


    Смотрим в гугль: «append» (англ.) — «присоединять» (ну или тут python list).
    Ну вроде логчно, было бы там что-то типа define, set, initialize и тп, тогда да.
    Тут скорее не ошибка, а не желание новичка хотя бы мануал почитать.


    1. SerafimArts
      07.09.2019 06:52
      +1

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


      1. resetme
        07.09.2019 09:07
        -1

        В документации все это описано, значит не ошибка, а проблема грамотности разработчика.


        1. resetme
          07.09.2019 09:13

          Ну вот минусующий показал свой уровень непонимания Python.


          1. CheY
            07.09.2019 13:02

            Всё же от языков программирования в 2019-м году ожидают большую «человечность» и отсутствие необходимости штудировать документацию, чтобы не совершать ошибок в таких базовых вещах. Особенно для языка, который на каждом шагу называется самым простым и понятным в освоении, что его даже детям предлагают в школах изучать.


            1. resetme
              07.09.2019 13:37

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

              Активно минисующие и думающие, что это ошибка, хотят чтобы аргументы функций каждый раз инициализировались при каждом вызове функции. А это лишний оверхед. В Python аргументы функции инициализируются только один раз при первом запуске, все последние запуски происходят с уже инициализированными аргументами. По моему, Гвидо придумал простое и изящное решение!

              Если что-то вы не понимаете в языке, это не значит что язык плох, это значит что вы не уделили достаточного времени для чтения и изучения документации языка.


              1. Siemargl
                07.09.2019 14:09

                Просто в большинстве других языков сделано наоборот.

                Принцип наименьшего удивления никто не отменял.


                1. resetme
                  07.09.2019 18:06
                  -2

                  Просто в большинстве других языков сделано наоборот.

                  Если сделано по другому, это не значит что ошибка.

                  Принцип наименьшего удивления никто не отменял.

                  Это относится к психологии и никак не к языкам программирования.


                  1. Siemargl
                    07.09.2019 18:54
                    +1

                    Это относится к психологии и никак не к языкам программирования.
                    Образованность она такая…
                    The principle of least astonishment (POLA), also called the principle of least surprise (alternatively a «law» or «rule»)[1][2] applies to user interface and software design.[3]

                    [1]The Art of Unix Programming.
                    Линк


                    1. resetme
                      07.09.2019 20:15

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


                      1. khim
                        07.09.2019 22:16

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


                        1. resetme
                          07.09.2019 22:47

                          А я про что писал? Вы вообще читаете посты?


                      1. Siemargl
                        07.09.2019 23:36

                        А как, по твоему переводится и понимается, software design?

                        Не благодари, заработал.


                        1. resetme
                          08.09.2019 00:07

                          У вас какое-то устойчивое недопонимание темы. Процессору по барабану какой software design. Он оперирует кодом, а не языком программирования, хотя и использует интерпретаторы и компиляторы чтобы привести код человека в свой понятный. Человеку нужен software design чтобы не запутаться в хитросплетении вызовов, методов и классов.

                          За что я вас должен благодарить?


              1. Pand5461
                07.09.2019 16:19

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

                И поэтому мы просто будем в каждом учебнике писать "в реальных проектах никогда не ставьте значением по умолчанию изменяемый объект" и рекомендовать закат солнца вручную:


                def foo(bar = None):
                    bar = [] if bar is None else bar
                    ...

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


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


                CL-USER> (defun foo (&optional (bar (make-array 0 :fill-pointer t)))
                           (vector-push-extend 'baz bar)
                           bar)
                FOO
                CL-USER> (foo)
                #(BAZ)
                CL-USER> (foo)
                #(BAZ)


                1. resetme
                  07.09.2019 20:21

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

                  А что языки программирования должны походить друг на друга? И приходить к пониманию даже для человеку ни разу не читающего руководство по языку?


                  1. Pand5461
                    07.09.2019 22:58

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


                    1. khim
                      07.09.2019 23:08

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


                    1. resetme
                      08.09.2019 00:17

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

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

                      Дизайн языка это компромисс в многих местах. Еще почему сделали единичную инициализацию аргументов по умолчанию, это из-за такого мощного свойства языка Python как развертываний args и kwargs в аргументах функций. Если бы при каждом вызове аргументы инициализировались, то про развертывание аргументов можно было бы забыть.


                      1. CheY
                        08.09.2019 18:20

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


                        1. resetme
                          08.09.2019 19:29

                          Возможно вы имеете дело с языками для обучения или узкоспециализированными. Возьмите язык широкого применения такой как Rust или Go и попробуйте на нем что-то внятное написать. Я думаю, сразу измените свое мнение.

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

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


                          1. khim
                            08.09.2019 20:16

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

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

                            Того факта, что огромное количество компаний (Facebook, Google, Microsoft и прочие монстры… о мелких стартапах я уж и не говорю) этого не требут — достаточен для того, чтобы сказать подо что подстраивается «современный мир разработки».


                            1. resetme
                              08.09.2019 20:46

                              А вам не кажется что уже этот факт является достаточным для того, чтобы сказать что «современный мир разработки именно что подстраивается под этих людей» — иначе как бы они смогли работать хотя бы джунами или, тем более, дорасти до миддлов?

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

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

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


                              1. khim
                                08.09.2019 20:59

                                Вау! Сколько пафоса-то. Я вам всё-таки советую задуматься над тем, почему вы — «не такие богатые как Google и Microsoft» и как это связана с тем, что вы идёте поперёк того, что делает индустрия.

                                Google и Microsoft, как бы, не всегда такими богатыми были, напоминаю. И если они смогли заработать достаточно денег для того, чтобы «тратить деньги на проходимцев» — а у вас их до сих пор недостаточно, то, может быть, проблема не в «проходимцах».


                              1. Siemargl
                                08.09.2019 21:52

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


                          1. Siemargl
                            08.09.2019 21:54

                            Возьмите язык широкого применения такой как Rust или Go
                            А что ты знаешь про Го? Он создавался как язык с низким порогом вхождения. И на этом преуспел (в т.ч.)


                          1. 0xd34df00d
                            08.09.2019 23:35

                            Возможно вы имеете дело с языками для обучения или узкоспециализированными. Возьмите язык широкого применения такой как Rust или Go и попробуйте на нем что-то внятное написать. Я думаю, сразу измените свое мнение.

                            Я брал языки широкого применения от хаскеля до идриса (до того мой основной язык — C++), а сейчас вот ковыряю Coq (уж где инопланетность-то, особенно после языков типа агды или того же идриса). Так вот, таких сюрпризов, как в питоне, нет.


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


                            1. khim
                              09.09.2019 12:00

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

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

                              И для вот такого подхода — Python неплохо подходит. Ну а те, кому хочется или нужно таки понимать — могут разобраться с реализацией… И у них тоже будет всё хорошо.

                              По той же причине «взлетел» Git, кстати. Им тоже невозможно пользоваться, не имея некоторого представления о том, как он устроен «внутри» (хотя там есть забавное читерство: есть некоторое «правдоподобное объяснение» о том, как Git устроен внутри и без него ничего понять невозможно, но оно уже давным-давно не описывает «реальный» Git… точное же описание того, что у Git'а внутри бывает нужно очень-очень редко).


                      1. Pand5461
                        08.09.2019 20:19

                        Можно пояснить, что именно вы понимаете под "развёртыванием аргументов", и почему это конфликтует с инициализацией аргументов при вызове?


                        А то я как бы с Julia прихожу посмотреть, там есть и splat, т.е. f(x, rest...) принимает произвольное число аргументов, и инициализация аргументов по умолчанию при каждом вызове. Однократная инициализация, кстати, тоже ограничивает — выражение для значения по умолчанию не может использовать значения предыдущих аргументов, вроде f(x, y=sin(x)) = x + y. Точнее, может, но только если на момент объявления функции x будет в глобальной области видимости, и результат будет немного не тот...


                        Но, конечно, настоящий разработчик™ знает весь стандарт назубок и таких ошибок допускать не будет, поэтому всё нормально.


                        1. resetme
                          08.09.2019 22:56

                          Если бы инициализоровались аргументы при каждом вызове, то должна бы работать такая конструкция:

                          def test(*args, foo=[1]):
                              print(args, foo)
                          

                          Т.е. сколько аргументов не передавай, foo всегда будет проинициализированна, а это тоже самое что вот такой код:
                          def test(*args):
                              foo=[1]
                              print(args, foo)
                          

                          Т.е. какая-то не нужная бессмыслица. Поэтому сейчас выдается SyntaxError.


                          1. Pand5461
                            08.09.2019 23:45

                            Ну я не знаю, Julia c этим как-то живёт, без ошибок синтаксиса и без эквивалентности первой и второй функции:


                            julia> test(args...; foo=[1]) = println(args, " ", foo)
                            test (generic function with 1 method)
                            
                            julia> test()
                            () [1]
                            
                            julia> test(42)
                            (42,) [1]
                            
                            julia> test(42, foo=42)
                            (42,) 42

                            Но там позиционные аргументы строго отделены от именованных (; в списке параметров). Но и в питоне можно понимать первую запись как то, что все аргументы после *args могут быть только именованными — вполне логичная семантика, как по мне.
                            Но фиг с ним, собственно, нельзя так нельзя. Почему нельзя для значения по умолчанию проверить тип и точно так же сказать "нельзя", если он оказался мутабельным? Ведь даже в документации признаётся, что это, скорее всего, не то, что программист хотел сказать. Чтобы, если уж хочется стрелять себе в ногу — это нужно было явно задекларировать:


                            _default_bar_in_foo = []
                            
                            def foo(bar=None):
                                bar = _default_bar_in_foo if bar is None else bar
                                bar.append("baz")
                                return bar

                            И, как я писал, — конечно, сейчас уже поздно это менять. Но Python3 всё равно ломал обратную совместимость, чёрт подери! Можно было тогда и сделать.


        1. Vadem
          07.09.2019 20:43

          В документации все это описано, значит не ошибка
          От создателей «это не баг, это фича».


      1. qellex
        08.09.2019 08:28

        Не минусуйте пожалуйста, я просто не очень хорошо разбираюсь.
        На сколько я понимаю bar=[] создается во время импорта модуля с этой функцией и соотвественном выделение памяти под bar происходит 1 раз, нужно ли для «условной очевидности» выделять память каждый раз при вызове foo с дефолтными аргументами?


        1. Pand5461
          08.09.2019 12:20

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


        1. resetme
          08.09.2019 19:49

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

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

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


          1. khim
            08.09.2019 20:54

            При постоянной инициализации аргументов по умолчанию изменить поведение было бы проблематично
            Почему?

            Пришлось бы тогда при каждом вызове вызывать декоратор,
            Зачем?

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

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

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

            Почему LISP полвека назад или C++ четверть века назад это делали ожидаемым и естественным способом, а Python — не может?

            Ну Ok, совместимость, к этому уже все привыкли… могу понять — достаточно веская причина.

            Но вот рассказы про декораторы и прочее — оставьте для людей, не умеющих в программирование. Люди, понимающие как это всё устроено как раз легко могут вам предложить несколько вариантов, которые могли бы проблему решить. Вопрос тут не во внутреннем устройстве а как раз исключительно «в человеческом факторе»: неясно — стоит ли исправление этой проблемы на третьем десятке жизни языка возможного хаоса или нет. Всё. Никакиз других веских причин для этого нет.


            1. resetme
              08.09.2019 23:04

              Почему?

              Потому что декоратор изменяет функцию один раз.
              Зачем?

              Чтобы проинициализировать аргументы.

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

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


              1. khim
                09.09.2019 12:30

                Вы прочитайте про передачу аргументов через * или **, возможно тогда поймете почему так, а не иначе.
                Какое имеет отношение передача аргументов через * и ** к их инициализации?

                Странное желание видеть все языки одинаковыми.
                Не одинаковыми. Логичными. То что одинаково выглядящие выражения «a = []», записанные в заголовке функции и в её теле работают по разному — ну вот ни разу не логично. И некрасиво. Да, можно пытаться это объяснять соображениями эффективности… (хотя «эффективность» и «питон» в одном предложении уже «делают мне смешно»), но вот никаких неразрешимых проблем с передачей параметров или декораторами отказ от этой «фичи» не вызывает.

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

                Да, действительно, Python так устроен, что он вначале вычисляет все аргументы, а потом только передаёт этот список в декораторы и делает другие манипуляции. Ок, пусть будет так. Однако… метод «откладывания» вычислений всем, как бы, хорошо известен: лямбда. Храните в этом списке не готовые аргументы, а лямбды, которые эти аргументы вычислят в нужный момент — и всё. Вот совсем всё: декораторы вам больше не нужно вызывать 100500 раз, и никаких трюков с * или ** вам тоже не нужно. Эффективности не хватает? Ну, введите «псевдолямбду» в реализации, одна проверка одного бита для языка, который и так, при вызове функций, выполняет сотни машинных инструкций — это немного.

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

                На крайний случай хотя бы честно напишите в документации «по историческим причинам есть такая бяка» (собственно сейчас так и сделано).

                Только не нужно обосновывать свои недоработки «глубинными материями» — глупо выглядит.


            1. Pand5461
              09.09.2019 22:12

              В Лиспе тоже есть, конечно, некоторые заморочки:


              CL-USER> (defun foo (&optional (bar '(spam)))
                         (nconc bar '(spam))
                         bar)
              ; WARNING:
              ;   Destructive function NCONC called on constant data: (SPAM)
              ;   See also:
              ;     The ANSI Standard, Special Operator QUOTE
              ;     The ANSI Standard, Section 3.2.2.3
              ; 
              ; compilation unit finished
              ;   caught 1 WARNING condition
              FOO
              CL-USER> (foo)
              (SPAM SPAM)
              CL-USER> (foo)
              ; бесконечный цикл

              Но SBCL вот сразу ругается и отправляет читать стандарт, согласно которому литералы в аргументах по умолчанию привязываются по ссылке, а не вычисляются заново во время вызова.
              Правильно будет везде вместо литералов писать явный вызов функции:


              CL-USER> (defun oof (&optional (bar (list 'spam)))
                         (nconc bar (list 'spam))
                         bar)
              OOF
              CL-USER> (oof)
              (SPAM SPAM)
              CL-USER> (oof)
              (SPAM SPAM)

              Если оставить вызов в аргументе функции, но оставить литерал в теле, то тоже будут весёлые побочные эффекты:


              CL-USER> (defun ofo (&optional (bar (list 'spam)))
                         (nconc bar '(spam))
                         bar)
              OOF
              CL-USER> (ofo)
              (SPAM SPAM)
              CL-USER> (ofo)
              (SPAM SPAM) ; всё хорошо? как бы не так!
              CL-USER> (let ((spam (ofo)))
                         (setf (nth 1 spam) 'ham)
                         spam)
              (SPAM HAM)
              CL-USER> (ofo)
              (SPAM HAM)

              Ещё, говорят, реализация вольна внутри одного определения функции два одинаковых литерала по одной ссылке связывать, но в SBCL я такого не видел.


          1. Siemargl
            08.09.2019 21:56

            Тут, кстати Раст упоминался…

            Советую ознакомиться, что он делает с аргументами функции.

            Ну и как бы оценить, типичный подход, подход в Питоне и подход в Расте.


            1. resetme
              08.09.2019 23:11

              Да за что я должен вас благодарить? По моему, вы своим хамским поведением поставили себя вне дискуссии.


  1. JTG
    07.09.2019 14:43
    -2

    Топ 10 ошибок, упоминается Python 2 — и ни слова про UnicodeDecodeError? Ну-ну :)


  1. Pand5461
    07.09.2019 16:28

    Решение этой распространенной проблемы с Python будет таким:
    def create_multipliers():
    return [lambda x, i=i : i * x for i in range(5)]

    Фи, второй параметр добавлять. Правильное решение — это


    def create_multipliers():
         return [(lambda k: lambda x: k * x)(i) for i in range(5)]


  1. sobolevn
    07.09.2019 19:39

    Хороший линтер поймает все ошибки из статьи.


    1. Siemargl
      07.09.2019 23:37

      Хорошие костыли ног не заменяют


      1. ardraeiss
        10.09.2019 10:21

        С чего это линтер костылём называть? Каждый человек устаёт, отвлекается, ошибается и ошибки пропускает и это неотключаемый биологический факт.
        И то, что в данный конкретный момент каждый конкретный ты-я-они-все помнят про вот такое и вот эдакое поведение — вовсе не значит, что оно будет помнится и завтра, когда случатся магнитная буря, телефонный спам от банков, пробки по дороге и перемена настроения маркетинга «теперь нам нужно вот так».


  1. NotThatEasy
    08.09.2019 10:39

    >>> def foo(bar=None):
    … if bar is None: # or if not bar:
    … bar = []
    … bar.append(«baz»)
    … return bar


    Как новичку в Питоне, этот пример был наименее понятным, более того, не смог врубиться даже переспав с этой мыслью.
    При первом вхождении должно быть всё отлично, однако же, было бы великолепно, если бы кто-нибудь из присутствующих смог мне пояснить, почему при последующих вхождениях в функцию выходит, что переменная bar снова становится None?
    Ведь будучи пустым массивом, переменная хранила своё состояние при выходе из функции, а будучи None, превращённым в массив, вдруг стала его терять.


    1. Pand5461
      08.09.2019 11:25

      Потому что в Питоне происходит не присваивание, а связывание, а типы данных бывают изменяемые и неизменяемые. "Значением" изменяемого типа является ссылка на данные, а неизменяемого — сами данные.
      Расшифровка определения функции:


      • При создании: Определить функцию foo от одного аргумента bar; вычислить выражение справа от bar= (вычисляется в None) и, если при вызове bar не дан явным аргументом, связать bar с получившимся объектом.
      • При вызове: если bar связано с объектом None, создать пустой список и связать bar с этим списком.

      Кстати, # or if not bar — неверный комментарий, т.к. not [] вычисляется в True — т.е. если вместо if bar is None будет if not bar — то при вызове foo() на пустом списке "baz" добавится в новый список, а не в уже имеющийся.


      1. NotThatEasy
        08.09.2019 11:47

        Премного благодарю за расшифровку, а также напоминание о различиях типов данных в Питоне!


      1. resetme
        08.09.2019 20:07
        -1

        Потому что в Питоне происходит не присваивание, а связывание, а типы данных бывают изменяемые и неизменяемые.

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


        1. khim
          08.09.2019 20:24

          На собеседованиях на сеньора это спрашивают, чтобы понять владеет ли собеседуемый терминологией Python.
          А не подскажите — на собеседованиях в какую именно компанию такие вещи спрашивать принято?

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


          1. resetme
            08.09.2019 23:30

            Судя по тому в каких хабах вы состоите на Хабре, вас не особо интересует разработка и языки программирование. Не льстите себе, вряд ли вас возьмут в девелоперскую компанию. Будьте хотя бы честным с самим собой!


            1. 0xd34df00d
              08.09.2019 23:44

              Вот никогда не могу пройти мимо диагностики по юзерпику, так что можно мне тоже диагноз, пожалуйста?


            1. khim
              09.09.2019 12:40

              Смешно. Если твой код вот прямо сейчас инсталлирован на миллиард или два компьютеров, но вы не подписаны на «правильные» хабы на Хабре и у вас нет профиля на HackerRank — то вы не разработчик?

              Ну тогда понятно почему от вас, по вашим же словам, через два-три года люди уходят ибо вы не можете обеспечить им достойную зарплату… продолжайте в том же духе!


  1. boojum
    09.09.2019 10:17

    >>> numbers[:] = [n for n in numbers if not odd(n)]  # ahh, the beauty of it all


    Зачем здесь срез [:]. Почему бы в данном примере не сделать просто numbers =?


    1. BasicWolf
      09.09.2019 14:13

      Потому что здесь происходит замена данных, а не присваивание (связывание). Рассмотрим пример с обычным присваиванием:


      numbers = [1, 2, 3]
      other_number = numbers
      
      numbers = [4, 5, 6]
      # В данной ситуации, значение `other_numbers` не меняется, т.к. эта переменная продолжает ссылаться на область в памяти, к которой она была привязана изначально
      print(other_numbers)
      >>> [1, 2, 3]

      Теперь с [:] = [...]:


      numbers = [1, 2, 3]
      other_number = numbers
      
      numbers[:] = [4, 5, 6]
      # Значение `other_numbers` меняется, т.к. обе переменные ссылаются на одну и ту же область памяти 
      print(other_numbers)
      >>> [4, 5, 6]


      1. boojum
        09.09.2019 14:22

        Это понятно. Но ведь в исходном примере нет other_numbers и его результат нисколько не изменится если убрать "[:]". Поэтому и вопрос был: зачем срез в данном конкретном примере?


    1. rSedoy
      09.09.2019 14:16
      +1

      возможно чтобы показать именно изменение списка, а не просто assignment с новым списком.