15 основных вопросов для Python собеседования
Вопрос 1
Расскажите про язык программирования Python. В чем его сильные и слабые стороны, по сравнению с другими технологиями.
Ответ
Несколько ключевых особенностей. Python - интерпретируемый язык программирования (ЯП). Это означает, что по сравнению с такими ЯП как C и ему подобных, Python не требует компиляции перед запуском программы. Другими интерпретируемыми ЯП являются: PHP, Java, Ruby, …
-
Python – динамически типизируемый, это означает, что не нужно определять типы переменных, когда производится их объявление. Можно выполнить x=111 и затем x ="тестовая строка" без ошибки.
-
Python – хорошо подходит для объектно-ориентированного программирования, а именно, позволяет определять классы наряду с композицией и наследованием. У Python нет спецификаторов доступа (таких как в C++ private, protect, public). Обосновывается это, тем что "здесь все взрослые люди".
-
Функции в Python являются объектами первого класса. Это означает, что они могут быть присвоены переменным, возвращаться из других функций и передаваться в функции. Классы также являются объектами первого класса.
-
Написание Python кода производится быстрее, но работает он часто медленнее, чем в компилируемых ЯП. К счастью, Python поддерживает вставки расширений на базе C, поэтому критические фрагменты по производительности могут быть оптимизированы. Таким примером может служить пакет numpy, который может быстро перемалывать большое количество чисел, что нереально сделать на голом Python.
-
Python находит применение во многих областях, например, Web приложения, автоматизация, научное моделирование, большие данные и многие другие. Он часто используется для межкомпонентной связки, скрепляя в одно целое части программы, написанные на разных ЯП.
-
Python упрощает сложные сущности, поэтому программисты могут фокусироваться на важных алгоритмах и структурах, а не на низкоуровневых деталях реализации.
Вопрос 2
Заполните недостающий код:
def print_directory_contents(sPath):
"""
Функция принимает имя каталога и распечатывает его содержимое
в виде путь и имя файла, а также любые другие
файлы во вложенных каталогах.
Эта функция подобна os.walk. Использовать функцию os.walk
нельзя. Данная задача показывает Ваши скилы по работе с
вложенными структурами.
"""
# заполните далее
Ответ
def print_directory_contents(sPath):
import os
for sChild in os.listdir(sPath):
sChildPath = os.path.join(sPath,sChild)
if os.path.isdir(sChildPath):
print_directory_contents(sChildPath)
else:
print(sChildPath)
Обращаем внимание
- Следуйте соглашениям об именовании. Если есть правила именования в каком-либо эталонном коде, то придерживайтесь его. Даже если это не соглашение об именовании, которые вы обычно используете.
- Внутри рекурсивной функции должен быть предусмотрен выход из неё. Убедитесь, что Вам понятно как это работает, это позволит избежать бездонного стека вызовов.
- В коде используется кроссплатформенный модуль os для общения с операционной системой. Можно было реализовать соединение пути и имени файла так: sChildPath = sPath + '/' + sChild, но это не будет работать в Windows.
- Знание базовых пакетов будет хорошим подспорьем, но не стоит ломать голову запоминая все. Поисковые системы являются Вашим лучшим другом в работе.
- Задавайте вопросы, если Вы не знаете, как реализовать тот и или иной код.
- Стремитесь писать хорошо читаемый, простой и понимаемый программный код.
Почему это важно?
- Проверяет знания программиста основ по взаимодействию с операционной системой.
- Рекурсия – очень полезна, но не стоит ей злоупотреблять.
Вопрос 3
Напишите содержимое объектов А0, А1, ... A6.
A0 = dict(zip(('a','b','c','d','e'),(1,2,3,4,5)))
A1 = range(10)
A2 = sorted([i for i in A1 if i in A0])
A3 = sorted([A0[s] for s in A0])
A4 = [i for i in A1 if i in A3]
A5 = {i:i*i for i in A1}
A6 = [[i,i*i] for i in A1]
Если не знакомы с функцией zip, то это не проблема. Ни один здравомыслящий работодатель не будет требовать детального знания стандартной библиотеки. Далее представлен результат работы функции help(zip)
zip(...)
zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]
Возвращает список картежей, где каждый кортеж содержит по одному элементу из каждого аргумента последовательности. Возвращаемый список будет усечён на длину кратчайшей последовательности одного из аргументов.
Если непонятно, то потратьте несколько минут чтобы разобраться.
Ответ
A0 = {'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4} # the order may vary
A1 = range(0, 10) # or [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] in python 2
A2 = []
A3 = [1, 2, 3, 4, 5]
A4 = [1, 2, 3, 4, 5]
A5 = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
A6 = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]]
Почему это важно?
- Понимание списков экономит время и является камнем преткновения для многих людей.
- Если Вы можете читать их, то и можете создавать.
- Часть кода умышленно сделана странной. Возможно, вы ещё столкнётесь с запутанным кодом.
Вопрос 4
Python и многопоточность. Является ли это хорошей идеей? Перечислите способы, заставляющие код на Python работать параллельно.
Ответ
По существу, Python не поддерживает многопоточности. У него есть пакет threading, но если нужно распараллелить один поток на несколько, для ускорения работы программы, тогда это плохая идея. Python базируется на основе Global Interpreter Lock (GIL). GIL подразумевает, что в любой момент времени, выполняется только однин поток. Поток получающий GIL работает немного, затем GIL переключает выполнение на следующий поток. Это происходит очень быстро, поэтому человеческому глазу может казаться, что потоки выполняются параллельно, но они действительно просто сменяются, используя одно и то же ядро ЦП. Переключения контекстов GIL добавляет издержек к выполнению. Это означает, что, если Вы хотите заставить свой код выполняться быстрее, то использование пакета threading как правило, плохая идея.
Но есть и причины использования пакета threading в Python. Если нужно выполнить некоторые действия одновременно, и эффективность не играет роли, то он будет полезен. Или если выполняется код, который должен ожидать чего-то (разновидности IO) тогда, выбор его обоснован. Но библиотека threading не позволяет использовать нам дополнительные ядра ЦП.
Многопоточность может быть реализована на стороне операционной системы (выполнение многопроцессорной обработки), некоторое будет являться внешним приложением вызываемым из кода Python (например, Spark или Hadoop), или может случиться так, что код Python, вызывают функцию C, которая выполняет дорогую многопоточную работу).
Почему это важно?
GIL – это дыра. Много людей тратит множество времени, пытаясь найти узкие места в специфическом Python многопоточным коде, прежде чем они познакомятся с GIL.
Вопрос 5
Версионируете ли Вы свой программный код?
Ответ
Я версионирую свой код! При этом Вы должны быть убедительны и упомяните, что пользуетесь Git (или любым другим решением по вкусу). И отслеживаете корреспонденцию в Granny. Git - предпочитаемая мною система управления версиями, но есть и другие, например, subversion.
Почему это важно?
Код без управления версиями похож на кофе без чашки. Иногда мы пишем одноразовые скрипты, а потом удаляем их, и это нормально. Но если Вы будете работать с каким-либо существенным объёмом кода, система управления версиями предоставит преимущества. Управление версиями помогает отслеживать кто произвел изменения в программный код; определять, когда появилась ошибка; отслеживать версии и релизы программного обеспечения; распространять исходный код среди членов команды; развертывание и определенная автоматизация. Она позволяет Вам возвращать код к работоспособному состоянию, после того как Вы повредили его. Они дают много преимуществ и это здорово.
Вопрос 6
Что распечатает код:
def f(x,l=[]):
for i in range(x):
l.append(i*i)
print(l)
f(2)
f(3,[3,2,1])
f(3)
Ответ
[0, 1]
[3, 2, 1, 0, 1, 4]
[0, 1, 0, 1, 4]
Почему?
Первый вызов функции волне очевиден, цикл добавляет 0, а затем 1 в пустой список. l – имя переменной которая указывает на список, хранящийся в памяти. Второй вызов начинается с создания нового списка, хранящегося в новом блоке памяти. l ссылается на новый список. Затем добавляются 0, 1 и 4 к новому списку. В третьем вызове функции происходит что-то странное. Она использует исходный список, хранящийся в исходной области памяти. Именно поэтому он начинается с 0 и 1.
Протестируйте следующий код, для понимания:
l_mem = []
l = l_mem # первый вызов
for i in range(2):
l.append(i*i)
print(l) # [0, 1]
l = [3,2,1] # второй вызов
for i in range(3):
l.append(i*i)
print(l) # [3, 2, 1, 0, 1, 4]
l = l_mem # третий вызов
for i in range(3):
l.append(i*i)
print(l) # [0, 1, 0, 1, 4]
Вопрос 7
Что такое «монкей патчинг» и когда он уместен?
Ответ
«Монкей патчинг» меняет поведение объекта или функции после того, как он/она будет определён/а.
import datetime
datetime.datetime.now = lambda: datetime.datetime(2012, 12, 12)
Чаще всего это – довольно плохая идея, предпочтительно такие вещи определять явным способом. Одна из причин применения «монкей патчинга» - тестирование. Пакет mock очень полезен с для этих целей.
Почему это важно?
Это показывает, собеседуемый знаком методологией модульного тестирования. Упоминание о «моней патчинге» покажет, что программист за поддерживаемый программный код (те кто против – менее эффективны). Помните принцип KISS? И это показывает, что вы знакомы, как работает Python на более низком уровне, как функции хранятся, вызываются и прочее.
PS: стоит немного почитать о mock если вы ещё не сделали этого. Это довольно полезно.
Вопрос 8
Для чего нужны конструкции *args, **kwargs?
Ответ
*args применяется, когда неизвестно точное количество передаваемых аргументов в функцию, или если нужно передать аргументы сохранив их в виде списка или кортежа. **kwargs применяется, когда неизвестно, сколько ключевых слов аргументов будет передано функции, или он может быть использован для передачи значений словаря в качестве ключевых аргументов. Идентификаторы args и kwargs являются соглашениями, можно также использовать *sveta и **marina, но это не совсем разумно.
Далее представлены примеры:
def f(*args,**kwargs): print(args, kwargs)
l = [1,2,3]
t = (4,5,6)
d = {'a':7,'b':8,'c':9}
f()
f(1,2,3) # (1, 2, 3) {}
f(1,2,3,"groovy") # (1, 2, 3, 'groovy') {}
f(a=1,b=2,c=3) # () {'a': 1, 'c': 3, 'b': 2}
f(a=1,b=2,c=3,zzz="hi") # () {'a': 1, 'c': 3, 'b': 2, 'zzz': 'hi'}
f(1,2,3,a=1,b=2,c=3) # (1, 2, 3) {'a': 1, 'c': 3, 'b': 2}
f(*l,**d) # (1, 2, 3) {'a': 7, 'c': 9, 'b': 8}
f(*t,**d) # (4, 5, 6) {'a': 7, 'c': 9, 'b': 8}
f(1,2,*t) # (1, 2, 4, 5, 6) {}
f(q="winning",**d) # () {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
f(1,2,*t,q="winning",**d) # (1, 2, 4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
def f2(arg1,arg2,*args,**kwargs): print(arg1,arg2, args, kwargs)
f2(1,2,3) # 1 2 (3,) {}
f2(1,2,3,"groovy") # 1 2 (3, 'groovy') {}
f2(arg1=1,arg2=2,c=3) # 1 2 () {'c': 3}
f2(arg1=1,arg2=2,c=3,zzz="hi") # 1 2 () {'c': 3, 'zzz': 'hi'}
f2(1,2,3,a=1,b=2,c=3) # 1 2 (3,) {'a': 1, 'c': 3, 'b': 2}
f2(*l,**d) # 1 2 (3,) {'a': 7, 'c': 9, 'b': 8}
f2(*t,**d) # 4 5 (6,) {'a': 7, 'c': 9, 'b': 8}
f2(1,2,*t) # 1 2 (4, 5, 6) {}
f2(1,1,q="winning",**d) # 1 1 () {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
f2(1,2,*t,q="winning",**d) # 1 2 (4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
Почему это важно?
Иногда нужно передать неизвестное количество аргументов или ключевых слов аргументов в функцию. Иногда мы хотим сохранить аргументы или ключевые аргументы для дальнейшего использования. Иногда это просто экономит время.
Вопрос 9
Что означает: @classmethod, @staticmethod, @property?
Подготовка к ответу
Все что приведено - это декораторы. Декоратор - это особый вид функций, которая либо принимает функцию и возвращает функцию или принимает класс и возвращает класс. Символ @ синтаксический сахар, который позволяет задекорировать что-либо, повышая читаемость кода.
@my_decorator
def my_func(stuff):
do_things
Будет эквивалентно:
def my_func(stuff):
do_things
my_func = my_decorator(my_func)
Декораторы @classmethod, @staticmethod и @property применяются к функциям, определенных в классах. Например:
class MyClass(object):
def __init__(self):
self._some_property = "properties are nice"
self._some_other_property = "VERY nice"
def normal_method(*args,**kwargs):
print("calling normal_method({0},{1})".format(args,kwargs))
@classmethod
def class_method(*args,**kwargs):
print("calling class_method({0},{1})".format(args,kwargs))
@staticmethod
def static_method(*args,**kwargs):
print("calling static_method({0},{1})".format(args,kwargs))
@property
def some_property(self,*args,**kwargs):
print("calling some_property getter({0},{1},{2})".format(self,args,kwargs))
return self._some_property
@some_property.setter
def some_property(self,*args,**kwargs):
print("calling some_property setter({0},{1},{2})".format(self,args,kwargs))
self._some_property = args[0]
@property
def some_other_property(self,*args,**kwargs):
print("calling some_other_property getter({0},{1},{2})".format(self,args,kwargs))
return self._some_other_property
o = MyClass()
# undecorated methods work like normal, they get the current instance (self) as the first argument
o.normal_method
# <bound method MyClass.normal_method of <__main__.MyClass instance at 0x7fdd2537ea28>>
o.normal_method()
# normal_method((<__main__.MyClass instance at 0x7fdd2537ea28>,),{})
o.normal_method(1,2,x=3,y=4)
# normal_method((<__main__.MyClass instance at 0x7fdd2537ea28>, 1, 2),{'y': 4, 'x': 3})
# class methods always get the class as the first argument
o.class_method
# <bound method classobj.class_method of <class __main__.MyClass at 0x7fdd2536a390>>
o.class_method()
# class_method((<class __main__.MyClass at 0x7fdd2536a390>,),{})
o.class_method(1,2,x=3,y=4)
# class_method((<class __main__.MyClass at 0x7fdd2536a390>, 1, 2),{'y': 4, 'x': 3})
# static methods have no arguments except the ones you pass in when you call them
o.static_method
# <function static_method at 0x7fdd25375848>
o.static_method()
# static_method((),{})
o.static_method(1,2,x=3,y=4)
# static_method((1, 2),{'y': 4, 'x': 3})
# properties are a way of implementing getters and setters. It's an error to explicitly call them
# "read only" attributes can be specified by creating a getter without a setter (as in some_other_property)
o.some_property
# calling some_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{})
# 'properties are nice'
o.some_property()
# calling some_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{})
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: 'str' object is not callable
o.some_other_property
# calling some_other_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{})
# 'VERY nice'
# o.some_other_property()
# calling some_other_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{})
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: 'str' object is not callable
o.some_property = "groovy"
# calling some_property setter(<__main__.MyClass object at 0x7fb2b7077890>,('groovy',),{})
o.some_property
# calling some_property getter(<__main__.MyClass object at 0x7fb2b7077890>,(),{})
# 'groovy'
o.some_other_property = "very groovy"
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# AttributeError: can't set attribute
o.some_other_property
# calling some_other_property getter(<__main__.MyClass object at 0x7fb2b7077890>,(),{})
# 'VERY nice'
Вопрос 10
Что распечатает следующий код:
class A(object):
def go(self):
print("go A go!")
def stop(self):
print("stop A stop!")
def pause(self):
raise Exception("Not Implemented")
class B(A):
def go(self):
super(B, self).go()
print("go B go!")
class C(A):
def go(self):
super(C, self).go()
print("go C go!")
def stop(self):
super(C, self).stop()
print("stop C stop!")
class D(B,C):
def go(self):
super(D, self).go()
print("go D go!")
def stop(self):
super(D, self).stop()
print("stop D stop!")
def pause(self):
print("wait D wait!")
class E(B,C): pass
a = A()
b = B()
c = C()
d = D()
e = E()
# specify output from here onwards
a.go()
b.go()
c.go()
d.go()
e.go()
a.stop()
b.stop()
c.stop()
d.stop()
e.stop()
a.pause()
b.pause()
c.pause()
d.pause()
e.pause()
Ответ
Вывод представлен далее
a.go()
# go A go!
b.go()
# go A go!
# go B go!
c.go()
# go A go!
# go C go!
d.go()
# go A go!
# go C go!
# go B go!
# go D go!
e.go()
# go A go!
# go C go!
# go B go!
a.stop()
# stop A stop!
b.stop()
# stop A stop!
c.stop()
# stop A stop!
# stop C stop!
d.stop()
# stop A stop!
# stop C stop!
# stop D stop!
e.stop()
# stop A stop!
a.pause()
# ... Exception: Not Implemented
b.pause()
# ... Exception: Not Implemented
c.pause()
# ... Exception: Not Implemented
d.pause()
# wait D wait!
e.pause()
# ...Exception: Not Implemented
Почему это важно?
Потому что понимание объектно-ориентированного программирование очень важно. Отвечая на этот вопрос вы демонстрируете понимание наследования и используете функцию super языка Python. Чаще всего, порядок разрешения не важен. Но иногда он всё таки важен, но это зависит от приложения.
Вопрос 11
Какой выход будет от следующего кода?
class Node(object):
def __init__(self,sName):
self._lChildren = []
self.sName = sName
def __repr__(self):
return "<Node '{}'>".format(self.sName)
def append(self,*args,**kwargs):
self._lChildren.append(*args,**kwargs)
def print_all_1(self):
print(self)
for oChild in self._lChildren:
oChild.print_all_1()
def print_all_2(self):
def gen(o):
lAll = [o,]
while lAll:
oNext = lAll.pop(0)
lAll.extend(oNext._lChildren)
yield oNext
for oNode in gen(self):
print(oNode)
oRoot = Node("root")
oChild1 = Node("child1")
oChild2 = Node("child2")
oChild3 = Node("child3")
oChild4 = Node("child4")
oChild5 = Node("child5")
oChild6 = Node("child6")
oChild7 = Node("child7")
oChild8 = Node("child8")
oChild9 = Node("child9")
oChild10 = Node("child10")
oRoot.append(oChild1)
oRoot.append(oChild2)
oRoot.append(oChild3)
oChild1.append(oChild4)
oChild1.append(oChild5)
oChild2.append(oChild6)
oChild4.append(oChild7)
oChild3.append(oChild8)
oChild3.append(oChild9)
oChild6.append(oChild10)
# specify output from here onwards
oRoot.print_all_1()
oRoot.print_all_2()
распечатает:
oRoot.print_all_1()
<Node 'root'>
<Node 'child1'>
<Node 'child4'>
<Node 'child7'>
<Node 'child5'>
<Node 'child2'>
<Node 'child6'>
<Node 'child10'>
<Node 'child3'>
<Node 'child8'>
<Node 'child9'>
распечатает:
oRoot.print_all_2()
<Node 'root'>
<Node 'child1'>
<Node 'child2'>
<Node 'child3'>
<Node 'child4'>
<Node 'child5'>
<Node 'child6'>
<Node 'child8'>
<Node 'child9'>
<Node 'child7'>
<Node 'child10'>
Почему это важно?
Потому что всё из чего состоят и строятся объекты, являются также объектами. Объекты состоят из сущностей, и их нужно как-то инициализировать. Они также связывают некоторые рекурсивные сущности и применяютя в генераторах.
Генераторы - это хорошо. Можно было достичь аналогичной функциональностьи в print_all_2, просто построив длинный список, а затем, печатав результат. Одно из хороших свойств генераторов является то, что они не занимают много места в памяти.
Стоит также отметить, что print_all_1 обходит дерево в глубину первым способом, в то время как print_all_2 преимущественно в ширину. Убедитесь, что вы понимаете, эти термины. Иногда один вид обхода является более предпочтительным, чем другой. Но это очень сильно зависит от приложения.
Вопрос 12
Кратко опишите механизм сборки мусора в Python.
Ответ
Есть несколько моментов про которые стоит упомянуть:
- Python отслеживает количество ссылок на каждый объект в памяти. Если счетчик ссылок равен нулю, то соответствующий объект удаляется из памяти, освобождённая память может быть использована повторно.
- Иногда случаются так называемые «ссылочные циклы». Сборщик мусора периодически ищет их и удаляет. Например, если есть два объекта О1 и О2 такие, что О1.х == О2 и О2.х == О1. Если О1 и О2 не ссылается на что-нибудь другое, то их недолжно быть. Но при этом каждый из них имеет счетчик ссылок равный 1.
- Для ускорения сбора мусора используются некоторые эвристики. Например, недавно созданные объекты, скорее всего, будут мертвы. Свежесозданным объектам, сборщик мусора присваивает поколение. Каждый объект получает одно поколение, младшие поколения рассматриваются в первую очередь.
Данные тезисы соответствуют CPython реализации.
Вопрос 13
Отсортируйте следующие функции в порядке эффективности, первыми идут самые эффективные. Функции принимают список из дробных чисел между 0 и 1. Список должен быть довольно длинным, например, таким
[random.random() for i in range(100000)]
Как вы обоснуете правильность своего ответа?
def f1(lIn):
l1 = sorted(lIn)
l2 = [i for i in l1 if i<0.5]
return [i*i for i in l2]
def f2(lIn):
l1 = [i for i in lIn if i<0.5]
l2 = sorted(l1)
return [i*i for i in l2]
def f3(lIn):
l1 = [i*i for i in lIn]
l2 = sorted(l1)
return [i for i in l1 if i<(0.5*0.5)]
Ответ
Ряд эффективности функций: f2, f1, f3. Чтобы это доказать, воспользуемся профилировщиками программного кода. У Python есть встроенный пакет для профилирования (https://docs.python.org/2/library/profile.html) в нём и протестируем функции.
import cProfile
lIn = [random.random() for i in range(100000)]
cProfile.run('f1(lIn)')
cProfile.run('f2(lIn)')
cProfile.run('f3(lIn)')
Развёрнутый вывод результатов работы профилировщика
>>> cProfile.run('f1(lIn)')
4 function calls in 0.045 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.009 0.009 0.044 0.044 <stdin>:1(f1)
1 0.001 0.001 0.045 0.045 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.035 0.035 0.035 0.035 {sorted}
>>> cProfile.run('f2(lIn)')
4 function calls in 0.024 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.008 0.008 0.023 0.023 <stdin>:1(f2)
1 0.001 0.001 0.024 0.024 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.016 0.016 0.016 0.016 {sorted}
>>> cProfile.run('f3(lIn)')
4 function calls in 0.055 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.016 0.016 0.054 0.054 <stdin>:1(f3)
1 0.001 0.001 0.055 0.055 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.038 0.038 0.038 0.038 {sorted}
Выводы сделаны на основе времени работы каждой из функций.
Почему это важно?
Выявление и предупреждение ботлнеков, стоит потраченного времени. Чаще всего эффективное программирование сводится к здравому смыслу, в приведённом примере вторая функция явно быстрее, потому что сортирует список меньшего размера, в котором отфильтрованы все элементы меньшие 0.5. Менее очевидные вещи могут быть найдены с помощью специальных профилировщиков. Будет хорошо, если вы научитесь ими пользоваться.
Вопрос 14
Делаете ли Вы ошибки в программном коде?
Неправильный ответ
Я никогда не косячу.
Вопрос 15
У вас есть личные проекты?
Ответ
Если да, то вы готовы на нечто большее, чем простое поддержание своих знаний в актуальном состоянии. Если вы работаете над своим программным проектом вне основного рабочего времени, то работодатель с большой вероятностью увидит вас более целеустремлённым работником, способным к карьерному росту. Если они не знают про личный проект, то намекните им об этом.
Заключение
Приложенные вопросы намеренно затронули многие темы. А ответы на них были даны развёрнутые. На собеседовании стремитесь продемонстрировать понимание в достаточно сжатой форме не углубляясь в подробности. В ответах на вопросы достаточно информации, чтобы они были полезны, даже если никогда с ними прежде не сталкивались. Надеемся, что вы найдёте что-то полезное для себя.