ПРОГРАММИРОВАНИЕ НА ПИТОНЕ

Вызываемый поплавок? Веселье и творчество в 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, воспользуйтесь моей реферальной ссылкой ниже: