Pull to refresh

Comments 31

А я вот никогда не хотел так делать, попахивает javascript'ом.
И с точки зрения питона, len(что-то) и обращение к элементу словаря через [] правильное.
В виде примера для len, можно подумать про яйца в корзине. Корзина — список, внутри нее объекты (яйца), чтобы узнать количество яиц в корзине, вы же не пинаете корзину и не спрашиваете: сколько у тебя там яиц? Вы начинаете считать самостоятельно, что и делает len и такой подход.
Мне наоборот больше нравится подход Smalltalk-а: если вам что-то надо узнать об объекте, спросите его (вызовите метод). Вы же не используете функцию replace для замены текста в string-ах, вы используете метод replace.
какие яйца? «длина списка» это свойство списка.
Подобная замета методов у меня ассоциируется с #define true false и в этом случае действительно «Удачи в освоении магии Python».
Но за Proof of concept — однозначно плюсик.
В моей реализации метода type.__setattr__ Я как раз убрал возможность заменять уже существующие методы. Основное предназначение модуля — добавление новых атрибутов в классы. В частности можно добавить метод map и filter в классы list, tuple, dict.
А как дело обстоит с PyPy? Там ведь, наверное, ничего править не надо и можно сразу динамически менять методы?
Да ровно тоже самое )

[e-max@e-max pypy]$ ./pypy-c
Python 2.7.2 (e4568fc96f21+, Apr 11 2012, 07:59:26)
[PyPy 1.8.1-dev0 with GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``pypy is like sausages''
>>>> str.len = property(lambda self: len(self))
Traceback (most recent call last):
File "", line 1, in TypeError: can't set attributes on type object 'str'
С первых же строчек
«Вам когда-нибудь хотелось добавить поле в класс dict? Вы мечтаете написать action.name.len() вместо len(action.name)?»
хотите такой гибкости начните программировать на Ruby

P.S. сам в данный момент пишу на питоне
Я на Ruby не программировал. Но когда изучал Smalltalk мне понравилась идея, что можно посмотреть реализацию любого класса и подправить код на лету: добавить метод, вставить breakpoint, добавить отладочный вывод.
Конечно так можно и в ногу себе выстрелить. Но в Smalltalk-е это нормальное поведение системы. Например, в стандартной поставке Squeak Smalltalk нет классов для работы с RegExp-ами. Поддержка регулярок устанавливается отдельно. И после добавления RegExp-ов в классе String появляются методы matchesRegex:, matchesRegexIgnoringCase: и другие.
Оказалось, что нет особых преград для модифицирования Python-классов на лету даже для встроенных типов.
Мне кажется такой код понятней, адекватней и правильней. Главное все будут вкурсе что и как работает и продакшн не будет страдать от левых непонятных модулей.
class a(dict):
    def count(self):
        return len(self)
aa = a()
aa // {}
aa[1] = 1
aa[2] = 2
aa.count() // 2
Вы, наверное, под
aa // {}
имели в виду
aa # {}
, ибо у меня заняло порядка минуты понять, что речь идет не о переопределении операции целочисленного деления.
=) Да, Вы правы, я не питошщик просто и провтыкал. Я к тому что костыли это плохо, когда есть стандартные средства «похожие» на задачу.
Понятно, что это статья не для практики, а чтоб разобраться, как все работает. Но т.к. и правда часто люди ругаются — почему такие страшные названия у магических методов (__init__, __len__), почему len(my_list), а не my_list.length(), попробую объяснить, чем это лучше)

Представьте, что у списка есть метод length. И у словаря есть такой метод. И т.д. Тогда, если мы захотим заставить свой класс «крякать» как список или словарь, нам, видимо, тоже нужно будет реализовать метод length. Из этого следует, что «length» становится почти зарезервированным атрибутом; иметь свойство length у своего объекта часто будет ошибкой, т.к. объект начнет «крякать» как список.

Если в предыдущем абзаце заменить length на __len__, то почти ничего не изменится. Это такое же «магическое» свойство, только с другим названием. Но пара отличий все же есть. Отличие 1, самое очевидное: сложно случайно назвать атрибут у объекта __len__ (тогда как length — вполне естественно). Отличие 2, вытекающее из первого: в том, что все атрибуты, начинающиеся с __, считаются зарезервированными (и предполагается, что их нет в пользовательском коде), есть большой плюс, касающийся разработки самого языка: можно вводить новые магические методы без опасения сломать старый код.

Представьте, что магические методы назывались бы без __подчеркиваний__. Выходит питон 2.5, в нем появляются контекст-менеджеры. Чтоб объект работал как контекст-менеджер, он должен реализовать несложный протокол — иметь методы __enter__ и __exit__. Без соглашения с __, если назвать методы enter и exit, мы, возможно, неявно сломаем весь код, в котором у объектов есть атрибуты enter или exit. С __наименованиями__ же появляется надежный механизм ввода новых протоколов в язык без поломки обратной совместимости. Понятное дело, не важно, какое именно соглашение принять, но совсем без соглашения (если каждый раз выдумывать удобные человекопонятные имена магических методов вроде length) всегда будет риск сломать работающий код (а разработчик ничего сделать с этим не сможет, т.к. не знает, как в будущем магический метод назовут, если соглашения нет).

Иногда на грабли эти, впрочем, наступают — во втором питоне у итераторов магический метод назван «next». В третьем поправили, сделали функцию next (которая ожидает объект, поддерживающий протокол итерации) и метод __next__ (который нужно реализовать, чтоб объект стал поддерживать протокол итерации).
Во 2 пункте смысл есть, согласен. Но не уверен, нужна ли такая защита ценой уродства именований.

А вот по поводу 1-го не согласен совсем:

«если мы захотим заставить свой класс «крякать» как список или словарь, нам, видимо, тоже нужно будет реализовать метод length» — логично, и в этом нет ничего плохого. Сейчас вы реализуете метод __len__.

«иметь свойство length у своего объекта часто будет ошибкой, т.к. объект начнет «крякать» как список» — с чего это он станет крякать именно как список от наличия метода length? чем тогда уж хэш или строка хуже?

Наличия метода length означает только лишь то, что у объекта есть некоторая «длина». И если кто-то на основе факта наличия длины делает вывод о том, что это список — то это не проблема языка. Ту же логику этот странный человек может применить и к методу __len__.
По-моему, уродства тут нет) Интерфейсы-то остаются вполне чистыми: next(my_iterator) — в них немного меньше от ООП, немного больше от ФП. Да и человек, читающий код, не обязан держать в голове все десятки названий магических методов, чтоб понять, является ли метод просто методом или он нужен для реализации какого-то питоньего протокола (__init__ используется часто, остальные магические методы в нормальном коде используются гораздо реже, а «Special cases aren't special enough to break the rules.»)

А с чем не согласны?)

«если мы захотим заставить свой класс «крякать» как список или словарь, нам, видимо, тоже нужно будет реализовать метод length» — логично, и в этом нет ничего плохого. Сейчас вы реализуете метод __len__.


Так я же и написал, что почти ничего не меняется с заменой length на __len__)

«иметь свойство length у своего объекта часто будет ошибкой, т.к. объект начнет «крякать» как список» — с чего это он станет крякать именно как список от наличия метода length? чем тогда уж хэш или строка хуже?


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

Наличия метода length означает только лишь то, что у объекта есть некоторая «длина». И если кто-то на основе факта наличия длины делает вывод о том, что это список — то это не проблема языка. Ту же логику этот странный человек может применить и к методу __len__.


Логику применить, понятно, можно, см. выше. Но вывод-то делает не «странный человек», читающий код, а интерпретатор питона. В случае с длиной ошибки при этом будут не очень частые (некоторый код может не упасть с исключением, когда нужно). В случае с другими магическими методами все может быть хуже — можно поломать кучу всего, случайно переопределяя магические методы, втч очень неявно (добавление элемента в словарь сломать — пожалуйста, __hash__; ошибка в случайном месте кода (когда сборщику мусора в голову взбредет) — __del__ к нашим услугам).

Я, впрочем, согласен, что п.2 тут гораздо важнее п.1.
По-моему, уродства тут нет

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

next(my_iterator) — в них немного меньше от ООП, немного больше от ФП

Скорее больше от процедурного стиля. Какое же тут ФП, когда next меняет состояние своего аргумента (что и в ООП не очень хорошо с моей точки зрения).

для реализации какого-то питоньего протокола

А свои протоколы программист не может использовать? Для них тоже принято объявлять такие имена методов?

он начнет в отношении длины крякать как список, словарь или строка и любой другой объект, имеющий длину

И разве это плохо?

Но вывод-то делает не «странный человек», читающий код, а интерпретатор питона.

Интерпретатор питона делает вывод о том, что объект — список или хеш на основе наличия у него метода __len__? Что-то я сильно сомневаюсь в этом. Мне кажется, что он опять же может делать вывод только лишь о том, что объект обладает длиной. И неужели это как-то специально учитывается им в дальнейшем?
Наличие двух подчеркиваний может положительно сказываться на читабельности кода, т.к. явно говорит, что метод имеет особый смысл и может вызываться неявно конструкциями языка (чтоб увидеть это, не нужно держать в голове все названия магических методов).

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

«Интерпретатор питона делает вывод о том, что объект — список или хеш на основе наличия у него метода __len__?» — да откуда вы это берете, не писал я такого) Коду не нужно обычно делать никаких выводов о типе; списки и словари — встроенные типы, которые поддерживают различные протоколы (втч __len__); пользовательские типы тоже могут эти протоколы поддерживать.

То, что объект начнет реагировать на len при добавлении length — может быть как хорошо, так и не очень. В реальной жизни — скорее всего, нормально. Не в этом дело. А дело в том, что нехорошо, если объект вдруг начнет реагировать на del или hash или new или еще что-то новое, что придумают в следующей версии питона. От этого __названия__ помогают.
Другие языки как-то с этим справляются =) Чем питон хуже? В том же .net есть метод count у многих классов для работы с данными. И опять же, свойство length есть в JavaScript и у строк и у массивов. И нормально, уживаемся. Все довольны.
Javascript, говорите, все довольны. Вспомните, сколько библиотек сломалось, когда нативный .toJSON в браузерах появился.
В ие нету метода indexOf у массива до 9 версии, но у меня она прекрасно работает во всех браузерах, а так же метод isArray тоже в ие меньше 9 не работает, но у меня, как ни странно работает. А знаете почему?
if( undefined === [].indexOf )
{
    Array.prototype.ondexOf = function(){...}
}
if( undefined === Array.isArray )
{
    Array.isArray = function(){...}
}

Ко всему с умом нужно подходить, а не в лоб долбить.
Я прекрасно знаю, как это обходится, и как с toJSON проблему решили (точно так же).

Это не отменяет того факта, что проблема есть (иначе зачем эти костыли), решают ее в основном постфактум (когда все уже сломалось), оставляя код, работающий на предыдущих версиях, сломанным. Пример: js от iframe-приложений вконтакте еще вроде в прошлом году использовал библиотеку (не помню, какую), которая в итоге переопределяла метод toJSON. И из-за этого мутулз не работал с iframe-приложениями вконтакте, даже несмотря на то, что в самом мутулз проверка на undefined была.

Чтоб обезопасить себя и делать надежные библиотеки, нужно, получается, или обладать телепатическими способностями и не переопределять методы, которые потом войдут в стандарт, или каждый метод проверять на undefined и молиться, что нативная версия будет делать то же самое, что и наша (== тоже телепатические способности). Пример — Function.bind из мутулз: нативная версия имеет другой интерфейс, нежели та, которая в мутулз реализована была, и никакие проверки на undefined тут не помогут опять, код починить можно только постфактум, старые версии оставляя сломанными.

И это все при том, что в питоне это было бы такой уж и большой проблемой (мы там обычно имеем контроль как над интерпретатором, так и над кодом). В js проблема серьезнее, т.к. интерпретатор обновляется постоянно, и контроля над ним разработчик не имеет. Из-за этого имеет чертову кучу сломанного js на старых сайтах, разные неработающие демки и тд.
давно мечтал str.__call__ = str.__mod__ но увы, я не знаю, как скомпилировать *.c файл в моей ОС
Метод далеко не всегда работает.
Для magic methods чаще идет вызов не через getattr, а напрямую посредством tp_. И тут ваше переопределение ломается замечательно непредсказуемым образом.
Да, изменения, которые мой хак делает в __dict__, не отображаются на значениях tp_* полей класса. Поэтому Я реализовал лишь добавление новых аттрибутов.
В Python 3 интерфейс поменяли (http://docs.python.org/py3k/extending/extending.html) но мы сделаем всё равно! Спасибо за интересную идею и реализацию на 2.7! Works like a charm. Теперь join, наконец, будет правильный :)

P.S. Если у кого нет компилятора, то модуль, построенный из вышеприведенного гуглокода для py-2.7/win-32, можно временно взять здесь.
UFO just landed and posted this here
Во-первых, то что вы написали будет продолжать работать. Во-вторых, я лично генераторы редко использую в своих программах, в 99% случаев хватает нормальных списков и циклов. К счастью, мне не надо встаиваться в чужие фреймворки и стандарты.
UFO just landed and posted this here
Логично, но Питон не развивается логичными путями, увы. Нет в мире совершенства.

Фреймворки же при том, что там могут быть свои правила и стандарты, и если, скажем, где-то сплошь и рядом итераторы, которые приходится join восемь часов в день, то да, ничего не с этим поделаешь.
UFO just landed and posted this here
Добавил реализацию для питона-3.2. Копилефт и всё такое.

Python 3.2.2 (default, Sep 4 2011, 09:51:08) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import typehack
>>> list.join = lambda x,s:s.join(x)
>>> ["123","456","7890"].join('-')
'123-456-7890'
Sign up to leave a comment.

Articles

Change theme settings