Что такое инстанцирование в программировании
Перейти к содержимому

Что такое инстанцирование в программировании

  • автор:

Инстанцирование (класса) — что это в программировании

melisa's picture

Инстанцирование (от слова instance — т.е. «сущность») — это создание экземпляра класса, т.е. объекта.

Пример инстанцирования класса

Строка на С-подобном синтаксисе (напр. PHP), в которой происходит инстанцирование класса MyClass:

$My = new MyClass();

Key Words for FKN + antitotal forum (CS VSU):

  • инстанцирование
  • instance
  • что это в программировании

Инстанцирование классов и экземпляры — Python: Введение в ООП

Класс, как мы уже увидели, может хранить данные. Но типичный класс присутствует в программе в единственном экземпляре. Поэтому сам по себе класс не очень полезен, ведь хранить определения можно и в модулях. Весь смысл использования классов заключается в их инстанцировании.

Инстанцированием (instantiation) называют процесс (акт) создания на основе класса экземпляра (instance) — такого объекта, который получает доступ ко всему содержимому класса, но при этом обладает и способностью хранить собственные данные. При этом, имея объект, всегда можно узнать, экземпляром какого класса он является.

Давайте объявим класс и создадим пару экземпляров, а заодно и познакомимся с синтаксисом инстанцирования классов:

class Person: pass bob = Person() bob # alice = Person() alice # bob is alice # False bob is Person # False alice is Person # False 

Что мы можем увидеть в этом примере? Первое, что бросается в глаза, это вызов класса как функции: Person() . Сходство это — не только внешнее. В Python инстанцирование фактически и является вызовом некоторой функции, которая возвращает новый экземпляр класса.

При выводе объекта класса в REPL можно увидеть строку, похожую на вывод информации о классе, только вместо «class» в строчке упоминается «object».

Также стоит обратить внимание на то, что все экземпляры являются отдельными объектами, поэтому оператор is дает False как при соотнесении экземпляров между собой, так и при соотнесении любого экземпляра с объектом класса ( bob , alice и Person — три самостоятельных объекта).

Атрибуты класса и экземпляры

В предыдущем примере класс был пустой. Теперь воспроизведем его, но добавим на этот раз атрибут:

class Person: name = 'Noname' bob, alice = Person(), Person() bob is alice # False bob.name is alice.name # True bob.name is Person.name # True bob.name # 'Noname' 

Этот пример показывает, что а) и bob , и alice имеют атрибут name , б) значение атрибутов name — общее для всех трех объектов.

Давайте же переименуем Боба:

bob.name = 'Bob' bob.name is Person.name # False Person.name # 'Noname' alice.name # 'Noname' bob.name # 'Bob' 

Вот вы и увидели то самое «собственное состояние объекта». Person продолжает давать имя всем экземплярам, пока те не изменят значение своего атрибута. В момент присваивания нового значения атрибуту экземпляра, экземпляр получает свой собственный атрибут!

Атрибут __dict__

Стоит прямо сейчас заглянуть «под капот» объектной системы Python, чтобы вы в дальнейшем могли исследовать объекты самостоятельно. Это и интересно, и полезно — как при обучении, так и при отладке объектного кода.

Итак, внутри каждого объекта Python хранит… словарь! Имена атрибутов в пространствах имен выступают ключами этого словаря, а значения являются ссылками на другие объекты. Словарь этот всегда называется __dict__ и тоже является атрибутом. Обращаясь к этому словарю, вы можете получить доступ к значениям атрибутов:

Person.__dict__['name'] # 'Noname' bob.__dict__['name'] # 'Bob' alice.__dict__['name'] # Traceback (most recent call last): # File "", line 1, in # KeyError: 'name' 

Присмотритесь, и вы увидите: у bob в __dict__ есть его собственное имя, а у alice собственного имени нет. Но при обращении к атрибуту привычным способом «через точку», вы видите имя и у alice ! Как же это работает?

Дело в том, что машинерия объектной системы Python при обращении к атрибуту сначала ищет атрибут в словаре экземпляра. Но если там соответствующего ключа не нашлось, то атрибут ищется уже в классе. Именно так alice получает имя: Python находит его в классе Person .

Надо сказать, что это очень разумный подход! Да, Python мог бы копировать словарь класса при инстанцировании. Но это привело бы к излишнему потреблению памяти. А вот «коллективное использование», напротив, позволяет память экономить!

И, конечно же, словарь __dict__ объекта может быть изменен. Когда мы давали Бобу имя, мы на самом деле сделали что-то такое:

bob.__dict__['name'] = 'Bob' 

Мы даже можем добавить Бобу фамилию и сделать это через модификацию __dict__ :

bob.__dict__['surname'] = 'Smith' bob.surname # 'Smith' 'surname' in Person.__dict__ # False 

А ведь у класса не было атрибута surname ! Каждый экземпляр класса тоже является самостоятельным пространством имен, пригодным для расширения в процессе исполнения программы (за счет использования под капотом словарей, как вы теперь знаете!).

Проверка принадлежности экземпляра к классу

Я выше уже упоминал, что объект всегда связан с классом. Эта связь заключается в наличии у экземпляра атрибута __class__ , который является ссылкой на объект класса:

bob.__class__ # bob.__class__ is Person # True 

Как вы уже могли заметить, в Python многие «внутренние штуки» имеют имена, заключенные в двойные символы подчеркивания. В разговоре питонисты обычно проговаривают подобные имена примерно так: «дАндер-класс», что является калькой с «dunder class», где «dunder», в свою очередь, это сокращение от «double underscore», то есть «двойной символ подчеркивания». Полезно запомнить этот стиль именования!

А еще стоит запомнить, что практически всегда, когда вы хотите использовать что-то, названное в dunder-стиле, «есть способ лучше»! Так с __dict__ напрямую работать не приходится, потому что есть возможность обращаться к атрибутам «через точку». Вот и __class__ в коде встречается редко. А рекомендуемый способ проверки принадлежности к классу выглядит так:

isinstance(bob, Person) # True 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Инстанцирование в Python

Какой метод вызывается первым при этом вызове Foo? Большинство новичков, да и, возможно, немало опытных питонистов тут же ответят: «метод __init__». Но если внимательно приглядеться к сниппетам выше, вскоре станет понятно, что такой ответ неверен.

__init__ не возвращает никакого результата, а Foo(1, y=2), напротив, возвращает экземпляр класса. К тому же __init__ принимает self в качестве первого параметра, чего не происходит при вызове Foo(1, y=2). Создание экземпляра происходит немного сложнее, о чём мы и поговорим в этой статье.

Порядок создания объекта

Инстанцирование в Python состоит из нескольких стадий. Понимание каждого шага делает нас чуть ближе к пониманию языка в целом. Foo — это класс, но в Питоне классы это тоже объекты! Классы, функции, методы и экземпляры — всё это объекты, и всякий раз, когда вы ставите скобки после их имени, вы вызываете их метод __call__. Так что Foo(1, y=2) — это эквивалент Foo.__call__(1, y=2). Причём метод __call__ объявлен в классе объекта Foo. Какой же класс у объекта Foo?

>>> Foo.__class__

Так что класс Foo — это экземпляр класса type и вызов метода __call__ последнего возвращает класс Foo. Теперь давайте разберём, что из себя представляет метод __call__ класса type. Ниже находятся его реализации на C в CPython и в PyPy. Если надоест их смотреть, прокручивайте чуть дальше, чтобы найти упрощённую версию:

CPython

static PyObject * type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) < PyObject *obj; if (type->tp_new == NULL) < PyErr_Format(PyExc_TypeError, "cannot create '%.100s' instances", type->tp_name); return NULL; > obj = type->tp_new(type, args, kwds); obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL); if (obj == NULL) return NULL; /* Ugly exception: when the call was type(something), don't call tp_init on the result. */ if (type == &PyType_Type && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && (kwds == NULL || (PyDict_Check(kwds) && PyDict_Size(kwds) == 0))) return obj; /* If the returned object is not an instance of type, it won't be initialized. */ if (!PyType_IsSubtype(Py_TYPE(obj), type)) return obj; type = Py_TYPE(obj); if (type->tp_init != NULL) < int res = type->tp_init(obj, args, kwds); if (res < 0) < assert(PyErr_Occurred()); Py_DECREF(obj); obj = NULL; >else < assert(!PyErr_Occurred()); >> return obj; >

PyPy

def descr_call(self, space, __args__): promote(self) # invoke the __new__ of the type if not we_are_jitted(): # note that the annotator will figure out that self.w_new_function # can only be None if the newshortcut config option is not set w_newfunc = self.w_new_function else: # for the JIT it is better to take the slow path because normal lookup # is nicely optimized, but the self.w_new_function attribute is not # known to the JIT w_newfunc = None if w_newfunc is None: w_newtype, w_newdescr = self.lookup_where('__new__') if w_newdescr is None: # see test_crash_mro_without_object_1 raise oefmt(space.w_TypeError, "cannot create '%N' instances", self) w_newfunc = space.get(w_newdescr, self) if (space.config.objspace.std.newshortcut and not we_are_jitted() and isinstance(w_newtype, W_TypeObject)): self.w_new_function = w_newfunc w_newobject = space.call_obj_args(w_newfunc, self, __args__) call_init = space.isinstance_w(w_newobject, self) # maybe invoke the __init__ of the type if (call_init and not (space.is_w(self, space.w_type) and not __args__.keywords and len(__args__.arguments_w) == 1)): w_descr = space.lookup(w_newobject, '__init__') if w_descr is not None: # see test_crash_mro_without_object_2 w_result = space.get_and_call_args(w_descr, w_newobject, __args__) if not space.is_w(w_result, space.w_None): raise oefmt(space.w_TypeError, "__init__() should return None") return w_newobject

Если забыть про всевозможные проверки на ошибки, то коды выше примерно эквивалентны такому:

def __call__(obj_type, *args, **kwargs): obj = obj_type.__new__(*args, **kwargs) if obj is not None and issubclass(obj, obj_type): obj.__init__(*args, **kwargs) return obj

__new__ выделяет память под «пустой» объект и вызывает __init__, чтобы его инициализировать.

  1. Foo(*args, **kwargs) эквивалентно Foo.__call__(*args, **kwargs).
  2. Так как объект Foo — это экземпляр класса type, то вызов Foo.__call__(*args, **kwargs) эквивалентен type.__call__(Foo, *args, **kwargs).
  3. type.__call__(Foo, *args, **kwargs) вызывает метод type.__new__(Foo, *args, **kwargs), возвращающий obj.
  4. obj инициализируется при вызове obj.__init__(*args, **kwargs).
  5. Результат всего процесса — инициализированный obj.

Кастомизация

Теперь давайте переключим наше внимание на __new__. Этот метод выделяет память под объект и возвращает его. Вы вольны кастомизировать этот процесс множеством разных способов. Следует отметить, что, хотя __new__ и является статическим методом, вам не нужно объявлять его используя @staticmethod: интерпретатор обрабатывает __new__ как специальный случай.

Распространённый пример переопределения __new__ — создание Синглтона:

class Singleton(object): _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance
>>> s1 = Singleton() . s2 = Singleton() . s1 is s2 True

Обратите внимание, что __init__ будет вызываться каждый раз при вызове Singleton(), поэтому следует соблюдать осторожность.

Другой пример переопределения __new__ — реализация паттерна Борг («Borg»):

class Borg(object): _dict = None def __new__(cls, *args, **kwargs): obj = super().__new__(cls, *args, **kwargs) if cls._dict is None: cls._dict = obj.__dict__ else: obj.__dict__ = cls._dict return obj
>>> b1 = Borg() . b2 = Borg() . b1 is b2 False >>> b1.x = 8 . b2.x 8

Учтите, что хотя примеры выше и демонстрируют возможности переопределения __new__, это ещё не значит что его обязательно нужно использовать:

__new__ — одна из самых частых жертв злоупотреблений. То, что может быть сделано переопределением этого метода, чаще всего лучше достигается другими средствами. Тем не менее, когда это действительно необходимо, __new__ — крайне полезный и мощный инструмент.

— Арион Спрэг, Хорошо забытое старое в Python

Редко можно встретить проблему в Python, где лучшим решением было использование __new__. Но когда у вас есть молоток, каждая проблема начинает выглядеть как гвоздь, поэтому всегда предпочитайте использованию нового мощного инструмента использование наиболее подходящего.

  • python
  • инстанцирование
  • экземпляр класса
  • Python
  • Программирование

Что такое инстанцирование?

Что такое файловый буфер? Что такое режим (модификатор) доступа, при работе с файлами?
Что такое файловый буфер? Что такое режим (модификатор) доступа, при работе с файлами?

Что такое рекурсивный тип данных? Что такое конструкция рекурсивного типа?
Что такое рекурсивный тип данных? Что такое конструкция рекурсивного типа?

Что такое хэндлер файла? Что такое файловый указатель?
Что такое хэндлер файла? Что такое файловый указатель?

Что такое заголовочный файл? Что такое файл исходного кода? Рассмотрите назначение каждого из них
Что такое заголовочный файл? Что такое файл исходного кода? Рассмотрите назначение каждого из.

Эксперт С++

2347 / 1720 / 148
Регистрация: 06.03.2009
Сообщений: 3,675
taras atavin, создание экземпляра класса.

ЦитатаСообщение от taras atavin Посмотреть сообщение

Это уже инстанцирование A?

Эксперт С++

3224 / 1751 / 435
Регистрация: 03.05.2010
Сообщений: 3,867

Вообще-то в C++ термин «инстанцирование» применяется к шаблонам.
Шаблон определяет семейство классов или функций. Результат инстанцирования этого шаблона с указанием его аргументов – класс или функция.
Например, вот это инстанцирование:

typedef std::complexint> T_int_complex;

И вот это тоже:

std::complexchar> char_c;

Делаю внезапно и красиво

Эксперт С++

1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744

ЦитатаСообщение от Mr.X Посмотреть сообщение

Вообще-то в C++ термин «инстанцирование» применяется к шаблонам.

Не только. Инстанцирование — создание экземпляра класса. В контексте шаблона речь идёт также и о генерации кода для поддержки соответствующего типа. Для не шаблонных классов, просто создание экземпляра.

Добавлено через 19 минут

ЦитатаСообщение от Mr.X Посмотреть сообщение

Например, вот это инстанцирование:
Очень сомневаюсь. Но проверить не получается.)))

Эксперт С++

3224 / 1751 / 435
Регистрация: 03.05.2010
Сообщений: 3,867

ЦитатаСообщение от Deviaphan Посмотреть сообщение

Для не шаблонных классов, просто создание экземпляра.

Ну, не знаю где вы это вычитали, но в русском переводе Страуструпа термин «инстанцирование» применяется только к шаблонам.

ЦитатаСообщение от Deviaphan Посмотреть сообщение

Цитата Сообщение от Mr.X Например, вот это инстанцирование:

Очень сомневаюсь. Но проверить не получается.)))

И что же тут сомнительного?

Эксперт CАвтор FAQ

21275 / 8292 / 637
Регистрация: 30.03.2009
Сообщений: 22,656
Записей в блоге: 30

ЦитатаСообщение от Deviaphan Посмотреть сообщение

Инстанцирование — создание экземпляра класса

Нет, не так. Если ты НЕ создаёшь экземпляр класса, то методы класса всё равно попадут в код (если они не inline). А вот если ты не создашь экземпляр чего-либо от шаблона, то код для этого шаблона не сгенерируется вообще. Если ты используешь лишь некоторые методы шаблонного класса, то неиспользуемые методы в код так же не попадут. Инстанциация по своей сути эквивалентна подстановке макроса. Сам макрос — это не есть материальный код, а всего лишь текстовое описание, и только использование этого макроса превращает описание в исходник (который уже превратится в код)

Делаю внезапно и красиво

Эксперт С++

1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744

