В чем разница между __init__ и __call__?
Заголовок: В чем разница между методами __init__
и __call__
в Python?
Текст вопроса:
Я хочу понять различия между методами __init__
и __call__
в Python.
Например, у меня есть следующий код:
class test:
def __init__(self):
self.a = 10
def __call__(self):
b = 20
Каковы основные отличия в функциональности и использовании этих методов? Как они работают в классе и какие практические сценарии их применения?
5 ответ(ов)
Первый метод используется для инициализации только что созданного объекта и принимает аргументы, которые используются для этого:
class Foo:
def __init__(self, a, b, c):
# ...
x = Foo(1, 2, 3) # вызов __init__
Второй метод реализует оператор вызова функции.
class Foo:
def __call__(self, a, b, c):
# ...
x = Foo()
x(1, 2, 3) # вызов __call__
Таким образом, __init__
отвечает за процесс создания объекта, а __call__
позволяет экземпляру класса вести себя как функция.
Определение пользовательского метода __call__()
позволяет экземпляру класса вызываться как функция, не всегда изменяя сам экземпляр.
In [1]: class A:
...: def __init__(self):
...: print("init")
...:
...: def __call__(self):
...: print("call")
...:
In [2]: a = A()
init
In [3]: a()
call
В данном примере, когда мы создаем экземпляр класса A
, вызывается метод __init__()
, который выводит "init". После этого, когда мы вызываем экземпляр a
как функцию, срабатывает метод __call__()
, который выводит "call". Это демонстрирует, что экземпляр класса может вести себя как функция, благодаря реализации метода __call__()
.
В Python функции являются объектами первого класса, что означает, что ссылки на функции могут передаваться в качестве аргументов другим функциям и/или методам и могут вызываться внутри них.
Экземпляры классов (или объекты) могут обрабатываться так, как если бы они были функциями: их можно передавать другим методам/функциям и вызывать. Для этого необходимо специальным образом определить метод класса __call__
.
def __call__(self, [args ...])
Этот метод принимает переменное количество аргументов. Допустим, x
— экземпляр класса X
, тогда x.__call__(1, 2)
аналогичен вызову x(1, 2)
, или вызову экземпляра как функции.
В Python метод __init__()
является конструктором класса (а __del__()
— деструктором класса). Таким образом, существует четкое различие между __init__()
и __call__()
: первый создает экземпляр класса, а второй делает экземпляр вызываемым как функция, не влияя на жизненный цикл самого объекта (то есть __call__
не влияет на процесс создания/разрушения), хотя и может изменять его внутреннее состояние (как показано ниже).
Пример:
class Stuff(object):
def __init__(self, x, y, range):
super(Stuff, self).__init__()
self.x = x
self.y = y
self.range = range
def __call__(self, x, y):
self.x = x
self.y = y
print('__call__ with (%d,%d)' % (self.x, self.y))
def __del__(self):
del self.x
del self.y
del self.range
>>> s = Stuff(1, 2, 3)
>>> s.x
1
>>> s(7, 8)
__call__ with (7,8)
>>> s.x
7
В этом примере мы создаем класс Stuff
, который имеет конструктор и метод __call__
. Мы можем менять внутренние значения атрибутов объекта, вызывая экземпляр как функцию.
В данной части кода мы видим два класса: A
и B
.
Для класса A
, конструктор (__init__
) просто печатает "From init ... " при создании экземпляра. Однако, когда мы пытаемся вызвать экземпляр класса A
(например, a()
), возникает ошибка AttributeError
, поскольку класс A
не определяет метод __call__
. Это означает, что объекты класса A
не могут быть вызваны как функции.
class A:
def __init__(self):
print("From init ...")
a = A() # Вывод: From init ...
a() # Ошибка: AttributeError: A instance has no __call__ method
В отличие от этого, класс B
также определяет конструктор, который печатает "From init ... ", но добавляет метод __call__
, который печатает "From call ... ". Это позволяет нам вызывать экземпляры класса B
как функции.
class B:
def __init__(self):
print("From init ... ")
def __call__(self):
print("From call ... ")
b = B() # Вывод: From init ...
b() # Вывод: From call ...
Таким образом, для того чтобы экземпляр класса можно было вызывать как функцию, необходимо определить метод __call__
.
Метод __call__
позволяет экземплярам класса быть вызываемыми, как функции. В каких случаях это может понадобиться?
Технически, метод __init__
вызывается один раз в процессе создания объекта через метод __new__
, чтобы произвести его инициализацию.
Однако существует множество сценариев, когда вы можете захотеть переопределить ваше состояние объекта. Например, вы завершили работу с текущим объектом и хотите создать новый объект, используя тот же экземпляр. С помощью метода __call__
вы можете переопределить поведение объекта, как будто он был создан заново.
Это лишь один из возможных случаев; на самом деле их может быть значительно больше.
Почему классы в Python наследуют от object?
Понимание Python super() с методами __init__()
Разница между старыми и новыми классами в Python?
Можно ли создать экземпляр абстрактного класса?
Имеет ли Python "приватные" переменные в классах?