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

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

Напрашивается, что использование коллекций противоречит «Простое лучше сложного». Нет?
Добавил в заметки. Лучше периодически подобные статьи просматривать, чтобы не написать того, о чем потом будет стыдно :)
НЛО прилетело и опубликовало эту надпись здесь
Все что полезно описано в любой книге, а что не описано вызывает смешанные чувства. Например это:
>> Создавая классы в Python задействуйте magic methods
??, так ли это обязательно?

или это
>>Обратите внимание что вы можете использовать выражение if...else в любом выражение.
Это тоже врядли добавит читабельности
>>Обратите внимание что вы можете использовать выражение if...else в любом выражение.
Это тоже врядли добавит читабельности

Это гораздо читабельнее, чем тернарный условный оператор в других языках. Я при чтении кода всегда спотыкаюсь о конструкции вида a?b:c, потому что там, конечно, не a, b и c стоят, а что-нибудь посложнее. А так гораздо симпатичнее.
Плюс применение в форме (func1 if y == 1 else func2)(arg1, arg2) выглядит вполне прилично и, что гораздо важнее, естественно. Обычно в таких ситуациях надо писать вилку из условий или делегировать выбор вызываемой функции в другую функцию. Если первое вполне ничего (и в большинстве случаев, конечно, так и надо писать), то второе и нужно редко, и читать неудобно (и незачем плодить сущности, кроме того).
На счёт скорости словарей:
# coding: utf-8

from collections import defaultdict

def trydict():
    freqs = {}
    for c in "abracadabra":
        try:
            freqs[c] += 1
        except:
            freqs[c] = 1

def getdict():
    freqs = {}
    for c in "abracadabra":
        freqs[c] = freqs.get(c, 0) + 1

def defdict():
    freqs = defaultdict(int)
    for c in "abracadabra":
        freqs[c] += 1

def indict():
    freqs = {}
    for c in "abracadabra":
        freqs[c] = freqs[c] + 1 if c in freqs else 1

if __name__ == '__main__':
    from timeit import Timer
    t = Timer("trydict()", "from __main__ import trydict")
    print t.timeit()
    t = Timer("getdict()", "from __main__ import getdict")
    print t.timeit()
    t = Timer("defdict()", "from __main__ import defdict")
    print t.timeit()
    t = Timer("indict()", "from __main__ import indict")
    print t.timeit()


7.2616994618
3.58429660753
3.52491727301
2.65262847652
А это:
c = Counter("abracadabra")

Хоть и красиво, ещё медленнее, чем с try (около 10, относительно этих замеров).
Интересно, спасибо. Решил сравнить с PyPy:
➜ ~ time python dict_speed.py
6.94295406342
4.35391998291
4.43011784554
3.0556640625
python dict_speed.py 18,81s user 0,02s system 99% cpu 18,828 total
➜ ~ time pypy dict_speed.py
0.70256114006
0.649827003479
0.807382106781
0.672923088074
pypy dict_speed.py 2,87s user 0,02s system 99% cpu 2,893 total
Нда с pypy разница в подходах уже особого значения не имеет.

у меня
0.593121051788
0.550466060638
0.690548181534
0.56803393364
Забавно, но следующий вариант просто рвет остальных конкурсантов по скорости:
def mydict():
  freqs = {}
  for c in "abracadabra":
   if freqs.has_key(c):
    freqs[c]+=1
   else:
    freqs[c]=1

Результат: 0.0511064953786

что я делаю не так?
Ай ли? Запускаете не так, читайте внимательно с чего все началось и как тестится производительность.

4.08951997757
2.25080990791
2.26123785973
1.59414696693
1.96405720711
У меня есть небольшое замечание, я обычно для проверки чёртности/нечётности использую не num % 2, а num & 1. Решил протестировать на интерпретаторе Python 2.7.3. Скорость выполнения моего вариант с & оказался чуточку быстрее, чем %. Написал я два скрипта следующих:
Первый:
# odd1.py
numbers = range(30000000)
numbers = [num for num in numbers if not num & 1]

Второй:
# odd2.py
numbers = range(30000000)
numbers = [num for num in numbers if not num % 2]

И далее выпонение:
time python odd1.py 

real	0m4.704s
user	0m4.228s
sys	0m0.388s

и

time python odd2.py 

real	0m5.122s
user	0m4.708s
sys	0m0.376s

НЛО прилетело и опубликовало эту надпись здесь
Да, именно спортивный интерес, не болеее. Эти, так сказать, эмпирические данные могут быть полезны программистам-олимпиадникам, которые используют Python (хотя, как я слышал, по перфомансу очень многих он не устраивает). & 1 — соглашусь, не особо очевидно.
По поводу x.reverse() есть одна опасность, которая по невнимательности может насолить. reverse() является методом списка и изменяет сам список, а не только возвращает перевёрнутую копию.
Кроме того, есть ещё built-in функция reversed, которая возвращает итератор, идущий в обратном направлении по тому же списку. Были ещё всякие reviter, ireverse, inreverse, но сейчас ими никто не пользуется вроде.
> и изменяет сам список, а не только возвращает перевёрнутую копию.

Не возвращает он ничего. Возвращает функция reversed(), а не метод списка.
Ну вот, ещё того хуже.
Частный случай x[::-1] является средством выражения x.reverse()

Совсем разные вещи получаются.

Хотя, с другой стороны, так даже стройнее: одно для изменения на месте, одно для изменённой копии, одно для итератора — никаких пересечений и дублирования.
reversed и срезы не то чтобы пересекаются — они работают с разными «интерфейсами» объекта.

Срез работает через метод __getitem__() объекта, причем этот метод должен уметь принимать экземпляр slice в качестве параметра.
reversed() работает только с объектами, которые имеют и __getitem__() и __len__(), но зато reversed() работает поэлементно и делает это лениво (итератор). При этом reversed() вызывает __getitem__() с целочисленным аргументом.

Таким образом, reversed(), в общем случае, предполагает, что источником данных для него будет объект-контейнер с произвольным доступом к содержимому.
А срез можно реализовать и для объектов, представляющих потоковые данные — через пропуски ненужных элементов. Правда без буферизации не будут работать отрицательные шаги и срез от конца. Так работает itertools.islice — итератор-срез от итерируемого источника

Ну и reversed()/срезы, это, так сказать, функциональный подход — результат отвязан от источника.
А reverse(), это ООП подход — метод объекта, изменяющий его состояние.
«Вкратце этот дизайн иногда встречается.»
«Использование многоточие для получения всех элементов, является нарушением принципа „Только Один Путь Достижения Цели“.»
«Хотя в какой то момент оно идет слишком далеко.»
Ад.
Лучше прочитать оригинал на нормальном английском языке. Переводчик не старался.
А ещё, для выделения подразделов предпочтительнее использовать теги <h2>, <h3>, <h4> вместо списка <ul> с одним элементом.
Существует ряд других магических методов.
кто-нибудь объясните, почему

>>> from datetime import date, timedelta

>>> 1 + 1
2

>>> date(2012, 01, 01) + timedelta(days=1)
datetime.date(2012, 1, 2)

но
>>> range(0, 3, 1)
[0, 1, 2]

>>> range(date(2012,01,01), date(2012,01,03), timedelta(days=1))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: range() integer end argument expected, got datetime.date.
А как связана range() с магическими методами?

Касательно магии сложения/вычитания: обычно операции сложения/вычитания работают над объектами одного типа (если для их класса реализовано соответствующее поведение), а datetime/timedelta, это скорее исключение: нельзя сложить две даты — это лишено смысла, но можно изменить дату на некоторое кол-во фиксированных единиц (дней/часов/минут...) и нельзя увеличить дату на n месяцев/лет — кол-во дней плавает.

range() работает только с числами и «магию» не использует (за пределами операций над числами).
При этом её аргумент «шаг» — необязателен. А как тогда как ф-ция должна определять, чем инкрементить дату?
Можно написать всеядную «магическую» версию range() (я напишу «ленивый» вариант):
def range_(from_, to_, step):
    while from_ < to_:
        yield from_
        from_ += step

Этот вариант работать будет для дат/времени (да и для списков, скажем), но потребует обязательного указания объекта-инкремента. Такое поведение неявно, ИМХО.
просто у меня сложилось впечатление, что build-in функции в python, в основном, обобщенные: len(), min(), max()… — а специальные живут в отдельных модулях. кстати:

>>> from datetime import date, timedelta
>>> min(date(2012,01,01), date(2012, 01, 02))
datetime.date(2012, 1, 1)

>>> min(timedelta(days=3), timedelta(days=2))
datetime.timedelta(2)

а range([start], stop, [step]) какой-то очень специфичный. не понятно, только — зачем? ведь, как вы указали, обобщенный вариант реализуется тривиально.
len/min/max — это не функции даже, это обёртки над вызовом __len__/__lt__/__gt__ (ну может min/max посложнее чуток — там на входе итерируемый источник)
Т.е. len() возвращает длину объекта, который может её предоставить, min()/max() сравнивают сравнимые объекты, а их sort() сортирует, int()/str() приводит к целому/строке те объекты, которые сами могут представлять себя целым/строкой. Всё логично.

А range() именно такой, потому что имеет [start] и [step] — необязательные параметры, и такая сигнатура в общем случае не позволяет определить начальный элемент, и уж тем более шаг! Поэтому поддерживаемый типы аргументов ограничены числами, и начало/шаг имеют значения по-умолчанию. Ну и range() чаще всего используется в качестве источника индексов для прохода по индексируемому контейнеру, а тут как раз целые числа используются в качестве индекса, и начальный элемент контейнера обычно имеет индекс «0».

Если бы можно было ввести такие маг.методы:
__min__ — минимальный элемент данного типа
__max__ — максимальный элемент данного типа
__succ__(__pred__) — следующий(предыдущий) за текущим объектом элемент данного типа
можно было-бы реализовать range более всеядным — по-умолчанию отсчет шел бы от __min__, а инкремент происходил бы через __succ__. Всё это реализуемо, но встроенные типы данных — нерасширяемы, поэтому смысла особого нет. Проще под ситуацию написать свой my_range(), c датой и дельтами :)

P.S. Ну и в конце концов, в документации к range() указано, что он работает с числами — всё прозрачно.
В целом, «магические методы» — это в первую очередь Методы, и уж во вторую — «магические». И как любые методы — должны быть определены для объекта где-то в его иерархии наследования: «a + b» это, всего навсего, «a.__add__(b)», только с некоторой долей синтаксического сахара, отсюда и вся «волшебность». И для datetime определен метод __add__, который может принимать объект timedelta в качестве аргумента.

А range() — обычная функция, реализованная с поддержкой чисел и ничего кроме. При этом ф-ция имеет проверку на вызов для других типов данных — и возбуждает исключение, причем вполне конкретное, с объяснением причины!
Таких статей уже было миллион. Перевод очень плохой.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории