Рубрики
Без рубрики

15 Основных Вопросов Интервью Python

Ищете работу на Python? Скорее всего, вам нужно будет доказать, что вы знаете, как работать с Python. Вот несколько вопросов, которые охватывают широкую базу навыков, связанных с Python.

Автор оригинала: Sheena.

Вступление

Ищете работу на Python? Скорее всего, вам нужно будет доказать, что вы знаете, как работать с Python. Вот несколько вопросов, которые охватывают широкую базу навыков, связанных с Python. Основное внимание уделяется самому языку, а не какому-либо конкретному пакету или фреймворку. Каждый вопрос будет связан с подходящим учебником, если таковой имеется. Некоторые вопросы будут охватывать несколько тем.

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

Чем Этот Учебник Не Является

Этот учебник не нацелен на то, чтобы охватить все доступные культуры на рабочем месте – разные работодатели будут задавать вам разные вопросы по-разному; они будут следовать разным соглашениям; они будут ценить разные вещи. Они будут испытывать вас по-разному. Некоторые работодатели усадят вас за компьютер и попросят решить простые задачи; некоторые поставят вас перед белой доской и сделают то же самое; некоторые дадут вам домашний тест для решения; некоторые просто поговорят с вами.

Лучший тест для программиста-это собственно программирование. Это трудно проверить с помощью простого учебника. Поэтому для получения бонусных баллов убедитесь, что вы действительно можете использовать функциональность, продемонстрированную в вопросах. Если вы действительно понимаете, как добраться до ответов достаточно хорошо, чтобы вы действительно могли использовать продемонстрированные концепции, тогда вы выигрываете.

Точно так же лучший тест для инженера-программиста-это собственно инженерия. Этот учебник посвящен Python как языку. Способность проектировать эффективные, эффективные, поддерживаемые иерархии классов для решения нишевых задач-это здорово и замечательно, и набор навыков стоит развивать, но далеко выходит за рамки этого текста.

Еще одна вещь, которой не является этот учебник, – это совместимость с PEP8. Это делается намеренно, поскольку, как уже упоминалось ранее, разные работодатели будут следовать разным соглашениям. Вам нужно будет адаптироваться к культуре рабочего места. Потому что практичность превосходит чистоту.

Еще одна вещь, которую этот учебник не является кратким. Я не хочу просто забрасывать тебя вопросами и ответами и надеяться, что что-то останется. Я хочу, чтобы вы поняли это или, по крайней мере, поняли достаточно хорошо, чтобы вы сами могли искать дальнейшие объяснения для любых проблемных тем.

Хотите пройти техническое собеседование? График а Практическое занятие по Техническому Собеседованию теперь с экспертом!

Вопрос 1

Что такое Python на самом деле? Вы можете (и вам рекомендуется) провести сравнение с другими технологиями в своем ответе

Ответ

Вот несколько ключевых моментов:

  • Python-это интерпретируемый язык. Это означает, что, в отличие от таких языков, как C и его вариантов, Python не нужно компилировать перед запуском. Другие интерпретируемые языки включают PHP и Ruby .

  • Python динамически типизирован, это означает, что вам не нужно указывать типы переменных при их объявлении или что-то в этом роде. Вы можете делать такие вещи, как x=111 , а затем x="Я строка" без ошибок

  • Python хорошо подходит для объектно-ориентированного программирования, поскольку он позволяет определять классы наряду с композицией и наследованием. Python не имеет спецификаторов доступа (например, C++’s public , private ), обоснование этого пункта дается так: “мы все здесь взрослые”

  • В Python функции являются объектами первого класса. Это означает, что они могут быть назначены переменным, возвращены из других функций и переданы в функции. Классы также являются объектами первого класса

  • Написание кода на Python происходит быстро, но его выполнение часто медленнее, чем на скомпилированных языках. К счастью, Python позволяет включать расширения на основе C, так что узкие места могут быть оптимизированы и часто таковыми являются. Пакет numpy – хороший пример этого, он действительно довольно быстрый, потому что большая часть хруста чисел, который он делает, на самом деле не выполняется Python

  • Python находит применение во многих сферах – веб-приложениях, автоматизации, научном моделировании, приложениях больших данных и многих других. Он также часто используется в качестве “клеевого” кода, чтобы заставить другие языки и компоненты играть хорошо.

  • Python делает сложные вещи легкими поэтому программисты могут сосредоточиться на переопределении алгоритмов и структур, а не на мелких деталях низкого уровня.

Почему Это Имеет Значение:

Если вы претендуете на должность питона, вы должны знать, что это такое и почему это так чертовски круто. И почему это не О.

Вопрос 2

Заполните недостающий код:

def print_directory_contents(sPath):
    """
    This function takes the name of a directory 
    and prints out the paths files within that 
    directory as well as any files contained in 
    contained directories. 

    This function is similar to os.walk. Please don't
    use os.walk in your answer. We are interested in your 
    ability to work with nested structures. 
    """
    fill_this_in

Ответ

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 для взаимодействия с операционной системой кросс-платформенным способом. Вы могли бы сказать childpath + '/' + child , но это не сработало бы в Windows
  • Знакомство с базовыми пакетами действительно стоит того, но не ломайте голову, пытаясь запомнить все, Google-ваш друг на рабочем месте!
  • Задавайте вопросы, если вы не понимаете, что должен делать код
  • ПОЦЕЛУЙ! Будь проще, Глупец!

Почему Это Имеет Значение:

  • Отображает знание основных элементов взаимодействия с операционной системой
  • Рекурсия чертовски полезна

Вопрос 3

Глядя на приведенный ниже код, запишите конечные значения A0, A1, …An.

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] ...), (...)]
    
    Return a list of tuples, where each tuple contains the i-th element
    from each of the argument sequences.  The returned list is truncated
    in length to the length of the shortest argument sequence.

Если это не имеет смысла, то потратьте несколько минут, чтобы понять это, как бы вы ни решили.

Ответ

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]]

Почему Это Имеет Значение

  1. Понимание списка-это прекрасная экономия времени и большой камень преткновения для многих людей
  2. Если вы можете прочитать их, вы, вероятно, можете записать их
  3. Некоторые из этих кодов были сделаны намеренно странными. Возможно, вам придется работать с некоторыми странными людьми

Вопрос 4

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

Ответ

Python не допускает многопоточности в самом прямом смысле этого слова. У него есть многопоточный пакет но если вы хотите, чтобы многопоточность ускоряла ваш код, то обычно не стоит его использовать. В Python есть конструкция, называемая Global Interpreter Lock (GIL). GIL гарантирует, что только один из ваших “потоков” может выполняться в любой момент времени. Нить получает ГИЛ, делает небольшую работу, затем передает ДЕВУШКУ на следующую нить. Это происходит очень быстро, поэтому человеческому глазу может показаться, что ваши потоки выполняются параллельно, но на самом деле они просто по очереди используют одно и то же ядро процессора. Вся эта передача GIL добавляет накладные расходы на выполнение. Это означает, что если вы хотите, чтобы ваш код работал быстрее, то использование пакета threading часто не является хорошей идеей.

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

Многопоточность может быть передана на аутсорсинг операционной системе (путем выполнения многопоточной обработки), некоторому внешнему приложению, которое вызывает ваш код Python (например, Spark или Hadoop), или некоторому коду, который вызывает ваш код Python (например, вы можете заставить свой код Python вызывать функцию C, которая делает дорогостоящие многопоточные вещи).

Почему Это Имеет Значение

Потому что ГИЛ-это А-дыра. Многие люди тратят много времени, пытаясь найти узкие места в своем причудливом многопоточном коде Python, прежде чем узнают, что такое GIL.

Вопрос 5

Как вы отслеживаете различные версии вашего кода?

Ответ:

Контроль версий! В этот момент вы должны вести себя взволнованно и рассказать им, как вы даже используете Git (или что-то ваше любимое), чтобы отслеживать переписку с бабушкой. 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 – это имя переменной, указывающей на список, хранящийся в памяти. Второй вызов начинается с создания нового списка в новом блоке памяти. l затем ссылается на этот новый список. Затем он добавляет 0, 1 и 4 к этому новому списку. Так что это здорово. Третий вызов функции-самый странный. Он использует исходный список, хранящийся в исходном блоке памяти. Вот почему он начинается с 0 и 1.

Попробуйте это, если вы не понимаете:

l_mem = []

l = l_mem           # the first call
for i in range(2):
    l.append(i*i)

print(l)            # [0, 1]

l = [3,2,1]         # the second call
for i in range(3):
    l.append(i*i)

print(l)            # [3, 2, 1, 0, 1, 4]

l = l_mem           # the third call
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 очень полезен для этой цели.

Почему Это Имеет Значение

Это показывает, что вы немного разбираетесь в методологиях модульного тестирования. Ваше упоминание об избегании обезьян покажет, что вы не из тех программистов, которые предпочитают модный код ремонтопригодному (они есть, и с ними плохо работать). Помните принцип ПОЦЕЛУЯ? И это показывает, что вы немного знаете о том, как Python работает на более низком уровне, как на самом деле хранятся и вызываются функции и тому подобное.

