7 марта 2015, 23:32

Кнопка «назад» в Андроиде

Это какой-то пиздец.

Как вы наверное знаете, в Андроиде есть три системные кнопки: домой (закрыть текущее приложение, эквивалентно единственной кнопке в iOS), список открытых приложений (эквивалентно двойному нажатию кнопки в iOS), и «назад». Куда назад? Где этот «зад»? Понять невозможно. Говорят, по гайдлайнам, она должна переключать строго на предыдущий вид. Т. е. если я переключился с другого приложения, то назад отменит мое переключение? Ну бред же.

Возьмем для примера Твитер. В нем как в iOS сверху слева на меню-баре есть программная кнопка «назад». На ней, кстати, находится заголовок текущего вида:



Именно так, заголовок находится на кнопке «назад». Он и нажимается, и визуально выделяется при нажатии на кнопку. Так вот, эта кнопка в меню-баре Твитера — не кнопка «назад». Если вы из профиля человека перешли в список всех его твитов, а оттуда открыли конкретный твит, то кнопка в меню-баре вернет вас в профиль, т. е. на два шага. А вот системная кнопка «назад» как раз вернет в список твитов. Поэтому Твитер приучает нас пользоваться настоящей кнопкой назад, которая возвращает нас куда нужно. Вроде бы.

Но этот паттерн ломается при выборе файлов. Когда открывается диалог, обычно открыта предыдущая папка. И нажатие «назад» уже не возвращает в список папок, а закрывает диалог. А вот если в текущем сеансе перейти в родительскую папку, а потом в какую-нибудь вложенную, то «назад» вернет в родительскую. Но зато из родительской «назад» всегда закрывает диалог. Всегда. Даже если вы открыли папку Видео, а потом открыли папку Картинки, «назад» не вернет вас в видео. Это какой-то пиздец.

Ну ладно, а как вы думаете, как действует кнопка «назад», когда из одного приложения открывается другое? Например, вы нажали на ссылку в Твитере и страница открылась в Хроме. Правильно, в Хроме закроется страничка и вас вернут в Твитер. Но это если была открыта страничка. А вот если это была ссылка в Инстаграме, то откроется Хром, еще раз спросит в чем вы хотите открыть эту ссылку (то, что Андроид не знает, в чем открывать, и не может запомнить, даже если поставить галочку, находится на втором месте по бесячести после кнопки «назад»), и если вы выберете Инстаграм, то в нем кнопка «назад» уже не вернет вас ни в Хром, ни в Твитер. Вы будете бесконечно переходить между экранами Инстаграма. Это какой-то пиздец.

Но это все херня. Один раз я нажал на ссылку в приложении Интеркома. В Интеркоме есть страница со списком диалогов и конкретный диалог. Открылся Хром, я прочел что мне было нужно, перешел обратно в приложение Интеркома и хотел вернуться из открытого диалога в список. Нажал «назад», меня перекинуло обратно в Хром. Вернулся, нажал на кнопку со стрелочкой в меню-баре, такую же как в Твитере, меня опять перекинуло в Хром. В общем, пока я не перезапустил приложение, я не мог вернуться в список диалогов, меня перепинывало в Хром. И вот это уже реальный пиздец.

31 декабря 2012, 2:05

Так ли нужно кешировать регулярные выражения

Если вы работали с регулярными выражениями в Питоне, то должны были заметить функцию compile(). Она позволяет один раз скомпилировать регулярное выражение для многократного использования. Скомпилированное регулярное выражение имеет те же методы, что есть в модуле re: match(), search(), sub() и так далее. В общем-то функции модуля re и являются обертками над методами объекта, возвращаемого функцией compile(). Вот, например, реализация функции search():

def search(pattern, string, flags=0):
    return _compile(pattern, flags).search(string)


Я решил разобраться, насколько оправдано кеширование скомпилированных регулярных выражений. Функция repeat() из модуля timeit производит 3 измерения, каждый раз прогоняя код 10000 раз. Время выполнения каждого измерения возвращается в секундах. Первый тест — вызов функции search() из модуля re. Второй — вызов метода search() заранее скомпилированного объекта.

>>> import re
>>> from timeit import repeat
>>> s = 'The length of Earth\'s Equator is roughly 40075 kilometres.'
>>> repeat(lambda: re.search('is roughly (\d+)', s), number=10000)
[0.030672073364257812, 0.026966094970703125, 0.030375003814697266]
>>>
>>> _d = re.compile('is roughly (\d+)')
>>> repeat(lambda: _d.search(s), number=10000)
[0.01565098762512207, 0.01611018180847168, 0.015336036682128906]


Как можно видеть, разница не велика. Кеширование скомпилированного регулярного выражения не дало даже двукратного прироста производительности, что на самом деле странно. Но если посмотреть код функции _compile() модуля re, все встанет на свои места: сама эта функция кеширует до 100 последних (кол-во задается переменной _MAXCACHE) регулярных выражений, которые она компилирует. Т. е. тестируемый выше случай, когда в программе используется только одно регулярное выражение, является самым оптимистичным. Давайте попробуем прикинуть время самого худшего случая, когда регулярное выражение приходится компилировать каждый раз. Для этого подключим модуль sre_compile и вызовем его функцию compile() напрямую.

>>> repeat(lambda: sre_compile.compile('is roughly (\d+)').search(s), number=10000)
[1.8406100273132324, 1.854691982269287, 1.828131914138794]


Получилась разница более чем в 100 раз. Конечно, результат сильно зависит от самого регулярного выражения. Но в целом, довольно очевидно, что кеширование небольшого набора постоянно используемых регулярных выражений лишним не будет. А кеширование большого набора просто необходимо.

26 ноября 2012, 13:40

List vs tuple

В Питоне есть два неожиданно похожих встроенных типа данных: список и кортеж. Они оба содержат последовательность элементов, к которым можно обратиться по индексу. Обычно среди отличий этих типов отмечают два:
— в списки можно добавлять элементы и удалять. Кортежи после создания нельзя изменить;
— как следствие первого: кортежи работают быстрее и занимают меньше памяти.

Из этих двух отличий большинство делает вывод, что кортежи — это способ оптимизации, когда заранее известно, что число элементов в списке меняться не будет. На самом деле кортежи о другом. Одно из отличий, которое часто упускается из вида — кортежи служат для хранения семантически разных данных, а списки — для хранения последовательности одинаковых данных. Например, [’Иванов’, ’Петров’, ’Сидоров’] — список. А (’Иванов’, ’Петр’, ’Михайлович’) — кортеж.

И именно из семантики кортежей следует то, что число элементов в них остается неизменным. (’Иванов’, ’Петр’, ’Михайлович’) — кортеж, описывающий полное имя человека. Если мы решаем, что нужно хранить еще и возраст, то это будет другой тип данных и другой кортеж. Но решение это принимается на этапе разработки, а не выполнения программы.

Бывает, что решение о количестве элементов списка тоже принимается на этапе разработки. Например, список приложений в конфиге Джанги INSTALLED_APPS. Но от этого список не становится кортежем. Хотя Джанга в данном случае, следуя принципу утиной типизации, переваривает и то, и другое.

8 октября 2012, 10:30

Хинт: быстрая вторая кнопка на трекпаде

Как известно, трекпад на Маках довольно гибко настраивается. В частности «вторую кнопку» можно настроить на клик или тап двумя пальцами. Раньше я замечал, что контекстное меню появляется спустя полсекунды после нажатия. Я думал, что это связано со скролом. Система ждет, может быть я двумя пальцами коснулся, чтобы прокрутить документ. Оказалось, нет. В разделе «scroll & zoom» настроек тачпада можно включить «умный зум». Он срабатывает по двойному тапу двумя пальцами. И именно двойного нажатия ждет система, когда нажимаешь первый раз. Поэтому, если отключить «умный зум», реакция на тап двумя пальцами станет мгновенной.

20 августа 2012, 10:50

Права на расшаренную папку в Убунте под VMware

Виртуальная машина в жизни типичного макоеба играет куда большую роль, чем в жизни виндузятника. Дело в том, что на Маке очень мало программ, и аналогов, например, такой программы, как Internet Explorer вообще нет. А в Windows 8 появится еще куча новых классных приложений в стиле Метро и конечно, никто не хочет пользоваться под маком старыми некрасивыми приложениями.

Я вообще всю жизнь для этих целей пользовался бесплатным Виртуалбоксом. Но он просто вынуждает искать альтернативы. Критический баг, приводящий к панике ядра хостовой (т. е. основной) операционной системы, не закрывается уже больше года. Я, конечно, слышал про Параллели, пробовал, но что-то они мне интуитивно не нравятся. Поэтому я поставил VMware Fusion.

