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(), чтобы дать дополнительные возможности нашим методам векторного класса. Мы также использовали арифметические операторы, такие как «+», «-», «*» и «@» для выполнения векторной алгебры. Наконец, мы использовали концепцию индексации с использованием оператора [] для получения компонентов векторного объекта.