PS : действительно стоит немного почитать о mock , если вы еще этого не сделали. Это очень полезно.

Вопрос 8

Что означает этот материал: *args , **kwargs ? И зачем нам его использовать?

Ответ

Используйте *args , когда мы не уверены, сколько аргументов будет передано функции, или если мы хотим передать сохраненный список или кортеж аргументов функции. **kwargs используется, когда мы не знаем, сколько аргументов ключевых слов будет передано функции, или он может быть использован для передачи значений словаря в качестве аргументов ключевых слов. Идентификаторы args и kwargs являются условностью, вы также можете использовать *bob и **billy , но это было бы неразумно.

Вот небольшая иллюстрация:

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 
# >

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
# >

o.class_method()
# class_method((,),{})

o.class_method(1,2,x=3,y=4)
# class_method((, 1, 2),{'y': 4, 'x': 3})

# static methods have no arguments except the ones you pass in when you call them

o.static_method
# 

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 "", line 1, in 
# 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 "", line 1, in 
# 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 "", line 1, in 
# 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

Почему нас это волнует?

Потому что программирование ОО очень, очень важно. Действительно. Ответ на этот вопрос показывает ваше понимание наследования и использования функции Python super . В большинстве случаев порядок разрешения не имеет значения. Иногда это происходит, это зависит от вашего приложения.

Вопрос 11

Рассмотрим следующий код, что он будет выводить?

class Node(object):
    def __init__(self,sName):
        self._lChildren = []
        self.sName = sName
    def __repr__(self):
        return "".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()

Ответ

root.print_all_1() печать:












root.print_all_2() печать:












Почему нас это волнует?

Потому что композиция и конструирование объектов-это то, что есть объекты. Объекты состоят из материала, и их нужно как-то инициализировать. Это также связывает некоторые вещи о рекурсии и использовании генераторов.

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

Стоит также отметить, что print_all_1 пересекает дерево в первую очередь по глубине, в то время как print_all_2 -по ширине. Убедитесь, что вы понимаете эти термины. Иногда один вид обхода более уместен, чем другой. Но это во многом зависит от вашего приложения.

Вопрос 12

Опишите вкратце механизм сборки мусора Python.

Ответ

Здесь можно многое сказать. Есть несколько основных моментов, которые вы должны упомянуть:

  • Python поддерживает подсчет количества ссылок на каждый объект в памяти. Если счетчик ссылок стремится к нулю, то связанный объект больше не живет, и память, выделенная этому объекту, может быть освобождена для чего-то другого
  • иногда происходят вещи, называемые “эталонными циклами”. Сборщик мусора периодически ищет их и очищает. Например, если у вас есть два объекта o1 и o2 такие, что o1.x и o2.x . Если o1 и o2 не ссылаются ни на что другое, то они не должны верить. Но каждый из них имеет отсчет отсчета 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 есть прекрасный профилирующий пакет , который должен сделать свое дело.

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 :1(f1)
        1    0.001    0.001    0.045    0.045 :1()
        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 :1(f2)
        1    0.001    0.001    0.024    0.024 :1()
        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 :1(f3)
        1    0.001    0.001    0.055    0.055 :1()
        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}

А Зачем Беспокоиться?

Обнаружение и устранение узких мест часто имеет смысл. Большая часть кодирования для повышения эффективности сводится к здравому смыслу – в приведенном выше примере, очевидно, быстрее сортировать список, если он меньше, поэтому, если у вас есть выбор фильтрации перед сортировкой, это часто хорошая идея. Менее очевидные вещи все еще могут быть обнаружены с помощью соответствующих инструментов. Хорошо знать об этих инструментах.

Вопрос 14

Что-то, в чем ты потерпел неудачу?

Неправильный ответ

Я никогда не ошибаюсь!

Почему Это Так Важно:

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

Вопрос 15

У вас есть какие – то личные проекты?

Действительно?

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

Вывод

Эти вопросы намеренно затрагивали многие темы. И ответы были намеренно многословны. В интервью по программированию вам нужно будет продемонстрировать свое понимание, и если вы можете сделать это в сжатой форме, то непременно сделайте это. Я постарался дать достаточно информации в ответах, чтобы вы могли извлечь из них какой-то смысл, даже если вы никогда раньше не слышали о некоторых из этих тем. Я надеюсь, что вы найдете это полезным в вашей охоте за работой.

Сходи за ними, тигр.