Собственно, о чем заметка. Помимо винды на виртуалке я держу Убунту, именно в ней крутятся все окружение для разработки (php, mysql, python, nginx). При этом исходники приложений (php и py файлы) находятся в файловой системе хоста. Ну и понятно, что к этим исходникам нужен общий доступ из виртуалки. Можно использовать сеть, но для этих целей она слишком капризна и медленна. Виртуальные машины позволяют расшаривать папки безо всякой сети. И вот в VMware эта функция работает странно. Файлы видны, но их владельцем оказывается пользователь и группа с тем же id, что на основной ОС. И даже несмотря на то, что имена пользователей в обоих системах у меня совпадают, id разные (на Маке отсчет id пользователей начинается с 501, на Убунте с 1000). Вопрос «как быть» встречается на форумах часто, ответ в большинстве случаев один: прописать в /etc/fstab гостевой ОС следующую строку:
.host:/ /mnt/hgfs vmhgfs defaults,ttl=5,uid=1000,gid=1000 0 0
Где в uid и gid указать id желаемого пользователя и группы-владельца на гостевой ОС. У меня такой вариант не работает и приводит к ошибке во время загрузки гостевой ОС: «An error occurred while mounting /mnt/hgfs. Press S to skip mounting or M for manual recovery». Если пропустить ошибку, то владелец остается такой же, как и был, ну а как делать manual recovery даже знать не хочу.

Долго я бился, приписывал к строке параметр nobootwait, пересобирал дополнения виртуальной машины. Бестолку. А решение нашлось очень простое, и почему-то его никто не приводит. /mnt/hgfs — это папка по-умолчанию, которая монтируется каким-то неведомым способом, не через fstab. Раз не получается изменить права в ней, нужно примонтировать эти же папки в другое место уже через fstab. Я создал в папке /media папку Code и добавил в fstab такую строку:
.host:/Code /media/Code vmhgfs defaults,ttl=5,uid=1000,gid=1000,nobootwait 0 0
Где Code — название расшаренной папки. В результате в /mnt/hgfs/Code осталась папка с непонятными првами, в которую я не хожу. А хожу я в нормальную папку /media/Code с понятными правами. И имя у нее получилось короче.

14 августа 2012, 16:06

Окружение

Когда пользователь обращается с проблемой, которую не удается воспроизвести, окружение начинает играть главную роль. Мы спрашиваем какой у пользователя браузер, какой операционной системой он пользуется, какой у него установлен антивирус. Иногда виновником проблем может стать какой-нибудь прокси, который режет некоторые http-загловки. Но даже когда эти моменты учтены, это еще не все окружение. Сам пользователь тоже является тем окружением, в котором работает приложение.

Один пользователь жаловался, что при сохранении формы ему выдается предупреждение: «данные не сохранены». У других пользователей и у нас при сохранении этой же формы с его аккаунта проблем не было. Отгадка была очень интересной. Сообщение появлялось при двойном клике по кнопке сохранения. Да, небольшой процент пользователей не хотять запоминать, куда нужно кликать один раз, куда два, поэтому всегда кликают два раза.

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

7 июля 2012, 13:51

Macbook retina

Почти месяц назад компания Эпл выпустила ноутбук с самым большим физическим разрешением в мире — 2880×1800. Естественно, такое бешенное разрешение используется не для увеличения пространства на рабочем столе — при таком разрешении все было бы слишком мелко — а для увеличения четкости изображения. Для пользователя и старых программ все выглядит так, как будто реальное разрешение экрана 1440×900, но операционная система рисует все именно в разрешении 2880×1800, по возможности используя более качественные шрифты и изображения с большим разрешением. Разрешение 1440×900 в данном случае называется логическим, а 2880×1800 —физическим.

Я пока не планирую покупать Макбук с ретиной, но мне было интересно вот что. Сейчас я работаю на 13-дюймовом ноутбуке с тем же разрешением 1440×900. Ретина — 15-дюймовый ноутбук. Следовательно, на нем можно было бы комфортно работать в большем разрешении, например 1680×1050. Я хотел узнать, насколько плохо выглядит изображение на не родном разрешении. На обычных мониторах, когда выводимое разрешение меньше физического, оно растягивается до физических размеров, отчего изображение немного мылится. 1680×1050 меньше физического разрешения ретины, и у меня не было сомнений, что в этом разрешении картинка просто растягивается.

