Комментарии 69
То есть когда мы объявляем список lst=[], он создаётся один раз и при каждом вызове функции он обращается к нему, а если мы будем создавать его внутри функции, то он будет каждый раз его создавать? Хотелось бы понять его видимость)
def get_lst(a):
lst = []
lst.append(a)
return lst
get_lst(1)
[1]
get_lst(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]
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
Следовало бы добавить первопричину такого поведения: Питон неотложно исполняет инструкции, соответственно, когда он натыкается на
def f(val = []):
print(id(val))
то создаётся объект f
типа function
и записываются вычисленные значения аргументов в аттрибуты f.__defaults__
и f.__kwdefaults__
.
Это легко проверить:
>>> f()
140694137408008
>>> print(id(f.__defaults__[0]))
140694137408008
> 3. Обновление списков или словарей
> 4. Интернированные (Interned) строки
Это же объясняется у Лутца сразу же в первой паре сотен страниц. Поэтому первая ошибка — не читать учебник, а думать, что по статьям в интернете всему научишься.
Тут не нужна статья, чтобы догадаться, что процедура вставки элемента в список новый список не вернет, почему она вообще должна это сделать?
И списки можно копировать через срезы:
b = a[:]
Маленькие строки и числа кэшируются. is проверяет на указание на одну область памяти, это и есть эквивалентность, а не то, что вы указали под этим словом.
Присваивание лишнее. С update у dict тоже самое.
>>> 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])
Бывает, что на автомате пишешь и забываешь про это, а потом ищешь, где же ты потерял значения.
Стандартные библиотеки Python обычно построены с учётом принципа CQS. Python-программисты обычно привыкают к нему. Это позволяет создавать простые и интуитивно понятные API.
NumPy нарушает этот принцип без видимых на то причин, он непитоничен, поэтому им трудно пользоваться.
А зачем давать ссылку на английском, если есть на русском?
Интересно, а хоть один из минусующих нормально ответить на мой вопрос сможет, или все, на что они способны, — по тихому анонимно сливать мой комментарий по делу? Если вы отвечаете за себя, конечно.
Размер массивов numpy, можно сказать, неизменяемый. Это не динамический контейнер как список в Python. Поэтому функция np.append возвращает новый массив (так же как любые другие функции или методы, завязанные на размерности массива). Да, вы можете сделать resize для существующего массива, но это будет перераспределение памяти под массив, и только если на массив нет других ссылок.
numpy вполне себе питоничен и им легко пользоваться когда вы работаете с матрицами или многомерными массивами и выполняете над ними векторизованные операции.
NumPy вообще-то непитоничен и в более простых вещах, например, не придерживается PEP-8 в именованиях методов и параметров, и насыщен сокращениями вплоть до полной нечитаемости. Откройте справочник и убедитесь.
Я не уверен, зачем это так сделано, но полагаю, что это должно было как-то помочь пользователям MatLab или Fortran перейти на Python. Если можете, просветите меня в этом вопросе. Но говорить, что NumPy питоничен − это просто отрицать очевидное.
не придерживается 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-фишек сверху.
А то, что функции NumPy игнорируют возможности Python по обращению с аргументами и требуют всё приводить к единственному аргументу-последовательности вместо args − это тоже питонично? Вот это, например, как следствие: github.com/numpy/numpy/issues/6555?
Я соглашусь, что в 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 и т. д.
Смысл придираться к подобным сокращениям, если в рамках предметной области ясно о чём идёт речь.
То, что NumPy не просто чужероден, а выглядит как жертва обфускации, я могу объяснить только тем, что он, возможно, скопирован с Fortran.
logging и unittest монстроузны и избыточны именно потому что просто скопированы из Java. :)
Сравните их с пакетами loguru и pytest. При том, что pytest не только более простой и изящный в использовании (и плевать, что там под капотом дикая черная магия), но ещё и более мощный с точки зрения функциональности и рсширяемости.
На ваш взгляд, насколько готовая к проду библиотека loguru?
Я использую 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.
И ещё на счёт 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
:)
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}
Гвидо не любит цепочки вызовов: I find the chaining form a threat to readability.
В свое время больно обжегся на таком:
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] ]
С многомерными массивами, конечно, лучше поаккуратнее быть, стандартные массивы здесь могут быть не всегда удобными.
Такая проблем может возникнуть и людей пришедших из древнючих языков (вроде Бейсика или Перла), где присваивание идет по значению.
Имхо это легаси Перла и косяк дизайна Питона. Булевы значения никакого отношения к целочисленным значениям не имеют, и то что True == 1 — это нифига не очевидная особенность языка. Впрочем все равно лучше чем у прародителя — true/false вообще отсутствуют, а в качестве false используется 0, '' или undef, а в качестве true — все остальное.
Лишь вопрос API. Например такой подход к построению API не позволяет делать чейнинг операций:
a = [] a.append('A').append('B').append('C') #=> ['A', 'B', 'C']
А есть еще и языки где строчки вообще иммутабельные.
Скорее косяк Питона — тут ничего удивительного что у людей возникают вопросы.
Например в Ruby:
def foo(a = []) a << "world" end foo() => ["world"] foo() => ["world"]
JavaScript:
> function foo(bar = []) { bar.push("world"); return bar; } > foo() ["world"] > foo() ["world"]
Насчет 4 — Строки и в python считаются иммутабельным типом.
В остальном же, ИМХО, приведенные ответы на пункты из статьи являются всего лишь субъективными (абсолютно валидными при этом) дополнениями, так что не вижу необходимости комментировать.
древнючих языков (вроде Бейсика или Перла)
К сведению: разработка перла началась в 1987-м, а питона в 1989-м
< del >
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 в питоне глобальный, если переменная только на чтение, и локальный если на запись… Те, кому это не было очевидно с первого же взгляда, не забывайте отмечаться в комментариях :)
Ещё примеры подобных языков?
Про 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
явным образом задающие контекст переменной.
Обратите внимание, я не критиковал сам инструмент, зачем мне на молоток ругаться. Я критикую его применение, т.к. насаживать гайки на резьбовые шпильки молотком не очень хорошо. Если ближе к делу, то в той стране, где я сейчас живу, питон в обязательном порядке преподаётся в школах, но про подобные неочевидные нюансы, разумеется, умалчивают. А потом бывшие школьники пытаются использовать тот инструмент, который, как им кажется, они освоили, и тратят феноменальное время на преодоление неочевидных косяков. Нехорошо.
Ну так по вашей логике, человек пришедший скажем из Питона в C, будет также недоумевать, а с чего это
#include "stdio.h"
int main() {
for (int i = 0; i < 10; i++);
printf("%d", i);
return 0;
}
не компилируется?
Я привёл вам пример, в котором видимость переменной после цикла или доступ к глобальным переменным — вполне логичны. Всё зависит от точки зрения. У каждого языка есть свои нюансы и хорошее владение языком подразумевает знание этих нюансов.
А учителям и ученикам можно порекомендовать соответствующий раздел туториала :)
У меня вообще складывается мнение, что на программирование на питоне нужно выдавать корочки по типу водительского удостоверения только после сдачи экзаменов по всем подобным тонким моментам.
А для программирования на плюсах корочки по устройству памяти, неявным преобразованиям всего подряд в целочисленный тип и по UB :)
100 лет не держал в руках VB, но там вроде то же самое было. VB6, VBA.
Иногда было удобно.
WTF?! Почему я могу достучаться до переменной i вне блока, её объявившего?
Ну потому что цикл не создает никакого «блока», где объявляются переменны (в отличие от def
или class
).
for i in range(10):
a = 2
print(a)
А вот если мы возьмём не цикл, а 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
. :)
Язык, который «плюётся ошибкой» на правильный, но делающий не то что вам нужно код — пока что недостижимый уровнь.
Просто все функции, которые ничего не возвращают, возвращают None. :)
Если у вас функция возвращает, например: typing.Optional[str]
(None или str), то на вас лежит ответственность проверять результат на None. Если функция просто ничего не возвращает, то, естественно, не надо проверять её результат и удивляться, что там None, а не ошибка.
>>> dict_a[True] = "mango"
Неужели это — распространенная ошибка?
Могу только надеяться, что она может быть распространена в стране, которая начинается на «И» и заканчивается на «я».
5 распространенных ошибок начинающих программистов на Python