Итоги конкурса. часть 2: Бэкендеры

    Привет, Хаброжители!

    Продолжая тему, в этом посте мы подведём итоги конкурса для бэкенд-разработчиков, расскажем о типичных ошибках и хороших решениях вопросов.

    Конкурс состоял из восьми заданий, проверяющих знания Python и сопутствующих технологий.



    Вопрос 1. Какие есть методы организации параллельных вычислений, какие преимущества и недостатки у каждого метода?

    Нам приходили целые трактаты о том, какие существуют способы разделения вычислений, что, собственно, не удивительно — проблема очень актуальная в современном мире. В ответе на этот вопрос достаточно было упомянуть два основных способа:
    • Параллельность на уровне потоков
      Все, кто прошли тест, прекрасно представляют себе, как проводятся вычисления в параллельных потоках. Но не многие указали, что знают, что такое GIL (Global Interpreter Lock, такой метод блокировки тормозит выполнение всех потоков кроме того, что установил блокировку). И, да, было бы круто упомянуть coroutine-фреймворки, которые позволяют обойти GIL, но они довольно сложны в применении и не решают проблем, связанных с синхронизацией IO. Кстати о последнем — есть библиотеки, которые позволяют выполнять асинхронное IO, например в состав twisted входит пакет twisted.internet.fdesc, но применение этих фреймворков довольно трудозатратно.
    • Параллельность на уровне процессов
      Такой подход позволяет лучше использовать мощности процессора, не чувствителен к GIL, однако синхронизация процессов всё ещё представляет некоторые сложности.


    Вопрос 2. Оцените сложность алгоритма и предложите варианты по улучшению

    def uniq(iterable):
    	result = []
    	for i in interable:
    		if i not in result:
    			result.append(i)
    	return result
    


    На самом деле, большиство ответивших ошиблись с расчётом сложности.
    		if i not in result:

    Так как result является списком, то сложность этой операции O(n), где n — длина result. А длина result с каждой итерацией по iterable изменяется от 1 до n. Мы имеем арифметическую прогрессию, а, значит, сложность будет — O(n^2).

    Чтобы улучшить, необходимо поменять структуру данных. Тип set на вставку имеет сложность O(1) и на поиск внутри себя тоже O(1) (если интересно больше, читайте про временную сложность).

    Можно переписать так:
    def uniq(iterable):
    	result = set()
    	for i in interable:
    		if i not in result:
    			result.append(i)
    	return list(result)
    
    и сложность станет линейной.

    Но круче:
    def uniq(iterable):
    	result = list(set(iterable))
    


    Вопрос 3. В чём разница и почему так?

    r = range(3)
    
    a = r * 2
    print a # [0, 1, 2, 0, 1, 2]
    a[0] = 1
    print a # [1, 1, 2, 0, 1, 2]
    
    a = [r] * 2
    print a # [[0, 1, 2], [0, 1, 2]]
    a[0][0] = 1
    print a # [[1, 1, 2], [1, 1, 2]]


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

    Специфика оператора умножения, применённого к списку, такова — он повторяет list используя shallow copy.

    То есть допустимо переписать выражения:
    a = z + z # a = r * 2
    """ это конкатенация двух листов, все числа копируются в новый общий лист """
    
    a = [z] + [z] # a = [z] * 2
    """ в данном случае копируются ссылки """

    Это и создаёт описанный в вопросе эффект.

    Вопрос 4. Мы придумали свой класс Point для хранения координат и примитивных операций над ними

    Выглядит он примерно так:
    class Point(): 
    	def __init__(self): 
      		self.a = None
    		self.b = None
    		self.c = None
    		...


    Потом обнаружили, что программа, оперирующая большим количеством экземпляров такого класса ест много памяти. Миллион таких объектов занимает минимум 360мб. Что занимает так много памяти и как можно улучшить ситуацию?

    Этот вопрос доставил наибольшее количество хлопот участникам. Самый корректный способ — это использовать свойство __slots__, где нужно перечислить названия всех полей, это сильно сократит размер объекта.

    class Point(object):
    	__slots__=('a', 'b', 'c')
    	def __init__(self): 
    		self.a = None
    		self.b = None
    		self.c = None
    


    Есть также альтернативный вариант решения: заменить класс Point на tuple.

    Вопрос 5. Что происходит? Кто виноват? Что делать?

    def add1Cent(sum):
    	return sum + 0.01
    
    s = 0
    
    for i in range(5):
    	s = add1Cent(s)
    """ now I have 5 cents! """
    
    print s == 0.05 # True
    """ ok, lets add another five! """
    
    for i in range(5):
    	s = add1Cent(s)
    """ now I have 10! """
    
    print s == 0.1 # False
    print s > 0.1 # False
    print s < 0.1 # True

    Этот вопрос очень простой, и никто не допустил в нём ошибки.

    Действительно, значение во float хранится в формате IEEE 754 и содержит погрешность. Для работы с валютами лучше использовать тип Decimal, а если такой возможности нет, то написать свою функцию сравнения для float.

    Вопрос 6. Перепишите следующий код с использованием генераторов

    file = open('somefile.csv')
    total = 0
    for line in file:
    	csv_list = line.split(',')
    	if csv_list[5]:
    		 total += int(csv_list[5])
    
    print "Всего: ", total

    Какие плюсы у получившегося кода? Какие минусы?

    Это задание не создало трудностей. Переписать этот код можно сотней способов, мы ждали чего-то вроде:
    total = sum(int(line.split(',')[5]) for line in open('somefile.csv') if line.split(',')[5])
    

    Плюсы: мало кода.
    Минусы: нечитаемо, 30% падение производительности.

    Вопрос 7. В чем разница между 'фыва' и u'фыва'? Где и почему это важно?

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

    'фыва' # строка закодированная по-умолчанию.

    Все строки в python по умолчанию кодируются в ASCII (именно поэтому при компиляции возникает проблема, если строка сожержит не ASCII символ). Такое поведение можно изменить, декларировав кодировку в заголовке файла, написав в первой строке, например “# coding=utf-8”, тогда все строки будут восприниматься как закодированные в UTF-8.

    u'фыва' # строка закодированная в unicode.

    Строка в кодировке Unicode. Эта кодировка поддерживает все существующие языки и символы. Выгода, применения такой кодировки, для работы с данными очевидна. Например, для многоязычных приложений.

    Некоторые функции python конвертируют строки в кодировку указанную по умолчанию, например print().

    Работа с cvs и xml библиотеками невозможна без качественного понимания различий этих типов данных.

    Вопрос 8. Как переписать код, чтобы в конструкторе B запускался и конструктор класса A

    class A:
    	def __init__(self):
    		print "init in A"
    
    class B(A):
    	def __init__(self):
    		print "init in B"
    
    b = B() # init in B
    

    С этим вопросом справилось абсолютное большинство. Так что просто отметим тот факт, что крутой ответ содержал два примера: для new style objects и old style.

    Лучший из бэкендеров

    Сегодня к нам в гости приходил Максим Аванов ghostwriter, мы провели ему экскурсию по офису и познакомили с членами команды. Максим ехал к нам на поезде аж 14 часов из города Чебоксары, где, как он рассказал, делает свой медиа-ресурс про компьютерные игры, которые хочет продвинуть на зарубежный рынок.

    Максим Аванов, ghostwriter

    Максим об Островке и стартапах:
    Здорово, что есть люди, которые видят смысл в том, чтобы серьезно подходить к делу.
    Мода на стартапы и погоня за инвесторскими деньгами — это как-то нездорово.
    Нужно заниматься, тем что любишь и во что веришь.


    Спасибо всем, кто принял участие!


    Команда Островок.ru

    P.S.: Следующий пост будет посвящен аналитикам.
    P.P.S.: Кстати, у нас есть вакансии для python-разработчиков!
    Ostrovok.ru
    36,00
    Компания
    Поделиться публикацией

    Похожие публикации

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

      +2
      Ответил на все вопросы. Даже на тот, который помечен как «крутой ответ». А макбук не дали. Оказывается, в оферте было написано, что приз даётся победителю викторины. При этом перед началом тестирования большими буквами: «Пройди тест и выиграй макбук». Начинать возможное сотрудничество с нехитрого обмана некомильфо, господа. Вы же не юриста нанимали на работу.
        +5
        «Пройди тест и выиграй макбук» — подразумевает именно, что его надо выиграть, а именно дать наиболее полный, внятный и развернутый ответ. Таких же участников, который давали свои ответы на викторину — не один десяток. В условиях конкуренции, я допускаю, что был кто-то, кто дал наиболее интересные ответы. Это как: «участвуй в конкурсе и выиграй квартиру», всем что ли участникам должны дать квартиру?
          +1
          ===
          Отправитель: **** <***@ostrovok.ru>
          Добрый день!
          Мы благодарим Вас за участие в конкурсе от Островка!
          К выполнению задания приступало более пятисот человек! И, к нашей и,
          надеемся, Вашей радости тоже, Вы оказались одним из лучших!
          Нам хотелось бы продолжить диалог и пригласить Вас на встречу!
          ===
          Отправитель: **** <***@ostrovok.ru>
          Александр, Вы принимали участие в Конкурсе на соискание работы в Островке.
          Материальные призы разыгрывались в Викторине.
          ===
          Речь о том, что макбук вообще не в тесте разыгрывался. И об этом было написано только в оферте.
            +1
            Можно ваш email, мне в личку, я буду разбираться с этим.
          0
          Вроде ответил на вопросы :)
          А вы можете прислать ответы, которые я дал?)) (а то это было поздно ночью)
            0
            Нет, это всё и так понятно и с Гуглом все дружат. А вы можете просто запостить целиком все ответы человека, показавшегося вам самым-самым? Не с точки зрения разборок (я вообще не участвовал), а для интереса. Вдруг там какие хорошие и необычные мысли проскакивали?
              0
              Я выложу свои ответы под этим комментарием, когда вернусь домой, если ребята с Островка не сделают это раньше. Сейчас под рукой просто нет сохраненной копии.
                +1
                1. При помощи процессов. Особенности:
                = Изолированное адресное пространство.
                = Примитивы межпроцессного взаимодействия (именованные (FIFO)/неименованные каналы, семафоры, очереди сообщений, разделяемая память, сокеты, STREAMS) и программные прерывания из signal.h (POSIX.1).

                2. При помощи потоков (те, что threads). Особенности:
                = Разделяемое адресное пространство (как следствие — просто организовать совместный доступ к памяти и файловым дескрипторам).
                = Примитивы синхронизации потоков / согласованного доступа к разделяемым ресурсам (мьютексы, совместно-исключающие блокировки (rwlocks)).

                3. Различные реализации green threads/processes, которые по-сути пытаются комбинировать “удачные” подходы (например – изоляция и время на порождение) двух предыдущих подходов, выполняясь в userspace внутри какой-нибудь виртуальной машины.

                Мне сложно формализовать ответ в виде списка преимуществ/недостатков. Попробую описать субъективную точку зрения.

                Если взять некий абстрактный однопоточный процесс, выполняющий несколько независимых операций последовательно, и сравнить его с процессом, в котором те же самые задачи выполняются в несколько потоков, то в таком же общем абстрактном случае, первый процесс будет иметь меньшую производительность.
                Ещё одно “абстрактное преимущество” — можно упростить обработку асинхронных событий, привязав отдельный тип события к отдельному потоку. Сам поток при этом может быть написан с использованием обычного синхронного подхода.
                Но это очень грубое обобщение. Если оно ещё работает в каких-нибудь интерактивных программах вроде текстового редактора, где вполне логично выделять в отдельный поток обработку пользовательского ввода-вывода от других компонентов редактора, то в более сложных системах (вроде реляционных СУБД) предпочтительный подход и его плюсы/минусы не так очевидны.

                В сложных системах иногда лучше ввязываться в использование IPC и идти на компромисс в производительности (не такой ощутимый, впрочем), чем усложнять логику дополнительными механизмами синхронизации потоков. Достаточно сравнить исходные тексты обработчиков соединений в MySQL (многопоточный подход) и Postgres (межпроцессный подход).
                Если снова прибегнуть к обобщениям, то отлаживать многопоточный компонент сложнее, он чреват на неявные ошибки, из-за которых страдает “надёжность” всего процесса (родительского потока).

                ========================================

                O(n*log(n))

                Если порядок элементов не важен, то
                def uniq(iterable):
                    return list(set(iterable))
                


                Если порядок важен, то
                def uniq(iterable):
                    selected = set() 
                    return [item for item in iterable if (item not in selected and not selected.add(item))]
                


                ========================================

                Умножение списка ([] * N) фактически выполняет операцию “мягкого” копирования каждого элемента списка в N экземпляров, при котором в списке создаются не новые объекты с одинаковыми значениями, а “метки” на оригинальные объекты (можно их назвать указателями/ссылками, но это не то же самое, что указатели/ссылки в C/C++).
                >>> a = range(3) * 2 
                >>> a 
                [0, 1, 2, 0, 1, 2] 
                >>> a[0] 
                0 
                >>> a[3] 
                0 
                >>> id(a[0]) == id(a[3]) 
                True 


                Простые типы в Python являются неизменяемыми (immutable) объектами, поэтому, при попытке изменения значения, на которое указывает “метка” a[0], фактически будет создан новый объект с новым значением, к которому будет привязана старая метка.
                >>> a[0] = 1 
                >>> id(a[0]) == id(a[3]) 
                False 


                Списки же, являются изменяемыми объектами, поэтому когда мы изменяем состав элементов в a[0], этот объект не замещается новым. Поэтому мы видим все изменения объекта и через метку a[0], и через метку a[1].
                >>> a = [range(3)] * 2 
                >>> a 
                [[0, 1, 2], [0, 1, 2]] 
                >>> id(a[0]) == id(a[1]) 
                True 
                >>> id(a[0][0]) == id(a[1][0]) 
                True 
                >>> a[0][0] = 1 
                >>> a 
                [[1, 1, 2], [1, 1, 2]] 
                >>> id(a[0]) == id(a[1]) 
                True 
                >>> id(a[0][0]) == id(a[1][0]) 
                True


                Однако, элементы внутри вложенного списка всё так же замещаются, т.к. являются простыми:
                >>> a 
                [[1, 1, 2], [1, 1, 2]] 
                >>> id(a[0][0]) == id(a[1][0]) 
                True 
                >>> b=a[0][0] 
                >>> id(b) == id(a[1][0]) 
                True 
                >>> a[0][0] = 10 
                >>> a 
                [[10, 1, 2], [10, 1, 2]] 
                >>> b 
                1 
                >>> id(b) == id(a[1][0]) 
                False 


                ========================================

                Всё дело в метаданных объектов Python.
                point = Point()
                dir(point)
                dir(point.x)

                Первоначальная оптимизация может заключаться в использовании array.array. Затем можно будет поискать возможные способы для группирования наборов точек в более крупные объекты, с меньшим количеством самостоятельных объектов-координат. Всё же, редко когда нам нужна именно отдельно взятая точка.
                Возможный начальный вариант объекта-точки с использованием array.array может выглядеть так:

                from array import array
                
                class Point(object):
                	_coord_mapping = {'x':0, 'y':1, 'z':2}
                	
                	def __init__(self, xyz_list, type_code='f'):
                		"""The type of elements of `xyz_list(count:3)` must correspond to 
                		`type_code`."""
                		object.__setattr__(self, '_container', array(type_code, xyz_list))
                		
                	def __getattribute__(self, key):
                		get_attr = object.__getattribute__
                		if key not in get_attr(self, '_coord_mapping'):
                			raise AttributeError('Unable to get the "{}" coordinate.'.format(key))
                		return get_attr(self, '_container')[get_attr(self, '_coord_mapping')[key]]
                	
                	def __setattr__(self, key, val):
                		get_attr = object.__getattribute__
                		if key not in get_attr(self, '_coord_mapping'):
                			raise AttributeError('Unable to set the "{}" coordinate.'.format(key))
                		get_attr(self, '_container')[get_attr(self, '_coord_mapping')[key]] = val
                	
                	def __repr__(self):
                		coords = object.__getattribute__(self, '_container')
                		return "Point({}, {}, {})".format(coords[0], coords[1], coords[2])


                Пример:
                >>> point = Point([1,2,3], 'f') 
                >>> point 
                Point(1.0, 2.0, 3.0) 
                >>> point.x 
                1.0 
                >>> point.y 
                2.0 
                >>> point.z 
                3.0 
                >>> point.x = 5 
                >>> point.y = 6 
                >>> point.z = 7 
                >>> point 
                Point(5.0, 6.0, 7.0)


                ========================================

                Это связяано со способом представления рациональных чисел в ЭВМ в бинарном виде (конкретно, согласно стандарту IEEE-754). В принципе, любой студент, писавший в университете на Паскале в курсе, что сравнивать числа с плавающей точкой (запятой?) можно только в пределах заданной точности. Т.е. в простейшем случае — выполнять сравнение между разницей двух чисел и точностью:
                >>> a=0.01 
                >>> b=0.1 
                >>> c = a + 0.09 
                >>> c 
                0.09999999999999999 
                >>> c == b 
                False 
                >>> abs(c - b) 
                1.3877787807814457e-17 
                >>> abs(c - b) <= 0.001 
                True 
                >>> abs(c - b) <= 0.000001 
                True 
                >>> abs(c - b) <= 0.0000001 
                True 
                >>> abs(c - b) <= 0.00000001 
                True 
                >>> abs(c - b) <= 0.000000001 
                True 
                >>> abs(c - b) <= 0.0000000001 
                True 
                >>> abs(c - b) <= 0.00000000001 
                True 
                >>> abs(c - b) <= 0.000000000001 
                True 
                >>> abs(c - b) <= 0.0000000000001 
                True 
                >>> abs(c - b) <= 0.00000000000001 
                True 
                >>> abs(c - b) <= 0.000000000000001 
                True 
                >>> abs(c - b) <= 0.0000000000000001 
                True 
                >>> abs(c - b) <= 0.00000000000000001 
                False 
                >>> abs(c - b) <= 0.000000000000000001 
                False 


                В стандартной библиотеке Python, однако, уже есть реализация decimal-чисел (чисел с фиксированной десятичной точкой), поэтому пример можно переписать так:
                from decimal import *
                getcontext().prec = 2
                
                def add1Cent(sum):
                	return sum + Decimal('0.01')
                
                s = Decimal('0.00')
                for i in range(5):
                	s = add1Cent(s)
                	
                # now I have 5 cents!
                print s == Decimal('0.05')
                
                # ok, lets add another five!
                for i in range(5):
                	s = add1Cent(s)
                
                # now I have 10!
                print s == Decimal('0.1')
                print s > Decimal('0.1')
                print s < Decimal('0.1')


                =======================================

                Вопрос не совсем понятен.
                Если это csv-файл, то там должны быть переводы строк. Любая конструкция вида for line in file – уже использует “ленивое” чтение строк из таких файлов.
                Правда, файл может состоять из одной большой строки. Тогда можно организовать чтение файла по кускам, но в таком случае, обработка csv-строки в таком виде, как она представлена в примере – невозможна.

                Поэтому, я бы оставил текущий алгоритм, но добавил бы в него with для безопасного и явного закрытия дескриптора:
                total = 0
                with open('somefile.csv') as file:
                    for line in file:
                        csv_list = line.split(',')
                        if csv_list[5]:
                            total += int(csv_list[5])
                print "Всего: ", total


                Собственно, преимущества генераторов сводятся к более эффективному потреблению памяти, т.к. во многих итеративных алгоритмах нам не нужно хранить всю коллекцию обрабатываемых данных в памяти на протяжении всего времени.
                А, например, при обработке небольших файлов в среде, где есть “гонка за ресурсами”, скорее всего будет выгоднее прочитать файл целиком и пораньше освободить файловый дескриптор, чем использовать генераторы.

                =======================================

                Разница в формате представления строк в памяти программы. Если речь о Python 2.x, то стандартное 'фыва' – это 8-битное ASCII-представление (литерал) строк, которое в программе представлено как <type 'str'>. u'фыва' – формат для представления в исходниках литералов юникодных строк. В памяти они хранятся как последовательности из 16 или 32-битных чисел (зависит от сборки и реализации интерпретатора). Когда исходник читается и выполняется интерпретатором, все u'' литералы фактически конструируются в памяти через вызов unicode('фыва', encoding), где encoding – кодировка исходного файла — примет либо значение по умолчанию (ASCII), либо значение, согласно магической последовательности -*- coding: -*-.
                В Python 3 любой строковый литерал представлен в программе как юникодная строка и никакого явного разделения на уровне типов нет. Бинарные данные – отдельный тип.

                > где и почему это важно?

                Как и везде – важно разделять, понимать, и использовать по назначению. Правда, “использовать по назначению” не-юникодные строки или не utf-* исходники в современном мире довольно странно.

                =======================================

                class B(A):
                    def __init__(self):
                        super(B, self).__init__()
                        print "init in B"


                либо

                class B(A):
                    def __init__(self):
                        A.__init__(self)
                	print "init in B"


                Правда, первый вариант в данном примере лучше.
                +1
                Но круче:
                def uniq(iterable):
                  result = list(set(iterable))


                Это не круче. Из названия функции не понятно, что она должен обязательно возвращать список. Лучше просто использовать set там, где нужны только уникальные значения, и не придумывать бессмысленных функций.
                  +2
                  Еще не сказано, существенен ли порядок элементов в результирующем списке?
                  Если нет, то еще один быстрый способ
                  {}.fromkeys(iterable).keys()

                  +5
                  Могу запостить как я отвечал. По крайней мере, на собеседование меня после этого пригласили))
                  Кстати, чтоб немного потешить самолюбие ^_^ — правда что на собеседование приглашали только 5 человек из ~500 кандидатов?
                    0
                    Ну цифры примерно такого порядка.
                      0
                      Будет здорово если запостите.
                        +1
                        Вопрос 1:
                        Какие есть методы организации параллельных вычислений, какие преимущества и недостатки каждого метода?
                        ---
                        Не совсем понял что здесь подразумевается… То ли методологии и алгоритмы (MapReduce etc), то ли процессы vs потоки… Судя по размеру окошка для ответа, подразумевается все-же второе)
                        Можно выполнять параллельные вычисления с взаимодействием через разделяемой памятью (потоки), можно через отправку сообщений (процессы ОС). Первое обычно быстрее т.к. быстрее работает обращение к разделяемой памяти, но возникает множество пробем:
                        1) race confitions. Одновременный доступ к данным. Приходится использовать различные блокировки/мьютексы/очереди отсюда вытекает проблема 2:
                        2) Deadlock's — взаимные блокировки.
                        3) В случае краха одного из потоков, скорее всего упадет вся программа.
                        4) Высокие накладные расходы на переключение контекста
                        При взаимодействии через отправку сообщений как правило лишаемся тех недостатков. Распараллеливание на несколько компьютеров (по сети) заметно облегчается. Из минусов — оверхед на передачу сообщений, на сериализацию и копирование данных.

                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                        Вопрос 2:
                        Оцените сложность алгоритма и предложите варианты по улучшению:
                        def uniq(iterable): 
                            result = []
                            for i in iterable: 
                                if i not in result: 
                                    result.append(i)


                        ---

                        WTF? Это-ж задание с яндексовских вакансий! Причем переделано не совсем корректно ибо не указано какого типа данные в i. Предположим, что строки.
                        Судя по тому, что функция ничего не возвращает, можно заменить ее на
                        `def uniq(iterable): pass`
                        Точно так же будет возвращать None)))
                        Ладно, сарказм в сторону, переходим к делу!
                        Основная проблема которую я вижу — это конструкция «i not in result». У нее временная сложность O(n) согласно wiki.python.org/moin/TimeComplexity. Сложность итерации — O(n). Т. е. суммарная сложность будет O(n^2).
                        Предположили, что элементы являются строками, т.е. они немутабельны и хешируемы. Соответственно, можно использовать дополнительное хранилище найденных уникальных токенов в виде set() объекта и использовать его для проверки дубликатов. У операции `i in _found`, где "_found" — это set, сложность O(1), у операции вставки в set сложность так же O(1). Тогда суммарная сложность алгоритма будет O(n). Но при этом получаем небольшой оверхед по памяти для хранения структуры set-а.
                        def uniq(iterable):
                        	result = []
                        	_found=set()
                        	for i in iterable:
                        		if i not in _found:
                        			_found.add(i)
                        			result.append(i)


                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                        Вопрос 3:
                        В чем разница и почему так:
                        >>> a = range(3) * 2
                        >>> print a
                        [0, 1, 2, 0, 1, 2]
                        >>> a[0] = 1
                        >>> a
                        [1, 1, 2, 0, 1, 2]
                        >>> a = [range(3)] * 2
                        >>> print a
                        [[0, 1, 2], [0, 1, 2]]
                        >>> a[0][0] = 1
                        >>> a
                        [[1, 1, 2], [1, 1, 2]]


                        ---

                        в первом случае создается список `[0, 1, 2]`, создается 2 его неполных — shallow(!!!) копии. И затем списки конкатенируются. При shallow — копировании контейнера создается новый объект такого же типа и в него вставляются ссылки(!!!) на содержимое копируемого контейнера.
                        Первое выражение эквивалентно
                        l=[0, 1, 2]
                        a=l+l

                        второе
                        l=[[0, 1, 2]]
                        a=l+l

                        В первом случае в `a` содержатся неизменяемые (immutable) данные — числа. Во втором — две ссылки на один и тот же объект — список `[0, 1, 2]`. Для второго случая даже будет верно
                        `assert id(a[0])==id(a[1])`

                        Таким образом, в строке
                        `a[0][0] = 1`
                        мы:
                        1) `a[0]` — получаем ссылку на единственный объект — список `[1, 2, 3]` (ссылка на него хранится в `a`)
                        2) `a[0][0] = 1` — в полученном объекте — списке заменяем нулевой элемент на единицу.

                        Как-то так…

                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                        Вопрос 4:
                        Мы придумали свой класс Point для хранения координат и примитивных операций над ними. Выглядит примерно так:
                        class Point(): 
                            def __init__(self): 
                                self.x = None
                                self.y = None
                                self.z = None
                                ...

                        Потом обнаружили, что программа, оперирующая большим количеством экземпляров такого класса ест много памяти. Миллион таких объектов занимает минимум 360мб. Что занимает так много памяти и как можно улучшить ситуацию?

                        ---

                        В Python по умолчанию для каждого экземпляра класса создается служебный параметр `__dict__` для хранения собственных атрибутов объекта. Этот `__dict__` представляет из себя обычный питоний словарь. Но для хранения словаря нужна память. Например, для хранения пустого словаря расходуется около 136 байт на x86 и 280 байт на x64:
                        >>> import sys
                        >>> sys.getsizeof({})
                        280

                        Для борьбы с этим оверхедом для new-style классов ввели атрибут __slots__. Этот атрибут представляет из себя кортеж, в котором перечислены имена всех атрибутов объекта. Так, Point можно переписать как
                        class GoodPoint(object):
                            __slots__=('x', 'y', 'z')#, ...)
                            def __init__(self): 
                                self.x = None
                                self.y = None
                                self.z = None
                                ...

                        Попробовал на своем десктопе — без __slots__ интерпретатор со списком из миллиона точек занимает 180Мб памяти, со __slots__ всего 42Мб

                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                        Вопрос 5:
                        Что происходит? Кто виноват? Что делать?
                        >>> def add1Cent(sum):
                        ...     return sum + 0.01
                        ... 
                        >>> s = 0
                        >>> for i in range(5):
                        ...     s = add1Cent(s)
                        ... 
                        >>> # now I have 5 cents!
                        >>> print s == 0.05
                        True
                        >>> # ok, lets add another five!
                        >>> for i in range(5):
                        ...     s = add1Cent(s)
                        ... 
                        >>> # now I have 10!
                        >>> print s == 0.1
                        False
                        >>> print s > 0.1
                        False
                        >>> print s < 0.1
                        True


                        ---

                        Побочный эффект арифметических операций над числами с плавающей точкой. Точность недостаточно высокая. Нужно использовать int или decimal. Судя по всему, это функция для работы с деньгами, тогда лучше брать decimal.Decimal.
                        >>> import decimal
                        >>> decimal.getcontext().prec = 8
                        >>>def add1Cent(sum):
                        ...    return sum+decimal.Decimal('0.01')
                        ...
                        >>> for i in range(10):
                        ...     s = add1Cent(s)
                        Decimal('0.10')


                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                        Вопрос 6:
                        Перепишите следующий код с использованием генераторов
                        file = open('somefile.csv')
                        total = 0
                        for line in file:
                            csv_list = line.split(',')
                            if csv_list[5]:
                                 total += int(csv_list[5])
                                    
                        print "Всего: ", total

                        Какие плюсы у получившегося кода? Какие минусы?

                        ---

                        Очень сомнительная идея — использовать тут генераторы. По-моему текущая версия и так самая простая, понятная и правильная.
                        Но для начала замечу, что парсить CSV таким образом не стоит. Для этого существует хороший встроенный модуль `csv`…

                        with open('somefile.csv', 'r') as file:
                            column5gen=(line.split(',')[5] for line in file)
                            total=sum(int© for c in column5gen if c)


                        Из плюсов — уместили алгоритм в 2 строчки вместо 4-х. Можно и в одну впихнуть, но кому это надо? Из минусов — заметно труднее понять что этот код делает. Производительность и расход памяти примерно одинаковые должны быть. Возможно небольшой оверхед на заморозку контекста генератора…

                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                        Вопрос 7:
                        В чем разница между 'фыва' и u'фыва'? где и почему это важно?

                        ---

                        'фыва' — это объект — строка (str), а u'фыва' — объект юникодная строка (unicode). Один символ в str занимает обычно 8 бит, в unicode — завимит от реализации. Если обычная str строка содержит НЕлатинские символы, то эта строка кодированная в какой-либо кодировке (utf8/cp1251/latin1 etc). При работе со str всегда нужно держать в голове в какой именно кодировке строка, иначе могут возникунуть проблемы при обработке этой строки (печати, сохранении в БД, запихивании в XML/JSON etc). Если в тексте программы используются строковые константы в виде str, например logging.info(«Случилось что-то хорошее!»), то эта строка будет в той кодировке, в которой сохранен файл с исходником программы — при пересохранении в Linux/Windows могут появиться проблемы… Внутри программы лучше повсеместно использовать unicode строки, конвертируя их сразу при получении от IO источников.

                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                        Вопрос 8:
                        Как переписать код, чтобы в конструкторе B запускался и коснтруктор класса A?
                        class A:
                            def __init__(self):
                                print "init in A"
                                    
                        class B(A):
                            def __init__(self):
                                print "init in B"
                                    
                        >>> b = B()
                        init in B


                        ---

                        Странный вопрос…
                        class A:
                            def __init__(self):
                                print "init in A"
                                    
                        class B(A):
                            def __init__(self):
                                print "init in B"
                                super(B, self).__init__()
                          0
                          Только сегодня осознал что на последний вопрос неправильно ответил для old-style классов. Когда решал задания долго искал подвох но не нашел.
                            0
                            Спасибо.
                        +1
                        > Вопрос 6. Перепишите следующий код с использованием генераторов
                        >… мы ждали чего-то вроде:
                        Генератор — это вроде как «yield», а у вас в ответе итераторы…
                        Да и вообще очень размытое задание.
                          0
                          Вы правы, мы ожидали увидеть Generator Expression а не Generator, но это никак не повлияло на выбор лучших.
                            0
                            > Строка в кодировке Unicode. Эта кодировка поддерживает все существующие языки и символы. Выгода, применения такой кодировки, для работы с данными очевидна. Например, для многоязычных приложений.
                            Многоязычные приложения могут работать и на utf-8. Соль не в этом.

                            > Некоторые функции python конвертируют строки в кодировку указанную по умолчанию, например print().
                            print не конвертирует кодировку, этим занимается sys.stdout
                            0
                            Полазил по сайту, ничего пока интересного и нового не нашел. Можете рассказать, в чем уникальность Островка, что отличает его от других сайтов?..
                            И после историй с Сони, как то стремно вводить номер кредитки у вас на сайте. Сделайте оплату через именитые биллинги, к примеру PayPal и т.д.
                              0
                              Я так понимаю, что базу отелей вы используете с travelnow.com?
                              Т.е. получается, вы являетесь посредником travelnow.com в России?
                                0
                                Мы уже отвечали на эти вопросы habrahabr.ru/blogs/startup/123196/#comment_4044334
                                0
                                Немножко пожалуюсь тоже

                                1) Некоторые вопросы были неоднозначны или не совсем точно сформулированы. Например под «способами организации параллельных вычислений» можно разные вещи понимать… То ли меня просят про Map-reduce рассказать, то ли про процессы vs потоки
                                2) Вопрос #2 вообще немного удивил. Может это и случайность, но ооочень похожий есть в вакансиях яндекса company.yandex.ru/job/vacancies/dev_mail_pyt.xml и там он сформулирован более корректно. Например, указывается что итератор возвращает строки. Если бы он возвращал mutable- данные (словари/списки), то в set его было бы бессмысленно засовывать т.к. они нехешируемые. Ну и то, что функция uniq() не возвращает результат повеселило.
                                Кстати, в вашем решении
                                total = sum(int(line.split(',')[5]) for line in open('somefile.csv') if line.split(',')[5])
                                падение производительности может вырасти и не на 30% а на бесконечно много, если в строках будет много значений разделенных запятыми — из за того что вы 2 раза делаете line.split() (один раз в int(), другой раз в if)

                                Ну а в целом, спасибо за конкурс — было интересно принять участие. И офис у вас классный (особенно сушилка для рук в туалете xD) и пообщались на собеседовании интересно.
                                  0
                                  Все строки в python по умолчанию кодируются в ASCII (именно поэтому при компиляции возникает проблема, если строка сожержит не ASCII символ).
                                  Неправда ваша, в Python 3.x все строки — Unicode, кодируются в UTF-8, если не указана другая кодировка в файле. Кроме того существует возможность сменить кодировку по-умолчанию для всего python environment любой версии, правда это из области грязных хаков.
                                    0
                                    Про uniq через set уже сказали — результат-то другой будет, чем в исходном алгоритме.

                                    class Point(object):
                                    	__slots__=('a', 'b', 'c')
                                    	def __init__(self): 
                                    		self.a = None
                                    		self.b = None
                                    		self.c = None
                                    

                                    Вместо __slots__ для данного примера в 2.6+ лучше, наверное, использовать namedtuple.

                                    total = sum(int(line.split(',')[5]) for line in open('somefile.csv') if line.split(',')[5])
                                    

                                    Этот код полагается на детали реализации сборщика мусора в CPython, лучше так не писать и файлы открывать всегда через with, т.к. в других реализациях (том же pypy) файловый дескриптор останется болтаться.

                                    u'фыва' # строка закодированная в unicode.

                                    Строка в кодировке Unicode. Эта кодировка поддерживает все существующие языки и символы. Выгода, применения такой кодировки, для работы с данными очевидна. Например, для многоязычных приложений.

                                    Принципиальное отличие 'foo' от u'foo' как раз в том, что юникод — это не кодировка.

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

                                    Самое читаемое