ПРОГРАММИРОВАНИЕ НА ПИТОНЕ
Вызываемый поплавок? Веселье и творчество в Python
Чтобы научиться быть креативным, мы реализуем вызываемые числа с плавающей запятой в Python.
Среди встроенных типов данных в Python у нас есть ряд типов, представляющих числа, наиболее важными из которых являются int
и float
. Как и все в Python, их экземпляры являются объектами; и как объекты они имеют свои атрибуты и методы. Например, вот что предлагают экземпляры типа float
:
Как видите, float
числа предлагают множество различных способов использования. Чего они не предлагают, так это метода .__call__()
, что означает, что вы не можете их вызывать.
Вы когда-нибудь задумывались, почему мы не можем вызывать число с плавающей запятой так, как вы можете вызывать функцию? Смотреть:
>>> i = 10.0012 >>> i() Traceback (most recent call last): ... TypeError: 'float' object is not callable
Это не работает. А почему не работает?!
Честно говоря… я никогда не задумывался, почему числа с плавающей запятой нельзя вызывать, но для меня это имеет смысл. Почему они? Зачем? Видите ли вы какую-то конкретную причину для их существования? Я не.
Но это не значит, что мы не будем реализовывать такой функционал — обязательно будем. И зачем нам это? По трем причинам: изучать Python, быть креативным и учиться быть креативным.
Научиться быть креативным — важный аспект изучения языка программирования. Однажды в проекте Python вы окажетесь в сложной ситуации, в которой стандартные решения не работают; вам придется думать и проявлять творческий подход. Ваша креативность может помочь вам найти новаторское, нетипичное решение, которое поможет вам решить эту странную или нетипичную проблему.
Таким образом, в этой статье мы реализуем вызываемый тип float; это определенно не типичный сценарий. Мы будем действовать поэтапно, и я подробно объясню каждый шаг. Постарайтесь быть творческим, когда читаете дальше. Возможно, это поможет вам придумать собственные идеи по улучшению решения. Если это так, реализуйте их и, пожалуйста, поделитесь ими в комментариях.
Выполнение
Мы начнем с реализации следующего простого подхода: если пользователь вызывает число с плавающей запятой, оно возвращает число, округленное до трех знаков после запятой. На данный момент давайте упростим — я жестко запрограммирую округление до трех десятичных цифр; Мы изменим это позже.
# callable_float.py class Float(float): def __call__(self): return round(self, 3)
И это все, что нам нужно! Как вы можете видеть ниже, в обычных случаях использования экземпляр Float
ведет себя так же, как обычный объект float
:
>>> from callable_float import Float >>> i = Float(10.0012) >>> i * 2 20.0024 >>> i - 1 9.0012 >>> round(2 * (i + 1) / 7, 5) 3.14320
Однако, в отличие от float
, Float
можно вызывать:
>>> i() 10.001
Voilà — вызываемое число с плавающей запятой.
Однако нет необходимости жестко кодировать количество десятичных цифр, которые будут использоваться при округлении. Мы можем позволить пользователю решать, сколько десятичных цифр использовать. Для этого достаточно добавить аргумент digits
в метод .__call__()
:
# callable_float.py class Float(float): def __call__(self, digits=3): return round(self, digits) >>> i(1) 10.0 >>> i(3) 10.001 >>> i(10) 10.0012
Идеальный! Обратите внимание, что это именно то, что мы получили бы, округлив соответствующее число float
:
>>> j = float(i) >>> i(1) == round(j, 1) True >>> i(3) == round(j, 3) True >>> i(10) == round(j, 10) True
Мы можем захотеть вернуть объект Float
вместо float
:
# callable_float.py class Float(float): def __call__(self, digits=3): return Float(round(self, digits))
Здесь единственное, что могут делать Float
числа при вызове, это округление. Скучный! Пусть он сможет делать все.
Чтобы реализовать такую общую концепцию, нам нужно сделать метод .__call__()
методом более высокого порядка, что здесь означает, что он принимает функцию (на самом деле, вызываемую) в качестве аргумента. Следующий класс реализует эту функциональность:
# callable_float.py from typing import Callable class Float(float): def __call__(self, func: Callable): return func(self)
Обратите внимание, что на этот раз мы не изменили тип возвращаемого значения на Float
, так как пользователь может захотеть использовать функцию func()
, которая возвращает объект другого типа.
Вот как работает эта версия Float
:
>>> from callable_float import Float >>> i = Float(12.105) >>> 2*i 24.21 >>> i(round) 12 >>> i(lambda x: 200) 200 >>> i(lambda x: x + 1) 13.105 >>> def square_root_of(x): ... return x**.5 >>> i(square_root_of) 3.479224051422961 >>> i(lambda x: round(square_root_of(x), 5)) 3.47922 >>> i = Float(12345.12345) >>> i(lambda x: Float(str(i)[::-1])) 54321.54321
Он работает как положено, но у него определенно есть существенный недостаток: вы не можете использовать дополнительные аргументы к функции func()
. Мы можем легко реализовать эту функциональность благодаря аргументам *args
и **kwargs
, позволяющим пользователю указывать любые аргументы:
# callable_float.py from typing import Callable class Float(float): def __call__(self, func: Callable, *args, **kwargs): return func(self, *args, **kwargs) >>> i(lambda x, digits: round(x**.5, digits), 5) 111.10861 >>> def rounded_square_root_of(x, digits=3): ... return round(x**.5, digits) >>> i(rounded_square_root_of) 111.109 >>> i(rounded_square_root_of, 5) 111.10861 >>> j = float(i) >>> i(rounded_square_root_of, 5) == rounded_square_root_of(j, 5) True
Все работает просто отлично и денди. Обратите внимание на следующие примечания:
- Пользователь предоставляет функцию для применения к экземпляру класса. Он не может принимать аргументы или любое их количество, как позиционное (
*args
), так и ключевое слово (**kwargs
). Тогда вызов значенияFloat
в качестве функции и функцииfunc()
в качестве аргумента этой функции — это то же самое, что вызов этой функции со значением в качестве аргумента… Если это не бред, то что?! - На этот раз возвращаемое значение
Float.__call__()
может быть любого типа, и это тот же тип, что и возвращаемое значение функцииfunc()
.
Заключение
Мы реализовали класс Float
, который позволяет вызывать число с плавающей запятой как функцию. Вызов экземпляра класса Float
с функцией в качестве аргумента означает вызов этой самой функции с экземпляром Float
в качестве аргумента. Вы можете использовать любые дополнительные аргументы, как позиционные, так и ключевые.
Мы сделали все это не для того, чтобы использовать вызываемый класс Float
. Я никогда не реализовывал и не использовал ничего подобного и не ожидаю. Но для меня это было весело, очень весело — и настоящим уроком творчества в использовании Python. Надеюсь, вам тоже понравилась эта статья. Я думаю, что такие сумасшедшие вещи имеют большую ценность: они помогают вам выучить Python и изучить тонкости языка, которые вы бы не изучили никаким другим способом.
Класс Float
не имеет большого значения. Важно то, что такой мозговой штурм может помочь вам стимулировать творчество в ваших будущих проектах, когда вам приходится иметь дело с нетипичными ситуациями. Практика таких реализаций, как эта, может помочь вам найти и реализовать нетипичные решения в ваших проектах Python.
Спасибо за прочтение. Если вам понравилась эта статья, вам могут понравиться и другие статьи, которые я написал; вы увидите их здесь. И если вы хотите присоединиться к Medium, воспользуйтесь моей реферальной ссылкой ниже: