Pull to refresh

Comments 27

Напрашивается, что использование коллекций противоречит «Простое лучше сложного». Нет?
Добавил в заметки. Лучше периодически подобные статьи просматривать, чтобы не написать того, о чем потом будет стыдно :)
UFO just landed and posted this here
Все что полезно описано в любой книге, а что не описано вызывает смешанные чувства. Например это:
>> Создавая классы в 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

UFO just landed and posted this here
Да, именно спортивный интерес, не болеее. Эти, так сказать, эмпирические данные могут быть полезны программистам-олимпиадникам, которые используют 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() — обычная функция, реализованная с поддержкой чисел и ничего кроме. При этом ф-ция имеет проверку на вызов для других типов данных — и возбуждает исключение, причем вполне конкретное, с объяснением причины!
Таких статей уже было миллион. Перевод очень плохой.
Sign up to leave a comment.

Articles