Как стать автором
Обновить

Комментарии 55

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

На самом деле, это подлянский вопрос из разряда бинарного поиска.

Интересно, если бы кандидат в качестве ответа на данный вопрос написал такую регулярку: @, то его бы попросили вон или сразу бы сделали сеньором-помидором?

17. Что может быть ключом в словаре?
В Python ключом в словаре может быть любой неизменяемый объект, такой как число, строка или кортеж.

my_dict = {[1, 2]: 'one two'}
# this will raise a TypeError: unhashable type: 'list'

Текст ошибки явно указывает на то, что ключ должен быть хешируемым объектом. И к слову про мутабельность:

# tuple, но ключом быть не может
>>> {(1, 2, [1]): "val"}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'


# А вот такой список может
class h_list(list):
    def __hash__(self):
        return hash("".join(str(val) for val in self))


>>> {h_list([1, 2, 3]): "value"}
{[1, 2, 3]: 'value'}

Поддерживаю - ключом может быть любой объект с методом __hash__. Даже __hash__ = lambda _: return 42 подойдёт.

Спасибо за коммент, поправлю в статье. Отличное замечание

могу дополнить, по хорошему нужно переопределить hash и eq на случай коллизий

Много где пропали __ ... __

Также, если вы попытаетесь добавить два ключа в словарь с одинаковым хеш-кодом, то второй ключ перезапишет первый:
my_dict = {1: 'one', '1': 'one again'}

{1: 'one again'}

Вранье полнейшее просто от начала и до конца.
Начнем вот с этого:

>>> print({1: 'one', '1': 'one again'})
{1: 'one', '1': 'one again'}

У 1 и '1' просто совсем разный хэш

>>> hash(1)
1
>>> hash('1')
619728117842555882

Но дело совершенно не в этом. Объекты с одинаковым хэшем (дандер __hash__) спокойно могут сосуществовать в рамках одного дикта/сета.
Создадим кастомный класс, у которого всегда будет одинаковое значение хэша (123):

>>> my_int = type('my_int', (int,), {'__hash__': lambda self: 123})
>>> one = my_int(1)
>>> two = my_int(2)
>>> hash(one) == hash(two) == 123
True
>>> one == two
False
>>> {one: 'one', two: 'two'}
{1: 'one', 2: 'two'}

За счет одинакого хэша ключи выше попали в один и тот же бакет в рамках хэшмапа/дикта, но это не мешает им оставаться разными по значению (дандер __eq__) и таким образом храниться вместе в одном словаре.

В Python ключевое слово self используется для обращения к текущему объекту класса


Минус бал.

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

Почему пустой список нельзя использовать как аргумент по умолчанию

Не соглашусь с формулировкой. Использовать можно, но не стоит.

Какие пространства имен существуют в python?

Можно еще LEGB упомянуть. Некоторые любят этот акроним использовать.

Почему всякий раз, когда python завершает работу, не освобождается вся память?

Что вы имеете ввиду под "python завершает работу"? Для меня звучит как завершение программы.

73. Как имплементировать словарь с нуля?

Нумного повторяет вопрос про устройство хэшмапа.

Отнаследоваться и реализовать абстрактные методы. https://docs.python.org/3.9/library/collections.abc.html?highlight=collections abc#collections.abc.Mapping

Рабочая реализация не намного больше вашей, хотя для собеса слишком тяжёлая.
https://github.com/TheAlgorithms/Python/blob/master/data_structures/hashing/hash_map.py

80. Как перевернуть генератор?
...
Здесь мы используем функцию reversed() вместе с функцией tuple() для обратного итерирования через генератор без создания полной копии.

Это так не работает. Все так-же что и у списка. В некоторых случаяз даже памяти может быть столько же.

83. Расположите функции в порядке эффективности, объясните выбор.

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

36. Как использовать глобальные переменные? Это хорошая идея?


В примере типичная ошибка при использования global. Вы можете удалить сторочку с глобал и ничего не поменяется.

51. Что такое MRO? Как это работает?
...
Например, если класс A наследуется от классов B и C, а класс B наследуется от класса D, а класс C наследуется от класса E, то MRO для класса A будет определен как [A, B, D, C, E, object]

нет, порядок будет [A, B, C, E, D,object]. Помимо этого куча ошибок еще в статье, нет желания тратить время. Вы бы для начала сами разобрались в Python прежде чем "учить" других.

65. Как ускорить существующий код python?

Чтобы ускорить существующий код на Python, можно использовать несколько подходов:

  • Векторизация: векторизация позволяет оптимизировать код, который выполняет большое количество операций над массивами данных, например, использование библиотеки NumPy.

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

Иначе непонятно как человеку может придти в голову такой специфичный ответ на такой достаточно общий вопрос(еще и как самый первый вариант ответа).


Вызвать библиотеку на другом языке и заставить её работать - это основной способ ускорения задач на вычисление. На мой взгляд норм для первого варианта.

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

В примере есть фраза "например, использование библиотеки NumPy". Эта библиотека предоставляет объект array который близок к С++ array. То есть в нем хранятся однотипные данные в компактной форме. Это занимает сильно меньше места, чем список ссылок на Питоновские объекты. Функции которые работают с такими массивами называются векторными (https://www2.lawrence.edu/fast/GREGGJ/Python/numpy/numpy_vector.html). Они сильно быстрее потому, что работают с байтам в памяти, а не с объектами Питона.

Вызвать библиотеку на другом языке и заставить её работать - это основной способ ускорения задач на вычисление. На мой взгляд норм для первого варианта.

Или написать код на другом языке. Или написать другой код. Или запустить на другом компьютере. Или на другой версии интерпретатора.

Короче сам вопрос в такой формулировке максимально неадекватный.

В этом примере мы используем функцию reversed() вместе с функцией list(), чтобы создать обратный список элементов, 

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

Мне потребовалось 48 часов чтобы осознать весь масштаб этого

Если вы в начале своей карьеры, это нормально.

пункт 17 - не правильный ответ. ключ словаря может быть только _хэшируемый объект_. который наследник Hashable. к сведению, hash(tuple([1,2,3],2)) выдаст ошибку и такой кортеж не может быть ключом словаря.

Да, индекс списка может быть отрицательным.

Угу, то есть у нас в (несвязном) списке сразу 2 индекса. Ну и раз список у нас несвязный, то значит и сложность обращения по положительному и отрицательному индексам будет O(1).

Или таки индексы только положительные, а "отрицательное" обращение будет иметь сложность O(2)?

А чем сложностьО(2) отличается от сложности О(1)?

Тем же чем отрицательные индексы отличаются от положительных.

Нет никаких отрицательных индексов.

l[-x] это просто синтаксический сахар для l[len(l) - x]

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

А сахар это просто удобно, если в меру.

Вы таки и не поняли. Я как раз о том, что использования понятия "отрицательные индексы" настолько же непрофессионально как и а-ля O(2)

А сахар это просто удобно, если в меру

И в данном контексте он более чем уместен, но по итогу мы получаем полное непонимание и "отрицательные индексы"

Это просто сарказм на тему того, что никаких отрицательных индексов не существует.

Терпеть не могу лямбду.

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

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

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

Терпеть не могу лямбду

Хороший инструмент для решения своего круга задач.

Часто её применяют не к месту. Далёко ходить не надо, возьмём примеры из статьи.

double = lambda x: x * 2

Для функций с именем есть другой синтакс

squares = list(map(lambda x: x**2, numbers))

Списковые варажения тут подойдут лучше

mylist = sorted(mylist, key=lambda x: len(x))

Можно написать короче, проще быстрее.
mylist = sorted(mylist, key=len)

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


Часть вопросов на базовые вещи. Которые знать нужно. Классы декораторы, итераторы, типы данных, это то с чем придётся столкнуться решая почти любые задачи. Хотя вопросы зазубривание тоже есть.

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

А вот на сеньёрском интервью такие вопросы смотряться смешно, у меня как-то было.

Вопроса про SOLID не хватает

пункт 30 тоже не полный. про корутины ни слова...

Многопроцессорный — это про процессоры (CPU), а не про процессы (process).

Забавное слово - собес. Словно для программистов вышедших на пенсию. И им задают какие-то вопросы, чтобы пенсию посчитать

Простите, думаю в первом вопросе забыли про None это тоже тип данных

Наверное стоило бы добавить в статью связь между изменяемыми/неизменяемыми типами данных и методом hash

Python является полным ООП

Не соглашусь. Вся инкапсуляция завязана на "ты это поле не трогай, оно начинается с _ или __ и оно не для тебя". Это просто условность, которой все следуют. При этом ты спокойно получаешь досту к "приватным" полям и методам.

А как вам такое?

class A:
  def hello(self):
    return self.b_name()
class B(A):
  def __init__(self, name):
    self._name = name
  def b_name(self):
    return self._name

b = B('Vasya')
b.hello()

Класс "А" обращается к методу дочернего класса. Можно притянуть, что тут даже функции - это инстанс типа "function", и все состоит из объектов.

При этом ты спокойно получаешь досту к "приватным" полям и методам.


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

А как вам такое?

Как пример с отсутствием инкапусляции что-то доказывает?

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

Инкапсуляция будет вы используете двойной (не путать с магическими методами, когда двойной подстрочник с обеих сторон). Вот там всё серьёзно, когда вы объявляете аттрибут с двумя подстрочниками, вы можете быть увернены, что наследники вашего класса его не переопределят случайно.

Вот вам позапутаней пример. _name перезаписыватеся, а __name у каждого свой.
Когда вызываете метод родителя, то там используется его __name а когда свой, переопределённый метод, то свой __name

class A:
    def __init__(self):
        self.__name = "A"
        self._name = "a"

    def foo(self):
        print(f"{self.__name}-{self._name}")

    def boo(self):
        print(f"{self.__name}-{self._name}")


class B(A):
    def __init__(self):
        self.__name = "B"
        self._name = "b"
        super(B, self).__init__()

    def boo(self):
        print(f"{self.__name}-{self._name}")


if __name__ == '__main__':
    A().foo()  # A-a
    B().foo()  # A-a

    A().boo(). # A-a
    B().boo()  # B-a

Вся инкапсуляция завязана на "ты это поле не трогай, оно начинается с _ или __ и оно не для тебя"

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

Ключевая идея инкапсуляции в объединении данных и методов. Тоесть это про "упаковку" данных и методов в какую-то единицу, будь то класс, функция, модуль и тд., и управлением доступом к этим данным для поддержания целостности объекта.

Тоесть когда мы выносим сущность "Деньги" в отдельный класс который занимается специфическим для денег округлением вместо того чтобы всюду по приложению вызывать функцию round это и является инкапсуляцией. И то что в пайтоне "приватность" основана на соглашении никак не влияет на наличие инкапсуляции в языке. Даже если бы и такого соглашения не было - инкапсуляция в пайтоне всеравно была бы. Так-то она существует даже в С или ассемблере, например.

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

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

Насчёт 60 пункта.

# Пока никаких неожиданностей
>>> a = 're'
>>> b = 're'
>>> id(a)
140721503622592
>>> id(b)
140721503622592
# Но в материале сказано "длиннее 5 символов"
>>> c = 'tytytyt'
>>> d = 'tytytyt'
>>> id(c)
1752156911920
>>> id(d)
1752156911920
>>> c is d
True
# c и d обе длиннее 5 символов, и, тем не менее, являются интернированными
# При этом:
>>> d = 'tyty tyt'
>>> c = 'tyty tyt'
>>> c is d
False
>>> c = 't t'
>>> d = 't t'
>>> c is d
False
>>> id(c)
1752171253072
>>> id(d)
1752171258016

То-есть, дело не в длине строки, а в наличии в ней пробела.

>>> sys.version
'3.12.0 (tags/v3.12.0:0fb18b0, Oct  2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)]'

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

А про многопоточность и multiprocessing есть много и других решений (помимо multiprocessing, threading, concurrent.futures), кажется как будто стоило про них рассказать тоже

В вопросе 60 ошибка: сначала написано, что интернируются строки до 20 символов (что верно только до версии 3.7), потом в примере есть указание " # False, потому что строка "hello world" длиннее 5 символов и не является интернированной".
Актуальная информация по этому вопросу есть здесь: https://stackabuse.com/guide-to-string-interning-in-python/#implicitinterning

Да и сам вопрос на мой взгляд ошибка. Слишком специфичные знания, я ни разу не использовал по назначению. Оператор is использую крайне редко, кроме случаев is True|False|None.

В статье на которую вы ссылаетесь есть небольшая неточность, про одиночные буквы.

Есть еще один уровень "интернирования", значения добавляются туда при старте интерпретатора и даже вычисляемые значения будет ссылаться на них в памяти.

Одиночные буквыи маленькие цифры.

small_int = 42
small_int_computed = int("42")

big_int = 100500
big_int_computed = int("100500")

one_char = "a"
one_char_computed = chr(97)

text = "hello"
text_computed = "helloa".strip("a")


assert small_int == small_int_computed and small_int is small_int_computed

assert big_int == big_int_computed and big_int is not big_int_computed

assert one_char == one_char_computed and one_char is one_char_computed

assert text == text_computed and text is not text_computed

Третий assert выдаёт ошибку, так что, полагаю, там имелось ввиду is not

>>> id(one_char_computed)
140721504567512
>>> id(one_char)
140721504537824

Но смысл понятен, конечно.

Спасибо.

Это не спецификация языка, это детали реализации.
В 3.9 работает, в 3.11 нет.
По карайней мере в этой песочнице: https://pythonsandbox.dev/?file=main.py

Может быть разное поведение на разных платформах и от того с какими флагами скомпилирован сам Питон.

И как обычно в таких статьях путают мультипроцессное и мультипроцессорное. Ещё и называют его многопроцессорным. Интересно, что тогда малопроцессорное. Треды или потоки объяснены тоже неправильно, из другого языка видео взято. GIL даёт свою специфику по сравнению с ними, с другими языками.

В 59 вопросе ошибка. Код из примера выведет это:

var_2 GET val_2 
var_1 GET val_1 
var_1 SET val_1 
var_2 SET val_2

Чтобы он выводил что-то похожее на приведенный результат, нужно в принте сеттера заменить self._value на value

Итоговый код:

class Variable:
    def __init__(self, name, value):
        self._name = name
        self._value = value

    @property
    def value(self):
        print(self._name, 'GET', self._value)
        return self._value

    @value.setter
    def value(self, value):
        print(self._name, 'SET', value)
        self._value = value


var_1 = Variable('var_1', 'val_1')
var_2 = Variable('var_2', 'val_2')
var_1.value, var_2.value = var_2.value, var_1.value

И соответственно результат будет:

var_2 GET val_2
var_1 GET val_1
var_1 SET val_2
var_2 SET val_1

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории