Какой специальный метод должен определить объект, чтобы bytes(obj) получил его собственное байтовое представление, а не использовал другие варианты протокола преобразования?
Объект должен определить специальный метод __bytes__. При вызове bytes(obj) Python обращается к этому протоколу и ожидает, что метод вернёт экземпляр bytes; наличие только __str__ для такого преобразования недостаточно.
В Python 3 текст и двоичные данные разделены: str хранит текст, а bytes — последовательность байтов. Поэтому преобразование текста в байты требует явной кодировки, а пользовательский объект должен явно описать собственное байтовое представление.
Специальный метод __bytes__ отделяет это представление от строкового вида объекта. __str__ предназначен для человекочитаемого текста и не определяет, как этот текст кодируется в байты.
Если у объекта есть только __str__, вызов bytes(obj) не обязан сначала получить строку, а затем закодировать её. В зависимости от типа и реализованных протоколов Python может рассматривать аргумент как целое число или итерируемую последовательность чисел; при неподходящем объекте возникнет TypeError.
Неверная реализация __bytes__ тоже приводит к ошибке: метод обязан вернуть именно bytes, а не str, bytearray или произвольный объект. Кроме того, метод должен однозначно определить формат и кодировку результата.
При вызове bytes(obj) Python ищет специальный метод __bytes__ через механизм специальных методов типа объекта. Если он найден, Python вызывает его и проверяет результат. Корректный результат — объект типа bytes.
Здесь __str__ задаёт текстовое представление, а __bytes__ — двоичное. Вызов bytes(user) не выполняет неявное преобразование через __str__; байтовое представление создаётся только явно реализованным __bytes__.
Если объектом является строка, обычно следует использовать encode с явно выбранной кодировкой: text.encode("utf-8"). Для объекта, который является последовательностью чисел, bytes(obj) может интерпретировать элементы как значения байтов в диапазоне от 0 до 255; это другой сценарий и не заменяет __bytes__.
Реализация должна учитывать совместимость формата. Изменение кодировки или структуры байтов может нарушить сетевой протокол, сериализацию, контрольные суммы и обратную совместимость. Если байтовое представление не имеет устойчивого смысла, лучше не добавлять __bytes__, а предоставить явно названный метод с параметрами формата или кодировки.
Класс описывает пакет протокола и уже имеет __str__ для журналирования. Разработчик пытается передать экземпляр в сетевой сокет через bytes(packet), но получает ошибку, потому что строковое представление не является байтовым протоколом.
Можно было бы вызывать str(packet).encode(...) в каждом месте отправки. Такой вариант прост, но дублирует знание о формате и легко приводит к разным кодировкам. Можно также сделать отдельный метод serialize, что лучше подходит для сложного протокола, но не интегрируется с универсальным вызовом bytes.
Если у пакета есть одно естественное и однозначное байтовое представление, целесообразно реализовать __bytes__, а внутри централизовать сериализацию. Тогда bytes(packet) работает единообразно, а __str__ остаётся независимым инструментом диагностики. Для нескольких форматов следует оставить явные методы вроде сериализации в конкретный формат, не перегружая __bytes__ скрытым выбором.
Вызывает ли bytes(obj) автоматически str(obj) перед преобразованием?
Нет. bytes(obj) не использует __str__ как универсальный промежуточный этап. Для пользовательского байтового представления нужен __bytes__; для обычного текста применяется явный вызов encode.
Что произойдёт, если __bytes__ вернёт bytearray?
Python завершит вызов ошибкой TypeError, поскольку контракт __bytes__ требует именно объект bytes. Даже если bytearray содержит те же байты, автоматическая замена типа нарушила бы однозначность результата протокола.
Можно ли передавать кодировку в bytes(obj) для управления __bytes__?
Нет, bytes(obj) не предоставляет параметр кодировки для пользовательского __bytes__. Если объект представляет текст и поддерживает разные кодировки, следует использовать явный метод с параметром или сначала получить строку и вызвать у неё encode с нужной кодировкой.