Comments 59
мне очень нравится питон синтаксисом, но именование ______________name_____________ выглядит просто вырвиглазно.
хотя, наверное, для питонщиков, это стало привычкой.
хотя, наверное, для питонщиков, это стало привычкой.
Ну, во-первых, подчёркивания там максимум по 2 с каждой стороны, что вполне читаемо. А во-вторых, сравните это с кучей ключевых слов и специального синтаксиса типа private, operator, Constructor() и т.д. в других языках ;)
Читаемо, не спорю. Но не красиво. Вырывается из стиля :-)
class, def, return, except, print — присуствуют и смотрятся ведь красиво. Так что нет ничего страшного в ключевых словах, кроме их количества.
Давайте пофантазируем.
согласитесь, выглядет по-гламурнее? :-) (надеюсь в существующем питоне нет возможнсти
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) в синтаксис языка добавляется новая сущность — точка в объявлении метода. Это усложнение, которое, к тому же, ко всяким неоднозначностям ведет:
и теперь вызываем file_object.del.core() — что будет напечатано?
__магические__ методы решают вполне осязаемые практические проблемы простым (и imho красивым) способом, зачем что-то усложнять? Переименование __next__ в next.core никаких практических проблем не решает.
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 никаких практических проблем не решает.
Ваш вариат не работает. Вызвать
На самом деле, это достаточно распространенная практика во многих языках — использовать знак(и) подчеркивания для обозначения приватных или «магических» вещей. Могли бы конечно выделять специальные методы только с одной стороны (было бы
Попробуйте предложить рабочую альтертаниву такому решению. Уверен, что однозначного «победителя» не найдется.
x.__init__(x)
можно, а вот x.init.core(x)
— уже нет, нету способа отличить это от 2х независимых вызовов.На самом деле, это достаточно распространенная практика во многих языках — использовать знак(и) подчеркивания для обозначения приватных или «магических» вещей. Могли бы конечно выделять специальные методы только с одной стороны (было бы
__init
, __del
и т.д.). Но это, на мой взгляд, менее красиво, да и выделение с одной стороны уже используется для name mangling.Попробуйте предложить рабочую альтертаниву такому решению. Уверен, что однозначного «победителя» не найдется.
Что ж, у вас железные аргументы. Асло, "__" мне не нравится и в других языках. Исключая бинарные операции, программирование символами псевдографики — это истинное брэйнфакство. Архаизм. Имхоу. От этого не уйдешь, потому что удобно, но "__" в именах — злоупотребление, мне кажется.
На счет рабочих вариантов — не приходит на ум ничего оригинального.
Mожет быть x.magic.init(x) и def magic.init(x):. «magic» — неймспейс для доступа к магии.
На счет рабочих вариантов — не приходит на ум ничего оригинального.
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 вызывают соответствующие __методы__.
Если чуть подробнее, этот стиль наименования:
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 версии питона) магический метод получения следующего значения у итераторов,
см. PEP 3114
.__next__()
, который принят на вооружение взамен старому .next()
. При этом, правильный метод в зависимости от версии питона, отлично вызывается встроенной функцией next().см. PEP 3114
__hash__
используется не только для поиска по словарю, но и для проверки вхождения в множество (set()
). Хотя, некоторые представляют себе множество словарём без значений… Технически это может и имеет смысл, но как-то совсем не интуитивно.__hash__
это просто дуаль встроенной функции hash
, а уж кто и как ее использует — дело второе.Нет. Значение, возвращаемое __hash__ для экземпляра класса, является значением, используемым для получения хэша объекта.
Вызов hash(a) вызывает __hash__ у объекта a.
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__ *на самом деле* вычисляет значения хэша.
2) Так она и используется — но функция hash всего-лишь вызывает __hash__ у объекта. Это не дуаль потому, что hash — это syntactic sugar, а __hash__ *на самом деле* вычисляет значения хэша.
> __hash__ *на самом деле* вычисляет значения хэша
__hash__ вычисляет значение, из которого вычисляется хэш.
__hash__ вычисляет значение, из которого вычисляется хэш.
__hash__ вычисляет *целое* из которого вычисляется хэш. docs.python.org/2/reference/datamodel.html#object.__hash__. По факту можно использовать и это целое, но реализация CPython хочет хэш в виде числа 32 или 64 битного.
set — это встроенная функция и узнать, что она тоже использует hash можно либо добравшись до исходников на C, либо потеряв некоторое время, пытаясь понять, почему даже при определение __eq__ равные объекты внутри множества не считаются равными. Поэтому я посчитал полезным добавить это замечание.
Не совсем.
>>> class X(object):
... def __hash__(self):
... return 111111111111111111111111111111111111111111111
...
>>> x = X()
>>> hash(x)
891475464178347604
Про __del__ неправильно написано. Метод будет вызван если интертретатор завершает работу.
Другое дело что он не вызовется сборщиком мусора (объекты с __del__ сборщик мусора до версии 3.4 не умеет обрабатывать вообще).
__del__ работает только если произошел decref, а не garbage collection.
Другое дело что он не вызовется сборщиком мусора (объекты с __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.»
Неужели я неправильно перевёл такое, вроде бы несложное предложение?
«It is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits.»
Неужели я неправильно перевёл такое, вроде бы несложное предложение?
Документация не совсем точна. Следующий параграф Note более внятно описывает что именно происходит
Скорее всего, это относится к общим гарантиям языка, которые не зависят от реализации. Например, это будет справедливо в 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.»
«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__ у них вызывается сразу.
Объекты, у которых есть метод __del__, но без циклических ссылок, удаляются сразу после удаления последней ссылки на него. Метод __del__ у них вызывается сразу.
> На самом деле, экземпляр объекта создаёт метод __new__ и затем передаёт аргументы в инициализатор.
Немного не так. __new__ не передает аргументы в __init__. __init__ вызывается интерпретатором, если __new__ вернул объект того класса. Причем, если вы вернули объект, уже созданный до этого и сохраненный где-то (например, это singleton), то __init__ все равно будет вызван.
Немного не так. __new__ не передает аргументы в __init__. __init__ вызывается интерпретатором, если __new__ вернул объект того класса. Причем, если вы вернули объект, уже созданный до этого и сохраненный где-то (например, это singleton), то __init__ все равно будет вызван.
Спасибо за комментарий, действительно не совсем правильно получилось. Изменил предложение на:
На самом деле, экземпляр объекта создаёт метод __new__
, а затем аргументы передаются в инициализатор.
У меня возникло ощущение, что писать ОО-код на питоне будет порядком мозговзрывательнее, чем, например, на том же С++ (с нормальной IDE с подсказками)
Вы не правы, модель в питоне очень проста, не обязательно использовать все перечисленные изыски, большинство из них для особых ценителей-извращенцев.
Как человек достаточно хорошо знающий и С++ и Python, могу сказать на Python гораздо менее мозговзрывательнее, чем на Це++. Просто с непривычки да, а как втянешься, переписать все на питон останавливает только то, что это все таки транслятор со всеми вытекающими.
Питон в самом деле превосходит Цэ++ по читабельности, но Вы правы в одном — отсутствие типизации в самом деле создает некоторые трудности с подсказками в IDE
Для полной картины не хватает еще и вещей вроде __dict__, __doc__, __ slots__ и т.д. (хоть это и не методы, но статья бы стала почти идеальной)).
Ну, и еще не хватает __prepare__.
Ну, и еще не хватает __prepare__.
Статья тупо перевод мануала, было бы клёво если бы показали больше примеров зачем это все нужно. Тот же синглетон на __new__.
Если в первом примере под тильдой в
Более того, опасно использовать __del__ так, как это было продемонстрировано, так как __del__ вызывается даже для тех экземпляров класса, для которых не было завершено конструирование. Таким образом, если создать FileObject с неправильным именем файла, open() выбросит IOError, и self.file не будет присвоен — но к нему обратится __del__ и получит вдобавок еще и AttributeError, и последующая деинициализация выполнена не будет, лишая __del__ того смысла, который в него вкладывал автор. Будьте осторожнее.
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, где разбирается работа метаклассов и дескрипторов.
Любителям магии и пошалить: смотрите занятие от Бизли с Pycon2013 US, где разбирается работа метаклассов и дескрипторов.
Магическими их называют потому-что обычно на прямую их не вызывают и определить их назначение в коде можно только обладая сакральными знаниями
:о)
Всё же немного безопаснее, т.к. при использовании бинарного формата квалификация злоумышленника должна быть выше, чем при обычном текстовом. Хотя, лучше конечно свой Pickler делать, недопускающий подобных трюков.
Есть быстрый и безопасный cerealizer.
Спасибо за статью, плюсовать ее поздно, а благодарность выразить хочется, большое спасибо!
Sign up to leave a comment.
Руководство по магическим методам в Питоне