Оказалось, что я ошибался. Хотя на экране недостаточно пикселей для вывода разрешения 1680×1050 с удвоением пикселей (понадобился бы экран с физическим разрешением 3360×2100), операционная система все равно рисует интерфейс в разрешении 3360×2100, а потом, вместо увеличения, уменьшает его до физических 2880×1800. В результате, несмотря на то, что логические пиксели уже вообще никак не попадают в физические, картинка получается лучше, чем на экранах с физическим разрешением 1680×1050. То же самое с разрешением 1920×1200, которое тоже можно выставить на новом ноутбуке. Оно будет рисоваться на поверхности 3840×2400 пикселей.

Получается, на экране с ретиной можно вывести разрешения в довольно большом диапазоне, не особо потеряв в качестве. Конечно, физическое разрешение, уменьшенное вдвое, будет смотреться немного лучше, но не принципиально. Именно это и отображено в настройке дисплея на новых Макбуках:
Macbook retina

И тут нельзя не вспомнить то, что довольно часто ставят в вину Мак ОС и Эпл. Дело в том, что в Линуксе и Виндусе можно указать dpi экрана, и операционная система будет пропорционально увеличивать шрифты и некоторые элементы интерфейса. Это работает не во всех приложениях, элементы интерфейса могут поплыть, а некоторые растровые изображения растянутся и перестанут попадать в физические пиксели. Но в целом, результат будет — шрифты увеличатся. А вот в Мак ОС такая настройка была доступна только для разработчиков, а из последней версии системы её вообще убрали.

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

И этот метод можно использовать даже с обычными экранами, не только с ретиной. Посмотрите, как, например, выглядит у меня на экране разрешение 1024×640. Я выставил это разрешение, сделал скриншот и растянул его до моих физических 1440×900:



А вот как выглядит экран, отрисованный в разрешении 2048×1280 и уменьшенный опять же до 1440×900:



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

9 июня 2012, 23:07

Производительность Пэхапэ и Питона на примере реализаций стека

Это запоздалый ответ на заметку Евгения Степанищева «Производительность различных реализаций стека в ПХП». Когда я её первый раз смотрел, там не было части «Для интереса, реализовал примерно то же самое на Пайтоне…» и далее. Либо я её просто пролистал не заметив.

Для начала результаты, которые, получил я на своем компьютере. Вот версии, которые я использовал. Там увеличено кол-во итераций с 10k до 100k, чтобы вносить меньшую погрешность, и увалено последнее действие из тестовой строки, чтобы можно было проконтролировать работу алгоритма.
OS X, 64 бита, Пэхапэ: 1,3с у Array и 1,84с у SplStack;
Linux, 32 бита, Пэхапэ: 1,63с у Array и 2,3с у SplStack;
OS X, 64 бита, Питон: 2,24с;
Linux, 32 бита, Питон: 1,38с;

Как уже заметили в комментариях к оригинальной статье, Питон на Маке (или на 64 битах, что менее вероятно) работает медленнее. Пэхапэ наоборот.

Первое, что бросается в глаза — не полная эквивалентность программ. Даже пять проверок на равенство работают быстрее, чем поиск в словаре. К тому же для каждого действия тут вызываются отдельные функции, что приводит к накладным расходам. Попробуем убрать и то и другое.
OS X, 64 бита, Питон: 1,93с;
Linux, 32 бита, Питон: 1,2с;

Дальше можно вспомнить, что глобальные переменные в Питоне работают немного медленнее, чем локальные. Учтем это, заодно оформим код в удобную функцию (файл forth_stack.v3.py в предыдущем гисте).
OS X, 64 бита, Питон: 1,72с;
Linux, 32 бита, Питон: 0,94с;

Еще немного производительности можно выжать, сделав функции append и pop локальными (forth_stack.v4.py):
OS X, 64 бита, Питон: 1,53с;
Linux, 32 бита, Питон: 0,83с;

Ну и результаты для pypy:
Linux, 32 бита, pypy, v1: 0,19с;
Linux, 32 бита, pypy, v2: 0,165с;
Linux, 32 бита, pypy, v3: 0,165с;
Linux, 32 бита, pypy, v4: 0,17с;

Ладно, перейдем к Пэхапэ. Замена switch/case на if/elseif не дает никаких изменений. Но стоит заменить двойное равно на тройное, как получаем внушительный выигрыш (forth_stack.v2.php):
OS X, 64 бита, Пэхапэ: 0,99с;
Linux, 32 бита, Пэхапэ: 1,25с;