ЦитатаСообщение от Mr.X Посмотреть сообщение

И что же тут сомнительного?

Как уже написал Evg, код будет сгенерирован только для тех методов, которые вызываются. В данном случае объявлен псевдоним типа, но никаких методов не вызывается и экземпляров не создаётся. Т.е. никакого кода сгенерировано не будет. И непонятно, что именно компилятор сделает? В общем я не могу спорить, т.к. не могу написать тестовый пример.(

Добавлено через 25 минут

ЦитатаСообщение от Evg Посмотреть сообщение

Нет, не так.

Я к тому, что без создания объекта инстанцирования вообще нет.) И, что термин инстанцирование применим не только к шаблонам.

Эксперт С++

3224 / 1751 / 435
Регистрация: 03.05.2010
Сообщений: 3,867

ЦитатаСообщение от Deviaphan Посмотреть сообщение

Т.е. никакого кода сгенерировано не будет. И непонятно, что именно компилятор сделает?

Ну, что сделает компилятор – это должно волновать разработчиков этого компилятора. А по стандарту инстанцирование шаблона – это применение к его имени списка аргументов в угловых скобках.
При определении типа и генерируется определение этого типа (в данном случае класса).
У Страуструпа написано: «из шаблона-класса и набора аргументов шаблона компилятору нужно сгенерировать и определение класса и определения его используемых функций-членов».
Так как использование других членов (не функций) специально не отслеживается, а они могут быть использованы в любой момент, то определение класса генерируется в любом случае.

Добавлено через 11 минут

ЦитатаСообщение от Deviaphan Посмотреть сообщение

Я к тому, что без создания объекта инстанцирования вообще нет.)

А вот тут у вас пока недопонимание. Инстанцирование — это создание типа или функции из шаблона. При чем тут объекты?

Делаю внезапно и красиво

Эксперт С++

1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744

ЦитатаСообщение от Mr.X Посмотреть сообщение

При чем тут объекты
При том, что инстанцирование — создание экземпляра, а не типа.

Эксперт С++

8049 / 4806 / 655
Регистрация: 24.06.2010
Сообщений: 10,562
Deviaphan, Создание экземпляра класса, но не объекта.

Инстанцирование (англ. instantiation) — создание экземпляра класса. В отличие от слова «создание», применяется не к объекту, а к классу. То есть, говорят: «(в виртуальной среде) создать экземпляр класса или инстанцировать класс». Порождающие шаблоны используют полиморфное инстанцирование.

Эксперт С++

3224 / 1751 / 435
Регистрация: 03.05.2010
Сообщений: 3,867

ЦитатаСообщение от Deviaphan Посмотреть сообщение

При том, что инстанцирование — создание экземпляра, а не типа.

Ну вот этот момент вы пока и не просекаете. Именно типа!
Прежде чем рассуждать, не вредно и в учебник заглянуть, а в нем сказано (у Страуструпа): «процесс генерации объявления класса по шаблону класса и аргументу шаблона называется инстанцированием шаблона».

Делаю внезапно и красиво

Эксперт С++

1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
В сообщении №4 я это и написал.
А теперь погугли слово экземпляр ещё.)

Эксперт CАвтор FAQ

21275 / 8292 / 637
Регистрация: 30.03.2009
Сообщений: 22,656
Записей в блоге: 30

ЦитатаСообщение от Deviaphan Посмотреть сообщение

При том, что инстанцирование — создание экземпляра, а не типа.

На самом деле как бы создание типа. Ты можешь вызывать метод класса или использовать поле класса без создания экземпляра класса. И инстанциация произойдёт. По стандарту скорее всего должно быть действительно так, что при любом упоминании шаблона с параметром происходит инстанциация всего, что растёт от этого шаблона, а то, что неиспользуемые методы не будут генериться, возможно это оптимизация. С другой стороны я не знаю, как это с формальной точки зрения соотносится с тем, что класс может быть описан в отдельном шаблоне, а метод — в отдельном. Но в итоге можно считать, что происходит именно то, о чём я писал выше: генерится код только для того, что реально используется

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *