Pull to refresh

Comments 53

Не соглашусь насчёт True и False: в Python последних версий это, всё-таки, отдельный булев тип:

>>> 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) ссылкой на первоисточник и кусочком кода
Насчёт True и False. Обычно ожидаешь, что True будет соответствовать всему, что не является False (0, [], '' и т.д.). Однако если 1 == True даёт True, то 5 == True вернёт False. Есть тут неконсистентность.
Проблема чисто методологическая, думаю сравнивать между собой переменные разных типов (int и bool, например) – не лучшая идея. Хотя в С уже давно придумали, как правильно приводить переменные к набору (0, 1), но на Python это будет слишком длинно. Вместо !!var нужно писать not not var.
Это актуально для Python 2.7.
Однако если 1 == True даёт True, то 5 == True вернёт False. Есть тут неконсистентность.

>>> True is 1
False
>>> True is 5
False


но на Python это будет слишком длинно. Вместо !!var нужно писать not not var.

bool(var)

а для условий и этого не надо:
if var:

1. Не знал, спасибо.
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, которая перезатрётся новой реализацией.
Хорошо если баг всплывёт сразу…
Неочевидного поведения тут нет, а отхватить проблему, которая непойми где и как вылезет можно.
Там в примере 11 большая разница между list и id / type: функция list создает проблемы пользователю библиотеки (т.к. она перекрывает list в области видимости модуля), а параметры id/type — нет, т.к. они перекрывают id/type только в области видимости функции (эта область видимости пользователю явным образом не доступна). Пользователю нужно называть аргументы функции id/type при вызове, но это не ведет ни к какому перекрытию, т.к. это, по сути, просто создание словаря с ключами 'id' и 'type'.

Такие названия аргументов могут создать проблемы разработчику функции, но это менее серьезная проблема, т.к. 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 нехорошо
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
Добавлю в статью.
Python поддерживает и более «естественный» синтакс:
val is not None
вместо
val not is None
Опечетка: в последней строке читать «not val is None»
ага, но я пишу так, чтоб не путаться как писать — то ли val is not None, то ли val not is None
У них разный приоритет, вроде как.
Спасибо большое за обзор, добавлю в список документов, которые стоит перечитать перед собеседованием:)
О некоторых вещах знал, о некоторых — нет, но настоящий ужас вызвал лишь 6-й пункт. Бегло загуглив, не нашел объяснения, не могли бы вы подробнее рассказать об этой штуке, чем это обосновано и т.п.?
Ээээ… 6 пункт реально прикольный. Вот так ищешь, ищешь баг в коде… А он вот таким оказывается. В чём проблема?
Да какой там ужас, просто в нормальном коде сравнение двух чисел по is и так встречаться не должно…

А так — объяснение достаточно простое. В случае чисел, например, с точки зрения интерпретатора Python, экземпляры даже примитивных типов вроде int «забоксены» как экземпляр PyObject. Поэтому в какой-то момент некоторый набор чисел как бы "заинтернили", чтобы, если такое число используется в программе, сэкономить на памяти под него. В некоторых случаях такой intern пытается произойти автоматически и аналогично сэкономить память.

И, опять про int-ы — как полезный сайд-эффект (и благодаря упомянутому факту, что True и False — это числа), это ещё даёт возможность сравнивать с True и False по is :)
UFO just landed and posted this here
С чего бы это? Используйте is для того, для чего он предназначен — сравнения object identity объектов (т.е., ссылаются ли две ссылки на один и тот же объект). Чаще всего это действительно нужно для None, но бывает полезно и во многих других случаях.

Просто надо понимать, что для immutable типов (к каковым относится и int, и str) этот оператор бессмысленен по определнию.
Если быть точным, то интерпретатор CPython предсоздаёт объекты для чисел от -5 до 256 включительно. Вот небольшой кусок Objects/longobject.c исходников Python 3.2.5:

#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

А как вам это сравнение яблок с апельсинами?

>>> None < 1
True

В Python 3 поправлено:

>>> None < 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: NoneType() < int()
UFO just landed and posted this here
10 еще чревато тем, что в Python SyntaxError — обычное исключение.
Не так давно перечитал спецификацию Питона и по некоторым местам, которые показались мне неочевидными или интересными сделал тест. После каждого ответа выводится верный ответ и ссылка на нужный пункт спецификации.
Укажите в начале, пожалуйста, что тест по третьему питону, т.к. есть некоторые различия.
Перепроверил, в двух вопросах, специфичных для третьего питона подписал что они для третьего питона. Спасибо.
Вопросы 11 и 12 — именно те два вопроса, текст которых я поменял. Попробуйте ctrl-f5
Пункт 2 специфичен только для второго питона, в третьем отказались от классического стиля наследования классов. Т.е. в третьем питоне можно не наследоваться явно от object
UFO just landed and posted this here
А вот такое поведение для большинства языков действительно неочевидно:

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:

let data => [1, 2, 3, 4, 5];
let [first, ...rest, last] = data();

Хотя так нельзя писать:
let first, ...rest, last = data();


Variadic templates — страшная штука. Аргумент принят, возражений не имею :)
Вот не соглашусь, что первое поведение очевидно.
Видимость `pw` ограничена телом `for`, так что логично предположить, что на каждой итерации это новая переменная.
К тому же текущее поведение абсолютно бесполезно и приводит только к ошибкам.

В scala, например, поведение как раз такое, что переменная каждый раз новая.
В C# поведение было аналогичным описанному в питоне. Это общепризнанный баг языка и его пофиксили в 5.0.
Видимость pw не ограничена телом for.
Да? Не знал.

Ну опять таки зря.
Выцеплять таким образом значение на последней итерации — криптокод, так что использовать все равно не стоит.
UFO just landed and posted this here
К моменту вызова функции переменная 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
Спасибо, дополнил в статью ваш пример кода и ссылку
не надо так делать

$ 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
Программист не должен выполнять работу обфускатора.
Поведение функции меняется, т.к. она теперь всегда возвращает измененную копию от it. (если мы передали в it список «на вырост»).
(ПС, чуть промахнулся, ответ для StreetStrider).
11. PEP8 в случае, если избежать использования встроенных имен ну никак не получается, предлагает дописывать к ним trailing underscore (т.е. list -> list_, class -> class_). Мне понравилось, вроде неплохо смотрится, и не нужны всякие монструозные конструкции с __builtins__
3. и 4. имхо одно и тоже.
Нужно просто знать, что значения по умолчанию «исполняются» и присваиваются/резервируются за переменной только во время декларации функции. Оно даже жирным выделено в доку:
Default parameter values are evaluated when the function definition is executed.
Есть отличное выступлениеНикиты Лесникова из wargaming, рассказывающее о подноготной некоторых из этих «особенностей»: www.youtube.com/watch?v=zOuxxnUY4lg
Sign up to leave a comment.

Articles