Если же немного изменить алгоритм, храня стек и указатель на его вершину, вместо изменения его размеров, то можно ускориться еще (forth_stack.v3.php):
OS X, 64 бита, Пэхапэ: 0,75с;
Linux, 32 бита, Пэхапэ: 0,93с;

Стоит сказать, что этот же способ для питона не дает выигрыша.

Я уже хотел заканчивать, как выяснилась интересная деталь у Питона: вызов int(x) работает медленнее int(x, 10), хотя значит то же самое. Вот новые результаты:
OS X, 64 бита, Питон: 1,15с;
Linux, 32 бита, Питон: 0,69с;

29 мая 2012, 12:21

Интересный способ частичного вызова

В Питоне довольно часто используется частичный вызов функции — когда часть параметров из числа тех, что принимает функция, известна заранее, а остальная часть будет известна позже. Например:

>>> from functools import partial
>>> def add(a, b):
...   return a + b
...
>>> add_2 = partial(add, 2)
>>> add(2, 3)
5
>>> add_2(3)
5


В данном случае функция add_2 всегда будет прибавлять 2. Обычно ткие вещи делаются через замыкание: функция partial возвращает другую функцию, объявленную внутри partial, а значит имеющую доступ к аргументам, с которыми мы вызвали partial (в данном случае это 2).

Я придумал способ сделать частичное применение без замыканий. Конечно, до меня его наверняка придумал кто-то другой. В Питоне функции являются объектами, и все их свойства доступны во время выполнения, и некоторые даже можно изменять. Так, например, можно изменить значения по-умолчанию для параметров, которые принимает функция. Например:

>>> def add_2(a, b=2):
...   return a + b
...
>>> add_2.func_defaults
(2,)
>>> add_2.func_defaults = (4,)
>>> add_2(3)
7


Вот так просто заставить функцию, которая прибавляет 2, прибавлять 4.

Но просто поменять func_defaults не годится, оригинальная функция не должна пострадать. Нужно её скопировать. Первое, что приходит в голову, воспользоваться встроенным модулем copy. Увы, это не даст никакой эффект:

>>> from copy import copy
>>> add == copy(add)
True


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

>>> another_add = type(add)(add.func_code, add.func_globals)
>>> another_add == add
False
>>> another_add(3, 5)
8


Теперь вновь созданной функции прописываем параметры по-умолчанию, и цель достигнута:

>>> def defaults(func, *args):
...   func = type(func)(func.func_code, func.func_globals)
...   func.func_defaults = args
...   return func
...
>>> defaults(add, 2)(3)
5


Основной бенефит — скорость при вызове получившейся функции, потому что оригинальная функция вызывается напрямую.

>>> from timeit import timeit
>>> def test_partial():
...   add_2 = partial(add, 2)
...   for _ in xrange(100):
...     add_2(3)
...
>>> timeit(test_partial, number=100000)
2.433206081390381


>>> def test_defaults():
...   add_2 = defaults(add, 2)
...   for _ in xrange(100):
...     add_2(3)
...
>>> timeit(test_defaults, number=100000)
1.8396401405334473


Это может быть критично, например, при использовании функций сортировки. Однако, если вызываться получившаяся функция будет не чаще чем, создаваться, то работа с partial будет даже быстрее, из-за реализации самой функции partial на Си.

Еще одно важное отличие: порядок аргументов, которые «запоминает» partial и defaults, будет разный. Partial запоминает аргументы с начала, defaults с конца:

>>> def sub(a, b):
...   return a - b
...
>>> partial(sub, 10)(7)
3
>>> defaults(sub, 10)(7)
-3

1 марта 2012, 13:50

Релятивистские эффекты в html

Сегодня наш верстальщик, Лёша Янке, столкнулся с проблемой, когда делал анимацию на странице:

Макет

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

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

И тут до меня дошло! Согласно теории относительности, объект, движущийся с большой скоростью должен уменьшаться в своих линейных размерах по направлению движения, относительно неподвижного наблюдателя . Эта теория, никак не применимая к движущимся html-элементам, идеально описывает такое поведение. Объект уменьшается и его часть становится невидимой.

Когда я рассказал об этом Лёше, мы посмеялись и он дополнил эту теорию: «значит нужно задать элементу position: irrelative, и на него перестанет действовать теория относительности».

PS. А пропадание рамки скорее всего связано с синхронизацией кадров, другого объяснения я найти не могу.
Ctrl +  Ранее