Комментарии 53
Не соглашусь насчёт True и False: в Python последних версий это, всё-таки, отдельный булев тип:
Внутренняя кухня для булевых типов довольно нетривильна (например, тип bool является подклассом int, для объектов существует понятие truth value, чьё поведение можно переопределить через методы __nonzero__/__bool__ и т.д.), но для простоты можно считать, что любой тип может быть сконвертирован в bool, а bool, в свою очередь, в арифметических операциях может быть сконвертирован в int (конкретно, в 1 или 0).
А вообще, спасибо за отличное выступление на митапе — многих вещей действительно не знал :)
>>> type(True)
<type 'bool'>
>>> type(1)
<type 'int'>
>>> 1 is 1
True
>>> True is 1
False
Внутренняя кухня для булевых типов довольно нетривильна (например, тип bool является подклассом int, для объектов существует понятие truth value, чьё поведение можно переопределить через методы __nonzero__/__bool__ и т.д.), но для простоты можно считать, что любой тип может быть сконвертирован в bool, а bool, в свою очередь, в арифметических операциях может быть сконвертирован в int (конкретно, в 1 или 0).
А вообще, спасибо за отличное выступление на митапе — многих вещей действительно не знал :)
Рад что понравилось,
Спасибо.
Дополнил пункт 8 (True и False) ссылкой на первоисточник и кусочком кода
Спасибо.
Дополнил пункт 8 (True и False) ссылкой на первоисточник и кусочком кода
Насчёт True и False. Обычно ожидаешь, что
Проблема чисто методологическая, думаю сравнивать между собой переменные разных типов (int и bool, например) – не лучшая идея. Хотя в С уже давно придумали, как правильно приводить переменные к набору (0, 1), но на Python это будет слишком длинно. Вместо
Это актуально для Python 2.7.
True
будет соответствовать всему, что не является False
(0, [], '' и т.д.). Однако если 1 == True
даёт True
, то 5 == True
вернёт False
. Есть тут неконсистентность. Проблема чисто методологическая, думаю сравнивать между собой переменные разных типов (int и bool, например) – не лучшая идея. Хотя в С уже давно придумали, как правильно приводить переменные к набору (0, 1), но на Python это будет слишком длинно. Вместо
!!var
нужно писать not not var
.Это актуально для Python 2.7.
1. Не знал, спасибо.
10. Проблема существует, потому что стандартная библиотека плохо документирована по части исключений, которые могут возникать в той или иной функции, не говоря о сторонних библиотеках.
11. Не вижу в этом большой проблемы.
PyCharm, как минимум, 3, 4, 6, 7, 10 и 11 выявляет по умолчанию из коробки.
10. Проблема существует, потому что стандартная библиотека плохо документирована по части исключений, которые могут возникать в той или иной функции, не говоря о сторонних библиотеках.
11. Не вижу в этом большой проблемы.
PyCharm, как минимум, 3, 4, 6, 7, 10 и 11 выявляет по умолчанию из коробки.
по поводу 11 приведу пример:
отредактировал кто-то модуль, и ввёл там на уровне модуля функцию bytearray не зная что такая есть в docs.python.org/2/library/functions.html#built-in-functions (при этом в модуле нет __all__), а где-то у кого-то возьми да и используется (ну осталось так в коде с давних пор, ничего не поделать)
from module import *
и случится магия, если вдруг у этого кого-то в его модуле и используется родная bytearray из built-in, которая перезатрётся новой реализацией.
Хорошо если баг всплывёт сразу…
Неочевидного поведения тут нет, а отхватить проблему, которая непойми где и как вылезет можно.
отредактировал кто-то модуль, и ввёл там на уровне модуля функцию bytearray не зная что такая есть в docs.python.org/2/library/functions.html#built-in-functions (при этом в модуле нет __all__), а где-то у кого-то возьми да и используется (ну осталось так в коде с давних пор, ничего не поделать)
from module import *
и случится магия, если вдруг у этого кого-то в его модуле и используется родная bytearray из built-in, которая перезатрётся новой реализацией.
Хорошо если баг всплывёт сразу…
Неочевидного поведения тут нет, а отхватить проблему, которая непойми где и как вылезет можно.
Там в примере 11 большая разница между list и id / type: функция list создает проблемы пользователю библиотеки (т.к. она перекрывает list в области видимости модуля), а параметры id/type — нет, т.к. они перекрывают id/type только в области видимости функции (эта область видимости пользователю явным образом не доступна). Пользователю нужно называть аргументы функции id/type при вызове, но это не ведет ни к какому перекрытию, т.к. это, по сути, просто создание словаря с ключами 'id' и 'type'.
Такие названия аргументов могут создать проблемы разработчику функции, но это менее серьезная проблема, т.к. id и type используются очень редко (и часто неправильно, вместо is и isinstance), да и ошибку в короткой функции заметить проще. Но это нужно иметь в виду, да.
Схожая проблема:
Тут builtin «id» перекрывается, но только на время, пока создается класс (выполняется его тело); после создания класса (например, после импорта) перекрытия уже нет: id теперь доступен как атрибут Foo, т.е. опять это, по сути, ключ в словаре, который в пользовательском коде «намусорить» не может (ну разве что пользователь locals/globals обновит сам). Строка «return id(x)» во время создания класса не выполняется, так что функция Foo.foo вызывает builtin.
Такие названия аргументов могут создать проблемы разработчику функции, но это менее серьезная проблема, т.к. id и type используются очень редко (и часто неправильно, вместо is и isinstance), да и ошибку в короткой функции заметить проще. Но это нужно иметь в виду, да.
Схожая проблема:
class Foo(object):
id = 'foo'
id_upper = id.upper()
def foo(self, x):
return id(x)
Тут builtin «id» перекрывается, но только на время, пока создается класс (выполняется его тело); после создания класса (например, после импорта) перекрытия уже нет: id теперь доступен как атрибут Foo, т.е. опять это, по сути, ключ в словаре, который в пользовательском коде «намусорить» не может (ну разве что пользователь locals/globals обновит сам). Строка «return id(x)» во время создания класса не выполняется, так что функция Foo.foo вызывает builtin.
3 нехорошо
Вообще 3 и 4 идет из одного корня — инициализация значений по умолчанию при импорте функции
print get_data(0) # [1]
# лучше в get_data
val = val if not val is None else []
Вообще 3 и 4 идет из одного корня — инициализация значений по умолчанию при импорте функции
Соглашусь, На случай если ожидается на входе пустая строка или там 0, то лучше так и писать как вы предлагаете, В python google-styleguide имеено проверка на is None.
google-styleguide.googlecode.com/svn/trunk/pyguide.html?showone=Default_Argument_Values#Default_Argument_Values
Добавлю в статью.
google-styleguide.googlecode.com/svn/trunk/pyguide.html?showone=Default_Argument_Values#Default_Argument_Values
Добавлю в статью.
Python поддерживает и более «естественный» синтакс:
val is not None
вместо val not is None
Спасибо большое за обзор, добавлю в список документов, которые стоит перечитать перед собеседованием:)
О некоторых вещах знал, о некоторых — нет, но настоящий ужас вызвал лишь 6-й пункт. Бегло загуглив, не нашел объяснения, не могли бы вы подробнее рассказать об этой штуке, чем это обосновано и т.п.?
О некоторых вещах знал, о некоторых — нет, но настоящий ужас вызвал лишь 6-й пункт. Бегло загуглив, не нашел объяснения, не могли бы вы подробнее рассказать об этой штуке, чем это обосновано и т.п.?
Ээээ… 6 пункт реально прикольный. Вот так ищешь, ищешь баг в коде… А он вот таким оказывается. В чём проблема?
Да какой там ужас, просто в нормальном коде сравнение двух чисел по
А так — объяснение достаточно простое. В случае чисел, например, с точки зрения интерпретатора Python, экземпляры даже примитивных типов вроде int «забоксены» как экземпляр PyObject. Поэтому в какой-то момент некоторый набор чисел как бы "заинтернили", чтобы, если такое число используется в программе, сэкономить на памяти под него. В некоторых случаях такой intern пытается произойти автоматически и аналогично сэкономить память.
И, опять про int-ы — как полезный сайд-эффект (и благодаря упомянутому факту, что True и False — это числа), это ещё даёт возможность сравнивать с True и False по
is
и так встречаться не должно…А так — объяснение достаточно простое. В случае чисел, например, с точки зрения интерпретатора Python, экземпляры даже примитивных типов вроде int «забоксены» как экземпляр PyObject. Поэтому в какой-то момент некоторый набор чисел как бы "заинтернили", чтобы, если такое число используется в программе, сэкономить на памяти под него. В некоторых случаях такой intern пытается произойти автоматически и аналогично сэкономить память.
И, опять про int-ы — как полезный сайд-эффект (и благодаря упомянутому факту, что True и False — это числа), это ещё даёт возможность сравнивать с True и False по
is
:)НЛО прилетело и опубликовало эту надпись здесь
С чего бы это? Используйте is для того, для чего он предназначен — сравнения object identity объектов (т.е., ссылаются ли две ссылки на один и тот же объект). Чаще всего это действительно нужно для None, но бывает полезно и во многих других случаях.
Просто надо понимать, что для immutable типов (к каковым относится и int, и str) этот оператор бессмысленен по определнию.
Просто надо понимать, что для immutable типов (к каковым относится и int, и str) этот оператор бессмысленен по определнию.
Если быть точным, то интерпретатор CPython предсоздаёт объекты для чисел от -5 до 256 включительно. Вот небольшой кусок Objects/longobject.c исходников Python 3.2.5:
Это лишь всего одна небольшая оптимизация конкретной реализации языка Python. Например, PyPy поступает по-другому.
Кстати, раз объекты предсоздаются, мы можем использовать модуль ctypes чтобы поменять значения в них.
Для третьего питона:
Для второго:
#define NSMALLPOSINTS 257
...
#define NSMALLNEGINTS 5
...
/* Small integers are preallocated in this array so that they
can be shared.
The integers that are preallocated are those in the range
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
Это лишь всего одна небольшая оптимизация конкретной реализации языка Python. Например, PyPy поступает по-другому.
Кстати, раз объекты предсоздаются, мы можем использовать модуль ctypes чтобы поменять значения в них.
Для третьего питона:
>>> import ctypes
>>> ctypes.c_long.from_address(id(7)+24).value=140
>>> print(7+7)
280
Для второго:
>>> import ctypes
>>> ctypes.c_int.from_address(id(7)+16).value=77
>>> print 7 + 7
154
А как вам это сравнение яблок с апельсинами?
В Python 3 поправлено:
>>> None < 1
True
В Python 3 поправлено:
>>> None < 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: NoneType() < int()
10 еще чревато тем, что в Python SyntaxError — обычное исключение.
Пункт 2 специфичен только для второго питона, в третьем отказались от классического стиля наследования классов. Т.е. в третьем питоне можно не наследоваться явно от object
НЛО прилетело и опубликовало эту надпись здесь
А вот такое поведение для большинства языков действительно неочевидно:
first, *rest, last = '0123'
print(first, rest, last)
Интересно, только в 3ем питоне
Вполне логичная распаковка. Это как
/^(.)(.*)(.)$/
Строка это iterable, конечно она распаковывается, чтож тут неочевидного. Это не шибко тянет на паттерн-матчинг, потому что вторую звёздочку мы уже не поставим, да и нельзя даже указывать макс. число символов в группу.
Конечно Python в этом нет ничего неочевидного, я имел ввиду сравнение с другими языками:
std::string first, last = "01";
std::cout << first << ',' << last << std::endl; // , 01
Ну так в С++ распаковки нет вообще. Синтаксис-то разный.
Распаковка в С++ есть, но она используется иначе (variadic templates).
Хорошое сравнение можно сделать с ES6:
Хотя так нельзя писать:
Хорошое сравнение можно сделать с ES6:
let data => [1, 2, 3, 4, 5];
let [first, ...rest, last] = data();
Хотя так нельзя писать:
let first, ...rest, last = data();
Вот не соглашусь, что первое поведение очевидно.
Видимость `pw` ограничена телом `for`, так что логично предположить, что на каждой итерации это новая переменная.
К тому же текущее поведение абсолютно бесполезно и приводит только к ошибкам.
В scala, например, поведение как раз такое, что переменная каждый раз новая.
В C# поведение было аналогичным описанному в питоне. Это общепризнанный баг языка и его пофиксили в 5.0.
Видимость `pw` ограничена телом `for`, так что логично предположить, что на каждой итерации это новая переменная.
К тому же текущее поведение абсолютно бесполезно и приводит только к ошибкам.
В scala, например, поведение как раз такое, что переменная каждый раз новая.
В C# поведение было аналогичным описанному в питоне. Это общепризнанный баг языка и его пофиксили в 5.0.
К моменту вызова функции переменная pw равна 5
Тема с лексическими замыканиями не раскрыта.
Для полного понимания приведу пример:
to_pow = [(lambda x: lambda y : y ** x)(x) for x in range(5)]
print(to_pow[2](10)) # 100
11) Использовать классические id, type в функции и класс list в модуле привычным образом не получится.
Привычным — да, но можно сделать так:
def list(id=None):
print(__builtins__.id(list))
Так же в __builtins__ можно поместить свои методы и они будут везде доступны.
Думаю в статью можно добавить эту ссылку: Hidden features of Python
Спасибо, дополнил в статью ваш пример кода и ссылку
не надо так делать
Это забавная особенность питона — в импортируемых модулях __builtins__ становится словарем
пользуйтесь __builtin__ — docs.python.org/2/library/__builtin__.html
$ echo 'print __builtins__.id(list)' > a.py
$ python a.py
8692896
$ echo 'import a' > b.py
$ python b.py
Traceback (most recent call last):
File "b.py", line 1, in <module>
import a
File "/home/megabuz/a.py", line 1, in <module>
print __builtins__.id(list)
AttributeError: 'dict' object has no attribute 'id'
Это забавная особенность питона — в импортируемых модулях __builtins__ становится словарем
пользуйтесь __builtin__ — docs.python.org/2/library/__builtin__.html
Вот неочевидное поведение:
import this
3.
def get_data (it = []):
it = it[:]
it.append(1)
return it
11. PEP8 в случае, если избежать использования встроенных имен ну никак не получается, предлагает дописывать к ним trailing underscore (т.е. list -> list_, class -> class_). Мне понравилось, вроде неплохо смотрится, и не нужны всякие монструозные конструкции с __builtins__
Есть отличное выступлениеНикиты Лесникова из wargaming, рассказывающее о подноготной некоторых из этих «особенностей»: www.youtube.com/watch?v=zOuxxnUY4lg
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Python. Неочевидное поведение некоторых конструкций