Comments 47
Надеюсь, вам было интересноБыло интересно. Но главное, чтобы эти вопросы никто не брал на вооружение, чтобы насиловать мозг людям на собеседованиях.
Кстати, не такие уж это и дурацкие вопросы для собеседования на некоторые позиции. Просто если человек глубоко понимает ответы на них, или способен рассуждениями и пониманием внутренней кухни питона дойти до правильного ответа после небольшого намёка, то человек знает питон и его не нужно спрашивать про какие-то тривиальные вещи.
Знание отвтетов на эти вопросы, кстати, не говорит о том насколько продуктивный перед вами программист или насколько качественнй он будет писать код, однако глубину понимания языка эти вопросу покажут.
Такие вопросы мне всё-таки больше нравятся, чем "сколько окон в Москве?" :)
Ещё однажды просили посчитать сколько в день расходуется деревянных палочек для мороженого во всех макдональдсах столицы. Понимаю, что интересует подход и обоснование методологии, но тут точно можно подобрать задачи на "подумать" из предметной области.
Студентам не помешает.
И пусть их "собеседуют" такими вопросами — они это уже будут знать.
Но, главное, повышается шанс на ускорение отладки.
Так что автору публикации респект.
Ну, одно дело — просто сказать "Не делай так", а другое — объяснить, почему. Ну и потом далее по списку:
Сначала ты не знаешь, что нельзя делать то-то.
Потом знаешь, что нельзя делать то-то.
Потом ты понимаешь, что иногда таки можно делать то-то.
Ну, а потом ты понимаешь, что помимо того-то существует еще шестьдесять шесть способов добиться желаемого, и все из них практически равноправны.
Когда тебя спрашивают "как мне добиться желаемого", ты быстро перебираешь в уме эти шестьдесять шесть способов, прикидываешь то общее, что в них есть, вздыхаешь и говоришь: "вообще-то, главное — гармония."
На вопрос обиженных учеников: "а как ее добиться?", ты говоришь: "никогда не делайте то-то".
Про объекты, из того что пришло в голову, можно к примеру проверять что элементы некоторого множества получены в одном блоке кода (да странная идея но мало ли) при этом только для питона(? а может нет) целочисленные объекты в определённом диапазоне будут вести себя по другому, и исходя из примера про print даже в разработчиках языка(точнее интерпретатора, и ещё бы проаерить как оно будет в компилируемой версии, нет единого мнения о том как оно должно быть...
PS: единственное, что тут надо знать: что всё это подозрительная муть, которую надо переписать на всякий случай.
Вообще-то, если компания растит своих джунов, то с таким кодом так или иначе придётся сталкиваться на ревью. Нечего нос воротить.
Дык молодёжь тоже не лыком шита! Опыта мало, но соображалка свежая и работает быстро, юношеский максимализм и беспечность, детская наивность. Жизнь не трепала пятничными релизами с восстановлением базы на все выходные и неаккуратными миграциями с потерей клиентских данных, которая обнаруживается только спустя пару недель…
Вот и спрашивает такая кроха: что такое хорошо, а что такое плоха…
Надо же не отмахиваться, а четко внятно объяснить что к чему или уж более менее адекватно послать в нужное место документации, хотя бы.
Ответ вида: "незнаю почему, но так делать нельзя, еще дет так говаривал..." считаю непрофессиональным. А это значит, что синьор девелопер обязан ориентироваться во всех этих тонкостях, а при поиске такого на собеседовании вполне нормально спросить пар утаких вопросов.
Важно только понимать, что невозможно всё знать и помнить, главное уметь думать, искать и разбираться. Так что ответ или отсутствие ответа кандидата по каждому из таких вопросов еще ни о чем не говорит, нужно смотреть в комплексе.
А вот второй пример очень похож на баг языка. Оптимизация, которая меняет поведение — это вы серьезно?
Непонятно: 1 — это все-таки объект или примитив?
Если примитив, то почему этот is в принципе работает?
Если объект, то какого черта используются такие ломающие оптимизации?
Это объект. В питоне всё — объект. Однако есть мутабельные и иммутабельные объекты. В данном случаее если сделать "логичнее", то получится "абсурднее". Ну, в смысле, можно сделать отдельную фазу работы транслятора, в которой вес одинаковые константы будут приводиться к одной, но это придётся делать только чтобы устранить "странное" поведение при нецелевом использовании оператора.
Это особенность языка, и проявляется она неочевидным образом при нецелевом использовании инструментов. В любом языке, да и вообще в любой отрасли найдутся такие пограничные серые зоны: сравните шуруп забитый молотком и гвоздь закрученный отвёрткой.
Никто никогда не проверяет значения через is
, потому что is
проверяет идентичность ссылок, а не значений. Он делает то же самое, что и id(x) == id(y)
. Через is
делают быструю и надежную проверку на None, False, True и т. д.
Конечно, bool(x)
совсем не то же самое что x is True
. Последний можно использовать для строгой проверки того, что x
— в точности True
, а не произвольный True-like объект с реализованным __bool__
. Такая строгая проверка на идентичность None/True/False
часто используется в сериализаторах, а is None
встречается в обычном коде вообще везде.
Параметр а превратился в метод класса, такой же, как при определении метода внутри класса через def a(self).
Тут, как мне видится, можно написать еще лучше, заменив человеческое «превратился» на описание того, что происходит. Насколько я помню, «При доступе к атрибуту экземпляра Python создаст объект типа types.MethodType с полями __func__ и __self__, если этот атрибут был найден в классе и является чем-то вызываемым». Ну и сразу будет понятно, почему для «b» превращений не произошло — его в классе не находили, его нашли в экземпляре.
Насколько я помню, «При доступе к атрибуту экземпляра Python создаст объект типа types.MethodType с полями func и self, если этот атрибут был найден в классе и является чем-то вызываемым».
Превращение функций в методы (bound methods, они же экземпляры MethodType) происходит еще на этапе создания объекта.
When an instance method object is created by retrieving a user-defined function object from a class via one of its instances, its __self__ attribute is the instance, and the method object is said to be bound. The new method’s __func__ attribute is the original function object.
Хм, ты был прав. Документация пишет, что
When a non-data attribute of an instance is referenced, the instance’s class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object.
https://docs.python.org/3/tutorial/classes.html
class A:
def f1(self):
pass
def f2():
pass
a = A()
A.f1 = f2
print(a.f1) # <bound method f2 of <__main__.A object at 0x7f0fd200d610>>
Я почему-то был уверен, что это не так ;)
Согласно ему, значение переменной из замыкания (это переменная i) вычисляется в тот момент, когда вызывается внутренняя функция (наши list_lamba_f).
Тут, как мне видится, можно написать еще лучше, заменив абстрактное «вычисляется» на техническую реализацию. Насколько я помню, «Замыкание сохраняется как ссылка на namespace, из которого значение будет получаться по ключу в момент использования (а не в момент замыкания). Все созданные lambda будут замыкать один и тот же namespace (Замыкание сохраняет ссылку на существующий namespace, откуда замыкается какое-то имя. Замыкание не создает копию namespace и ничего из namespace не копирует). Так как после прокручивания range в этом namespace останется последнее значение i=4, то имено оно будет использовано кодом позднее»
Нужно использовать хороший статический анализатор, который не допустит ни одной ситуации из списка: github.com/wemake-services/wemake-python-styleguide
В чём магичность -5 для питона?
Никакой магии нет, этот диапазон закешировали как наиболее часто используемый.
С 256 довольно очевидно.
А с -5 что-то ни чего толкового не гуглиться.
PS: Нашёл несколько статей — я взял и померял количесвто ссылок на своём проекте. Ок. Возможно в среднем на типовом проекте где-то так и есть, хотя ожидал какого-то более научного обьяснения :)
Я вечером откопал старый топик на mail.python.ru. Раньше диапазон этих "синглтонов" был [-1, 99], затем он был расширен до [-5, 100], но с введением bytes
его еще раз расширили до [-5, 256] чтобы покрыть байт.
https://bugs.python.org/issue1436243
https://hg.python.org/cpython/rev/4d1a15fc4748
https://github.com/deadsnakes/python2.4/search?q=NSMALLNEGINTS
https://github.com/deadsnakes/python2.5/search?q=NSMALLNEGINTS
Этот вопрос однажды встретился мне на собеседовании. Он про классы и методы.
Всегда очень с подозрением отношусь к компаниям, которые на интервью спрашивают вопросы вроде: «Вот вам и безобразный и заведомо не работающий код, в котором больше одной ошибки. Какую из них вернет интерпретатор?» — А какая собственно разница? Я готов еще понять, если бы под этим вызовом была бы еще обработка TypeError и NameError с разной логикой, хотя, это еще безобразнее. Сразу вопрос к потенциальному нанимателю: «а какую задачу мне придется выполнять, если надо будет разбирать такой код?». Вы хотите понять, понимаю ли я логику построения и разворачивания цепочки вызовов? Ну так придумайте релевантную практике задачу, где это значимо. Не можете? значит в вашем проекте просто нету таких задач, и этот навык не релевантен вашей позиции, зачем его проверять?
В той компании собеседование было на большой монолит с приличной историей. Кода было всякого не мало.
Такие задачи, мне кажется, дают, чтобы проверить "соображалку" и понимание языка. А релевантные примеры обрезаются до минимума, чтобы кандидату не пришлось вникать в условную "бизнес логику", а сосредоточиться на поведении языка.
А какие задачи вам запомнились на собеседованиях? Что хорошо характеризовало работодателя? Что было приятно решать?
Нормальные вопросы для собеседования. Но лучше сначала показывать майндфак, а потом предлагать объяснить/пофиксить. За незнание заранее минус не ставить.
Аналогичный пример для числа > 256 сработает ожидаемо
А вот PyPy выдаст True вместо False.
Про LOAD_CONST
я бы добавил пояснение, что за массив констант такой. Его можно глянуть в test.__code__.co_consts
.
Про метод против функции, как-то не проясняется сам механизм, что дескрипторы используются только при заглядывании в свойства класса, если в экземпляре свойства с таким именем не оказалось. Если дескриптор читается, то вызывается его метод __get__
, а такой метод есть у любой функции. Можно делать любопытные вещи:
>>> f = lambda self: self + 1
>>> m = f.__get__(6)
>>> m
<bound method <lambda> of 6>
>>> m()
7
То есть, чтобы исправить пример нужно написать
self.b = (lambda self: None).__get__(self)
тогда функция b
привяжется к self и станет методом, в точности как a
.
I = 0
def create_multipliers():
for _ in range(5):
yield lambda x: x * I
multipliers_generator = create_multipliers()
next(multipliers_generator)(2)
I = 1
next(multipliers_generator)(2)
I = 2
next(multipliers_generator)(2)
...
И только тогда до меня дошло)
>>> a = {True : "1", 1 : "one"}
>>> print(a) #?
>>> print(a)
{True: 'one'}
Можно пойти еще дальше и вспомнить, как считаются хешы для float:
>>> a = {True : "1", 1 : "one", 1.0 : "double one"}
>>> print(a)
{True: 'double one'}
Такой вот коварный питон :)
$ python
Python 3.8.3 (default, Jul 2 2020, 16:21:59)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 123
>>> b = 123
>>> a == b
True
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a == b # True
True
>>> a is b # False
False
>>>
$ cat 1.py
a = 65537
b = 65537
print(a == b)
print(a is b)
a = 251
b = 251
print(a == b)
print(a is b)
$ python 1.py
True
True
True
True
Видимо, второй пример работает так как описано только при запуске в интерактивном режиме в интерпретаторе, а при запуске файла — происходит какая-то компилляция в байткод и в ходе нее что-то оптимизируется, так что две переменных с числом 65537 тоже оказываются одним объектом. Что совсем интересно — в байткоде полученом дизассемблированием pyc лежит вот это:
1 0 LOAD_CONST 0 (65537)
2 STORE_NAME 0 (a)
2 4 LOAD_CONST 0 (65537)
6 STORE_NAME 1 (b)
3 8 LOAD_NAME 2 (print)
10 LOAD_NAME 0 (a)
12 LOAD_NAME 1 (b)
14 COMPARE_OP 2 (==)
16 CALL_FUNCTION 1
18 POP_TOP
4 20 LOAD_NAME 2 (print)
22 LOAD_NAME 0 (a)
24 LOAD_NAME 1 (b)
26 COMPARE_OP 8 (is)
28 CALL_FUNCTION 1
30 POP_TOP
5 32 LOAD_CONST 1 (251)
34 STORE_NAME 0 (a)
6 36 LOAD_CONST 1 (251)
38 STORE_NAME 1 (b)
7 40 LOAD_NAME 2 (print)
42 LOAD_NAME 0 (a)
44 LOAD_NAME 1 (b)
46 COMPARE_OP 2 (==)
48 CALL_FUNCTION 1
50 POP_TOP
8 52 LOAD_NAME 2 (print)
54 LOAD_NAME 0 (a)
56 LOAD_NAME 1 (b)
58 COMPARE_OP 8 (is)
60 CALL_FUNCTION 1
62 POP_TOP
64 LOAD_CONST 2 (None)
66 RETURN_VALUE
```
class C:
a = lambda self: self.b()
def __init__(self):
self.b = (lambda self: None).__get__(self)
c = C()
c.a()
```
Каверзные вопросы по Python