Python имеет множество встроенных функций, поведение которых можно добавить в созданные пользователем классы, чтобы предоставить дополнительные функциональные возможности объектам. Эта статья поможет вам понять концепцию перегрузки операторов или функций в Python. Мы применим концепцию перегрузки функций и операторов на примере к нашим классам, чтобы сделать объекты более питоническими.
Встроенные методы
Интерпретатор Python имеет несколько встроенных функций и типов, которые всегда доступны. Вы можете найти список встроенных функций в алфавитном порядке здесь на python.org. В качестве альтернативы эти функции находятся в модуле __builtins__ в виде словаря. Ключи этого словаря — это имена функций, как показано ниже.
print([*__builtins__.__dict__]) ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__build_class__', '__import__', 'abs', 'all', 'any', 'ascii', 'bin', 'breakpoint', 'callable', 'chr', 'compile', 'delattr', 'dir', 'divmod', 'eval', 'exec', 'format', 'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id', 'input', 'isinstance', 'issubclass', 'iter', 'len', 'locals', 'max', 'min', 'next', 'oct', 'ord', 'pow', 'print', 'repr', 'round', 'setattr', 'sorted', 'sum', 'vars', 'None', 'Ellipsis', 'NotImplemented', 'False', 'True', 'bool', 'memoryview', 'bytearray', 'bytes', 'classmethod', 'complex', 'dict', 'enumerate', 'filter', 'float', 'frozenset', 'property', 'int', 'list', 'map', 'object', 'range', 'reversed', 'set', 'slice', 'staticmethod', 'str', 'super', 'tuple', 'type', 'zip', '__debug__', 'BaseException', 'Exception', 'TypeError', 'StopAsyncIteration', 'StopIteration', 'GeneratorExit', 'SystemExit', 'KeyboardInterrupt', 'ImportError', 'ModuleNotFoundError', 'OSError', 'EnvironmentError', 'IOError', 'WindowsError', 'EOFError', 'RuntimeError', 'RecursionError', 'NotImplementedError', 'NameError', 'UnboundLocalError', 'AttributeError', 'SyntaxError', 'IndentationError', 'TabError', 'LookupError', 'IndexError', 'KeyError', 'ValueError', 'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'AssertionError', 'ArithmeticError', 'FloatingPointError', 'OverflowError', 'ZeroDivisionError', 'SystemError', 'ReferenceError', 'MemoryError', 'BufferError', 'Warning', 'UserWarning', 'DeprecationWarning', 'PendingDeprecationWarning', 'SyntaxWarning', 'RuntimeWarning', 'FutureWarning', 'ImportWarning', 'UnicodeWarning', 'BytesWarning', 'ResourceWarning', 'ConnectionError', 'BlockingIOError', 'BrokenPipeError', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionRefusedError', 'ConnectionResetError', 'FileExistsError', 'FileNotFoundError', 'IsADirectoryError', 'NotADirectoryError', 'InterruptedError', 'PermissionError', 'ProcessLookupError', 'TimeoutError', 'open', 'copyright', 'credits', 'license', 'help', '__IPYTHON__', 'display', 'get_ipython']
Обратите внимание, что эти встроенные функции являются методами определенных встроенных классов. Мы можем проверить встроенные методы, доступные для определенного объекта, как показано ниже.
l = [1,2,3] print(dir(l)) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Когда объект или экземпляр определенного класса передается через встроенные функции, для этого объекта вызывается метод с соответствующими аргументами. Например, функция len() — это встроенная функция для возврата длины (количества элементов) объекта. Вызов функции len() с объектом в качестве аргумента эквивалентен вызову метода __len__() для того же объекта без каких-либо аргументов. В приведенном ниже коде мы видим, что и len(obj), и obj.__len__() возвращают один и тот же вывод, который является длиной списка.
print(len([1,3,3])) print([1,3,3].__len__())
Выход:
3 3
Точно так же, когда у нас есть оператор, применяемый к двум объектам, и соответствующий метод для этого оператора — __op__(), python вызывает этот оператор для объекта 1 и передает второй объект в качестве аргумента. В приведенном ниже коде оператор + между объектами a и b эквивалентен вызову метода __add__() для a с аргументом b.
a = 1 b = 2 print(a+b) print(a.__add__(b))
Выход:
3 3
По сути, каждая из этих функций (скажем, func()) — это общедоступный интерфейс, который мы используем для получения или обновления состояния объекта Python. За кулисами, когда мы используем общедоступный интерфейс, для объекта реализуется метод (__func__).
Следовательно, мы можем определить эти встроенные методы в нашем классе и переопределить поведение функции или оператора, связанного с ними, потому что за кулисами python вызовет наш определенный метод.
Использование встроенных методов для классов
Допустим, нам нужен класс, представляющий семейство векторов в двумерном пространстве. Мы также хотим иметь некоторые методы для этого класса, такие как сумма, скалярное произведение и перекрестное произведение, для выполнения элементарных операций векторной алгебры.
Во-первых, мы хотим представить наш вектор, используя стандартную запись единичного вектора, например, ai + bj. Для этого кто-то может решить реализовать метод uni_vect_not() в своем классе. В качестве альтернативы мы можем настроить встроенную функцию str() (__str__) таким образом, чтобы она возвращала запись единичного вектора при задании двух компонентов вектора.
После векторного представления мы решаем выполнить элементарные алгебраические операции над векторами, такие как сложение и вычитание. Для этого кто-то может решить определить в своем классе методы с именами add_vec() и sub_vec() соответственно. В качестве альтернативы мы можем настроить встроенные операторы «+» и «-» таким образом, чтобы они возвращали сумму и разность двух векторов.
Как обсуждалось ранее, эти встроенные методы вызываются с помощью функций и операторов более высокого уровня (знаков). Если мы хотим использовать эти встроенные методы для наших классов, мы должны определить их в определении нашего класса. Это переопределит поведение функции и оператора, связанных с объектами.
Использование str() для печати объектов
Функция str() возвращает строковую версию объекта, и если объект не указан, она возвращает пустую строку. Вызов str(object) эквивалентен object.__str__(), который возвращает неформальное или красивое для печати строковое представление объекта. Если у объекта нет метода __str__(), то str() вернет repr(object).
Давайте определим красивое строковое представление, используя функцию str() для векторного объекта для нашего класса Vector.
class Vector: """A class for family of vectors in 2-D""" def __init__(self, x_comp, y_comp): self.x_comp = x_comp self.y_comp = y_comp def __str__(self): if self.y_comp >= 0: return str(f'{self.x_comp}i + {self.y_comp}j') else: return str(f'{self.x_comp}i - {abs(self.y_comp)}j') #let's define a vector object v v = Vector(2,-3) v <__main__.Vector at 0x225e3202408>
Здесь мы видим вывод в виде объекта Vector в определенной ячейке памяти. Но это не стандартное обозначение единичного вектора. Давайте используем определяемую пользователем функцию str() для нашего объекта, чтобы получить обозначение единичного вектора.
str(v) '2i - 3j' #much better
Использование арифматических операторов для векторной алгебры
Операторы «+», «-», «*», «@» и т. д. используются для арифметических операций в Python. Когда мы используем операторы «+», «-», «*» и «@» для двух или более объектов, интерпретатор Python вызывает встроенные функции __add__, __sub__, __mul__ и __matmul__ соответственно для этих объектов. Например, чтобы оценить выражение a+ b, где a — экземпляр класса a, который имеет метод __add__(), вызывается a.__add__(b).
Давайте реализуем эти операторы для нашего векторного класса, чтобы складывать, вычитать и умножать два вектора. Для умножения оператор «*» используется для получения скалярного произведения двух векторов, а оператор «@» используется для получения перекрестного произведения двух векторов. Следующий код показывает реализацию этих методов в нашем классе Vector.
class Vector: """A class for family of vectors in 2-D""" def __init__(self, x_comp, y_comp): self.x_comp = x_comp self.y_comp = y_comp def __str__(self): if self.y_comp >= 0: return str(f'{self.x_comp}i + {self.y_comp}j') else: return str(f'{self.x_comp}i - {abs(self.y_comp)}j') def __add__(self, v1): xa = self.x_comp + v1.x_comp ya = self.y_comp + v1.y_comp return Vector(xa,ya) def __sub__(self, v1): xs = self.x_comp - v1.x_comp ys = self.y_comp - v1.y_comp return Vector(xs,ys) def __mul__(self,v1): self_list = [self.x_comp, self.y_comp] v1_list = [v1.x_comp, v1.y_comp] return np.dot(self_list, v1_list) def __matmul__(self,v1): self_list = [self.x_comp, self.y_comp] v1_list = [v1.x_comp, v1.y_comp] return np.cross(self_list, v1_list) v = Vector(2,-3) w = Vector(4, -1) print(v+w) print(v*w) print(v@w)
Выход:
6i - 4j 11 10
Здесь следует отметить одну интересную вещь: когда мы использовали функцию print(v+w) вместо функции str() для возврата нотации единичного вектора для нашего векторного объекта, она возвращала нотацию правильно. Это связано с тем, что когда мы используем оператор print(), метод __str__() вызывается для объекта неявно.
Получение компонентов и длины вектора
Чтобы получить компоненты вектора, мы будем использовать концепцию индексации списка. Оператор [] вызывается для получения элемента по определенному индексу, или для получения значения определенного ключа в словаре, или для получения части последовательности путем нарезки. Когда мы используем этот оператор, за кулисами вызывается встроенная функция __getitem__(). Мы будем использовать этот оператор, чтобы получить компоненты x и y нашего вектора. Доступ к компоненту x будет осуществляться с помощью v[i], и аналогичным образом компонент y будет доступен с помощью v[j].
Чтобы получить длину вектора, мы будем использовать функцию abs() в Python. Поскольку длина (величина) вектора всегда является абсолютной величиной, имеет смысл использовать функцию abs(). Другой вариант — использовать функцию len(), но эта функция всегда возвращает целочисленное значение, а длина вектора может быть плавающей. Если мы используем для этой цели функцию len(), она выдаст ошибку типа. Давайте реализуем эти два метода в нашем классе Vector, чтобы получить компоненты и длину векторного объекта.
class Vector: """A class for family of vectors in 2-D""" def __init__(self, x_comp, y_comp): self.x_comp = x_comp self.y_comp = y_comp def __str__(self): if self.y_comp >= 0: return str(f'{self.x_comp}i + {self.y_comp}j') else: return str(f'{self.x_comp}i - {abs(self.y_comp)}j') def __getitem__(self, key): if key == 'i': return self.x_comp elif key == 'j': return self.y_comp else: print('Not a valid key') def __abs__(self): return (self.x_comp ** 2 + self.y_comp ** 2) ** 0.5 v = Vector(2,-3) print(v) print(v['i']) print(abs(v))
Выход:
2i - 3j 2 3.605551275463989
Заключение
В этой статье мы узнали о встроенных функциях Python и о том, как эти функции можно использовать в определяемых пользователем классах, чтобы сделать наш код более питоническим. Мы использовали функции str(), abs(), чтобы дать дополнительные возможности нашим методам векторного класса. Мы также использовали арифметические операторы, такие как «+», «-», «*» и «@» для выполнения векторной алгебры. Наконец, мы использовали концепцию индексации с использованием оператора [] для получения компонентов векторного объекта.