Pull to refresh

Comments 59

мне очень нравится питон синтаксисом, но именование ______________name_____________ выглядит просто вырвиглазно.

хотя, наверное, для питонщиков, это стало привычкой.
Ну, во-первых, подчёркивания там максимум по 2 с каждой стороны, что вполне читаемо. А во-вторых, сравните это с кучей ключевых слов и специального синтаксиса типа private, operator, Constructor() и т.д. в других языках ;)
Читаемо, не спорю. Но не красиво. Вырывается из стиля :-)

class, def, return, except, print — присуствуют и смотрятся ведь красиво. Так что нет ничего страшного в ключевых словах, кроме их количества.

Давайте пофантазируем.
from os.path import join

class FileObject:
    def __init__(self, filepath='~', filename='sample.txt'):
        self.file = open(join(filepath, filename), 'r+')

    def __del__(self):
        self.file.close()
        del self.file
например в
from os.path import join

class FileObject:
    def init.core(self, filepath = '~', filename = 'sample.txt'):
        self.file = open(join(filepath, filename), 'r+')

    def del.core(self):
        self.file.close()
        del self.file


согласитесь, выглядет по-гламурнее? :-) (надеюсь в существующем питоне нет возможнсти def parent.child(args))
Пара минусов del.core/init.core по сравнению с __del__/__init__:

1) del.core читается «удалить ядро», init.core — «инициализировать ядро». В __del__ таких лишних смыслов нет.
2) в синтаксис языка добавляется новая сущность — точка в объявлении метода. Это усложнение, которое, к тому же, ко всяким неоднозначностям ведет:

class Foo:
    def core(self):
        print('Foo')

class FileObject:
    def del.core(self):
        print('FileObject')

    def del(self):
        return Foo()


и теперь вызываем file_object.del.core() — что будет напечатано?

__магические__ методы решают вполне осязаемые практические проблемы простым (и imho красивым) способом, зачем что-то усложнять? Переименование __next__ в next.core никаких практических проблем не решает.
согласен с вашими аргументами, ниже ответил anjensan.
Ваш вариат не работает. Вызвать x.__init__(x) можно, а вот x.init.core(x) — уже нет, нету способа отличить это от 2х независимых вызовов.

На самом деле, это достаточно распространенная практика во многих языках — использовать знак(и) подчеркивания для обозначения приватных или «магических» вещей. Могли бы конечно выделять специальные методы только с одной стороны (было бы __init, __del и т.д.). Но это, на мой взгляд, менее красиво, да и выделение с одной стороны уже используется для name mangling.

Попробуйте предложить рабочую альтертаниву такому решению. Уверен, что однозначного «победителя» не найдется.
Что ж, у вас железные аргументы. Асло, "__" мне не нравится и в других языках. Исключая бинарные операции, программирование символами псевдографики — это истинное брэйнфакство. Архаизм. Имхоу. От этого не уйдешь, потому что удобно, но "__" в именах — злоупотребление, мне кажется.

На счет рабочих вариантов — не приходит на ум ничего оригинального.
Mожет быть x.magic.init(x) и def magic.init(x):. «magic» — неймспейс для доступа к магии.
Исключая бинарные операции, программирование символами псевдографики — это истинное брэйнфакство.


* — вместо «всё»
import com.package.*
[] — для обращения к элементам массива
items[5]
= — для обозначения присваивания:
x = 1
ну и т.д.

Что касается конкретно символов подчёркивания в имени, чем они хуже точки или CamelCase? Это вопрос привычки, и когда привыкаете, понимаете, что это действительно удобно, и вы уже не хотите ничего другого.
Уж что-что, а Питон это просто оазис красоты, вкуса, стиля и изящества среди языков, протоколов, систем разметки и т. д., созданных людьми с психикой, навсегда травмированной консольными интерфейсами.
Вырвиглазность повышает читаемость кода и позволяет улучшать язык обратно-совместимым способом.

Если чуть подробнее, этот стиль наименования:

1. позволяет вводить в язык новые магические методы без риска сломать старый код. Программисты на питоне в своем коде названия __name__ не используют (по соглашению, да и сложно так назвать что-то случайно), а значит новый магический метод ничего не сломает. Без подобного соглашения нужно быть телепатом, чтоб писать код, который не сломается в будущих версиях интерпретатора. Ср., например, с toJSON в javascript — когда в браузерах поддержка этого метода появилась, куча кода перестала работать.

2. при чтении кода сразу видно, что метод магический, и вызываться он, скорее всего, будет неявно. Вызовы остальных методов обычно можно грепом найти. Кстати, название магического метода часто подсказывает, как он может вызываться: если метод называется __foo__, то во многих случаях он будет вызван с помощью функции foo(obj). Например, функции getattr, setattr, len, iter, next, reversed, copy, deepcopy, hash вызывают соответствующие __методы__.
Бывают и менее очевидные вызовы магических методов. Например, сложение вызывается не только для оператора +, но и для встроенной функции sum.
class N():
    def __init__(self, n):
        self.n = n

    def __add__(self, other):
        return N(self.n + other.n)

    def __repr__(self):
        return '<N: %s>' % self.n

print sum([N(1), N(2), N(3)], N(0))

Выводит <N: 6>
Или вот ещё __nonzero__. Вызывается не только когда явно укажешь bool(object), но и просто if obj:
В таком случае bool() применяется к объекту «автоматически».
Добавил в табличку ваш пример с if. Но, на самом деле, в обоих ваших примерах было бы странно ожидать от Питона какого-нибудь другого поведения, мне кажется :)
Согласен, это логично. Ещё пара примеров (сами в голову приходят, уж извините :о))
all(iterable) и any(iterable) тоже вызывают bool неявным образом.
С одной стороны, об этом тоже можно догадаться внимательно посмотрев в документацию, а с другой, лучше помнить заранее, чтобы не писать своих хитроумных алгоритмов для проверки истинности «всех», или «хотя бы одного» объекта из списка, когда достаточно определить один простой магический метод и пользоваться встроенными возможностями.
Не подскажите как правильно написать _iadd__(self, other)? Я просто слегка не понял этого
Ну все, можно смело положить в избранное. Материал обязательно пригодится, когда надо срочно что-то заставить работать по-другомупереопределить
Огромнейшее спасибо. Однозначно в избранное. Я бы даже сделал в виде pdf.
Можно добавить еще про новый (с 3 версии питона) магический метод получения следующего значения у итераторов, .__next__(), который принят на вооружение взамен старому .next(). При этом, правильный метод в зависимости от версии питона, отлично вызывается встроенной функцией next().
см. PEP 3114
__hash__ используется не только для поиска по словарю, но и для проверки вхождения в множество (set()). Хотя, некоторые представляют себе множество словарём без значений… Технически это может и имеет смысл, но как-то совсем не интуитивно.
__hash__ это просто дуаль встроенной функции hash, а уж кто и как ее использует — дело второе.
Нет. Значение, возвращаемое __hash__ для экземпляра класса, является значением, используемым для получения хэша объекта.

class A(object):
    def __hash__(self):
        return 10

a = A()


Вызов hash(a) вызывает __hash__ у объекта a.

>>> class A(object):
...     def __hash__(self):
...             print "i'm called, you are wrong"
...             return 10
... 
>>> a = A()
>>> v = {a: 10}
i'm called, you are wrong
5 раз перечитал ваш комментарий, не понял, почему «нет». Если бы стандартные словари и множества были реализованы на самом Питоне, а не внутри интерпретатора, там бы использовалась ф-я hash. Любой может сделать свой контейнер и использовать там ф-ю hash, но это не повод писать "__hash__ используется не только для поиска по словарю и для проверки вхождения в множество, но и вот в этом моем контейнере".
1) Контейнер не мой, это просто словарь.
2) Так она и используется — но функция hash всего-лишь вызывает __hash__ у объекта. Это не дуаль потому, что hash — это syntactic sugar, а __hash__ *на самом деле* вычисляет значения хэша.
> __hash__ *на самом деле* вычисляет значения хэша
__hash__ вычисляет значение, из которого вычисляется хэш.
set — это встроенная функция и узнать, что она тоже использует hash можно либо добравшись до исходников на C, либо потеряв некоторое время, пытаясь понять, почему даже при определение __eq__ равные объекты внутри множества не считаются равными. Поэтому я посчитал полезным добавить это замечание.
Не совсем.

>>> class X(object):
...     def __hash__(self):
...         return 111111111111111111111111111111111111111111111
...         
>>> x = X()
>>> hash(x)
891475464178347604

Слишком длинное число, хэш должен помещаться в целое. Он берёт hash от hash'а.
Про __del__ неправильно написано. Метод будет вызван если интертретатор завершает работу.
Другое дело что он не вызовется сборщиком мусора (объекты с __del__ сборщик мусора до версии 3.4 не умеет обрабатывать вообще).
__del__ работает только если произошел decref, а не garbage collection.
Хм, в документации написано:

«It is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits.»

Неужели я неправильно перевёл такое, вроде бы несложное предложение?
Документация не совсем точна. Следующий параграф Note более внятно описывает что именно происходит
Но и там я так и не нашёл утверждения, что вызов __del__ гарантирован :(

Сейчас потестил и всё говорит за то, что вы правы и __del__ на самом деле всегда вызывается по завершению интерпретатора. Добавлю замечание.
Скорее всего, это относится к общим гарантиям языка, которые не зависят от реализации. Например, это будет справедливо в Jython, IronPython, или другой реализации, не использующей подсчет ссылок, и основанной на стороннем GC, не гарантирующим финализацию объектов при выходе процесса. Как написано в самом начале этой же страницы документации:

«Objects are never explicitly destroyed; however, when they become unreachable they may be garbage-collected. An implementation is allowed to postpone garbage collection or omit it altogether — it is a matter of implementation quality how garbage collection is implemented, as long as no objects are collected that are still reachable.»
Однако, следует пояснить, что это справедливо только для объектов с циклическими зависимостями.
Объекты, у которых есть метод __del__, но без циклических ссылок, удаляются сразу после удаления последней ссылки на него. Метод __del__ у них вызывается сразу.
> На самом деле, экземпляр объекта создаёт метод __new__ и затем передаёт аргументы в инициализатор.
Немного не так. __new__ не передает аргументы в __init__. __init__ вызывается интерпретатором, если __new__ вернул объект того класса. Причем, если вы вернули объект, уже созданный до этого и сохраненный где-то (например, это singleton), то __init__ все равно будет вызван.
Спасибо за комментарий, действительно не совсем правильно получилось. Изменил предложение на:
На самом деле, экземпляр объекта создаёт метод __new__, а затем аргументы передаются в инициализатор.
Я не уверен, что можно называть __new__ методом, поскольку объекта в этот момент ещё не существует. Это скорее classmethod.
У меня возникло ощущение, что писать ОО-код на питоне будет порядком мозговзрывательнее, чем, например, на том же С++ (с нормальной IDE с подсказками)
Вы не правы, модель в питоне очень проста, не обязательно использовать все перечисленные изыски, большинство из них для особых ценителей-извращенцев.
Как человек достаточно хорошо знающий и С++ и Python, могу сказать на Python гораздо менее мозговзрывательнее, чем на Це++. Просто с непривычки да, а как втянешься, переписать все на питон останавливает только то, что это все таки транслятор со всеми вытекающими.
Питон в самом деле превосходит Цэ++ по читабельности, но Вы правы в одном — отсутствие типизации в самом деле создает некоторые трудности с подсказками в IDE
Для полной картины не хватает еще и вещей вроде __dict__, __doc__, __ slots__ и т.д. (хоть это и не методы, но статья бы стала почти идеальной)).
Ну, и еще не хватает __prepare__.
Тогда уж можно начать описывать вообще все magic method's, в том числе NumPy'вые.
Статья тупо перевод мануала, было бы клёво если бы показали больше примеров зачем это все нужно. Тот же синглетон на __new__.
Если в первом примере под тильдой в filepath='~' понимается домашняя директория текущего пользователя, то это так не работает — будет попытка открыть файл в директории ./~/, т.к. Python не разворачивает шелл-подстановки (и спасибо ему). Чтобы работало, код нужно написать следующим образом:

    def __init__(self, filepath = '~', filename = 'sample.txt'):
        # открыть файл filename в filepath в режиме чтения и записи
        self.file = open(os.path.expanduser(os.path.join(filepath, filename)), 'r+')


Более того, опасно использовать __del__ так, как это было продемонстрировано, так как __del__ вызывается даже для тех экземпляров класса, для которых не было завершено конструирование. Таким образом, если создать FileObject с неправильным именем файла, open() выбросит IOError, и self.file не будет присвоен — но к нему обратится __del__ и получит вдобавок еще и AttributeError, и последующая деинициализация выполнена не будет, лишая __del__ того смысла, который в него вкладывал автор. Будьте осторожнее.
В Python 3 больше нет метода __cmp__() и функции cmp(), и рекомендуется использовать __lt__/__gt__/__le__/__ge__/__eq__/__ne__ исключительно. Если нет необходимости определять все операторы сравнения, можно определить один оператор сравнения (например, __eq__) и один оператор упорядочения (например, __lt__), и применить к объявлению класса декоратор @functools.total_ordering — он достроит остальные функции на основании имеющихся.
В статье есть всё из того, что вы написали.
Про __metaclass__ нет упоминания, и это, как мне кажется, большое упущение. На хабре даже есть подробная статья, посвященная этой теме: Метаклассы в Python.
Про __dict__, __name__, __doc__ тоже ничего нет, потому что статья про магические методы, а не про всю объектную модель Питона вообще. А метаклассы это вообще отдельная тема.
Электронный текст имеет право быть семантическим, не так ли? Если затронули тему магических методов, то стоит упомянуть и о магических свойствах.

Отдельные темы оформляются отдельными ссылками (на соответствующие статьи).

Не кажется ли вам, что неправильно давать материал в отрыве от контекста? То есть без возможности продолжить изыскания на смежные темы. Уважение к читателю — важная составляющая работы любого автора.
Я всего лишь перевёл статью. А продолжить изыскания всегда можно в гугле.
Зря так часто специализированные методы называют «магическими». В них нет никакой магии, в них — специализация.

Любителям магии и пошалить: смотрите занятие от Бизли с Pycon2013 US, где разбирается работа метаклассов и дескрипторов.
Магическими их называют потому-что обычно на прямую их не вызывают и определить их назначение в коде можно только обладая сакральными знаниями :о)
Ага, тогда, пожалуй, любой метод, который, скажем, обычно вызывают не напрямую, сказочным образом становится магическим ;)
Неее, тут явно не обошлось без неутоленного в детстве желания волшебства.
UFO just landed and posted this here
Всё же немного безопаснее, т.к. при использовании бинарного формата квалификация злоумышленника должна быть выше, чем при обычном текстовом. Хотя, лучше конечно свой Pickler делать, недопускающий подобных трюков.
Спасибо за статью, плюсовать ее поздно, а благодарность выразить хочется, большое спасибо!
Sign up to leave a comment.

Articles

Change theme settings