Итак, когда же лучше всего использовать это функциональное средство
языка Python в своем собственном программном коде?
Мое эмпирическое правило заключается в следующем: если функция не
имеет возвращаемого значения (в других языках такая функция называ-
ется процедурой), то я исключаю инструкцию
return
. Добавлять эту ин-
струкцию было бы лишним и вносило бы путаницу. Примером процедуры
является встроенная в Python функция печати
print
, которая вызывается
100 Глава 3 •
Эффективные функции
только ради ее побочных эффектов (распечатки текста) и никогда — ради
ее возвращаемого значения.
Давайте возьмем функцию, например встроенную в Python функцию
sum
. Она, безусловно, имеет логическое возвращаемое значение, и, как
правило, функция
sum
не вызывается только ради ее побочных эффектов.
Ее цель состоит в том, чтобы подсчитать сумму последовательности чи-
сел и затем представить результат. Итак, если с логической точки зрения
функция действительно имеет возвращаемое значение, то необходимо
решить, использовать неявную инструкцию
return
или нет.
С одной стороны, вы можете утверждать, что исключение явной инструк-
ции
return
None
делает программный код более сжатым и, следовательно,
более легким для чтения и понимания. Субъективно вы отметили бы, что
это делает программный код «симпатичнее».
С другой стороны, некоторые программисты могут удивиться, что Python
ведет себя таким образом. В том, что касается написания чистого и удоб-
ного в сопровождении программного кода, такое непредсказуемое пове-
дение редко является хорошим признаком.
Например, я использовал «неявную инструкцию возврата» в одном из
примеров исходного кода в более ранней версии этой книги. Я не говорил
о том, что делал, — мне просто нужен был безупречный короткий образец
кода для объяснения какого-то другого функционального средства языка
Python.
В итоге я начал получать непрекращающийся поток электронных писем,
указывающих мне на «пропущенную» в том примере кода инструкцию
возврата. Неявное поведение инструкции
return
в Python отнюдь
не
было очевидным для всех и в данном случае отвлекало от сути. Я добавил
примечание, чтобы прояснить, что происходит, и электронные письма
прекратились.
Не поймите меня превратно: я люблю писать чистый и «красивый» про-
граммный код так же, как и любой другой разработчик. И раньше я тоже
был твердо убежден, что программисты должны знать всю подноготную
языка, с которым они работают.
3 .6 . Здесь нечего возвращать
101
Но когда вы рассматриваете влияние даже такого простого недоразумения
на сопровождение кода, возможно, имеет смысл склониться к написанию
более явного и четкого программного кода. В конце концов, программный
код — это общение.
Ключевые
выводы
Если в функции не указано возвращаемое значение, то она возвращает
None
. Возвращать
None
явным образом или неявным, решается стили-
стически.
Это ключевое функциональное средство языка Python, однако ваш
программный код может передавать свое намерение четче при помощи
явной инструкции
return
None
.
4
Классы и ООП
4 .1 . Сравнения объектов: is против ==
Когда я был мальчишкой, у наших соседей жили кошки-близняшки.
Внешне они были очень похожи — одинаковая темно-серая шерсть и оди-
наковый пронизывающий взгляд зеленых глаз. Отбросив некоторые инди-
видуальные особенности, на глаз вы бы их не различили. Но, конечно, они
были двумя разными кошками, двумя отдельными существами, несмотря
на то что выглядели одинаково.
Это подводит меня к разнице в смысле между понятиями «равенство»
и «тождество». И эта разница крайне важна для понимания того, как
ведут себя операторы сравнения Python
is
и
==
.
Оператор
==
выполняет сравнение путем проверки на равенство: если бы
эти кошки были объектами Python и мы сравнивали их оператором
==
, то
в качестве ответа мы получили бы, что «обе кошки равны».
Однако оператор
is
сравнивает идентичности: если бы мы сравнивали
наших кошек оператором
is
, то в качестве ответа мы получили бы, что
«это две разные кошки».
Но прежде чем я запутаюсь в этом кошачьем клубке, давайте взглянем на
небольшой реальный код Python.
Прежде всего, мы создадим новый объект-список и назовем его
a
, а затем
определим еще одну переменную (
b
), которая указывает на тот же самый
объект-список:
4 .1 . Сравнения объектов: is против ==
103
>>> a = [1, 2, 3]
>>> b = a
Давайте изучим эти две переменные. Мы видим, что они указывают на
внешне идентичные списки:
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
Когда мы сравним эти два объекта-списка на равенство при помощи опе-
ратора
==
, мы получим ожидаемый результат, поскольку эти два объекта-
списка выглядят одинаково:
>>> a == b
True
Однако этот результат не говорит о том, указывают ли
a
и
b
в действитель-
ности на тот же самый объект. Конечно, мы знаем, что это так, потому что
мы определили их ранее, но предположим, что мы не знаем, — тогда как
можно было бы это узнать?
Ответ на этот вопрос следует искать в сравнении обеих переменных опе-
ратором
is
. Это сравнение подтверждает, что обе переменные в действи-
тельности указывают на один объект-список:
>>> a is b
True
Давайте посмотрим, что происходит, когда мы создаем идентичную копию
нашего объекта-списка. Это можно сделать, вызвав
list()
с существую-
щим списком в качестве аргумента, чтобы создать копию, которую мы
назовем
c
:
>>> c = list(a)
И снова вы увидите, что только что созданный нами новый список вы-
глядит идентичным объекту-списку, на
который указывают
a
и
b
:
>>> c
[1, 2, 3]
104 Глава 4 • Классы и ООП
А вот теперь начинается самое интересное. Давайте сравним нашу копию
списка
c
с первоначальным списком
a
, использовав для этого оператор
==
.
Какой ответ вы ожидаете увидеть?
>>> a == c
True
О’кей. Надеюсь, что вы как раз этого и ожидали. Данный результат гово-
рит следующее:
c
и
a
имеют одинаковое содержимое. Python их считает
равными. Но вот вопрос: указывают ли они в действительности на один
и тот же объект? Давайте это выясним при помощи оператора
is
:
>>> a is c
False
О-па! И вот тут мы получаем другой результат. Python говорит, что
c
и
a
указывают на два разных объекта, несмотря на то что их содержимое
может быть одинаковым.
Итак, чтобы подытожить, давайте попробуем разложить разницу между
is
и
==
на два коротких определения:
Выражение
is
дает
True
, если две переменные указывают на тот же
самый (идентичный) объект.
Выражение
==
дает
True
, если объекты, на которые ссылаются пере-
менные, равны (имеют одинаковое содержимое).
Всякий раз, когда вам придется решать, применять оператор
is
или опе-
ратор
==
, просто вспомните про кошек-близняшек (в принципе, сойдут
и собаки).
Если вы это будете делать, то у вас все будет в порядке.
4 .2 . Преобразование строк
(каждому классу по __repr__)
Когда вы определяете собственный класс в Python и затем пытаетесь на-
печатать один из его экземпляров в консоли (или проверить его в сеансе
интерпретатора), вы получаете относительно неудовлетворительный
4 .2 . Преобразование строк (каждому классу по __repr__)
105
результат. Принятое по умолчанию поведение с преобразованием в стро-
ковое значение в стиле «to-string» является примитивным и испытывает
недостаток в подробностях:
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
>>> my_car = Car('красный', 37281)
>>> print(my_car)
<__console__.Car object at 0x109b73da0>
>>> my_car
<__console__.Car object at 0x109b73da0>
По умолчанию вы получаете лишь строковое значение, содержащее имя
класса и идентификатор экземпляра объекта (который в Python являет-
ся адресом объекта в оперативной памяти). Это лучше, чем
ничего, но не
очень-то полезно.
Вы можете попытаться найти обходной путь, непосредственно распе-
чатав атрибуты класса или даже добавив в классы собственный
метод
to_string()
:
>>> print(my_car.color, my_car.mileage)
красный 37281
Общая идея совершенно верная, но она игнорирует договоренности
об именовании и встроенные механизмы, которые Python использует
для
обработки того, как объекты представляются в виде строк.
Вместо того чтобы строить свой собственный механизм преобразования
строк, будет гораздо лучше, если вы добавите в свой класс дандер-ме-
тоды
__str__
и
__repr__
. Они представляют собой питоновский способ
управления тем, как объекты преобразовываются в строковые значения
в различных ситуациях
1
.
1
См. документацию Python «Модель данных Python»:
https://docs .python .org/3/
reference/datamodel .html
106 Глава 4 • Классы и ООП
Давайте взглянем, как эти методы работают на практике. Для начала мы
добавим метод
__str__
в класс
Car
, который мы определили ранее:
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __str__(self):
return f'{self.color} автомобиль'
Если сейчас попробовать напечатать или проинспектировать экземпляр
Car
, то вы получите другой, слегка улучшенный результат:
>>> my_car = Car('красный', 37281)
>>> print(my_car)
Do'stlaringiz bilan baham: