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

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

  • автор:

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

Объект — некоторая сущность в виртуальном пространстве, обладающая определённым состоянием и поведением, имеет заданные значения свойств (атрибутов) и операций над ними (методов) [1] . Как правило, при рассмотрении объектов выделяется то, что объекты принадлежат одному или нескольким классам, которые в свою очередь определяют поведение (являются моделью) объекта. Время с момента создания объекта (конструкция) до его уничтожения (деструкция) называется временем жизни объекта. Объект наряду с понятием «класс», является важным понятием объектно-ориентированного подхода в программировании. Объекты обладают свойствами наследования, инкапсуляции и полиморфизма. [1]

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

Экземпляр класса (англ. instance ) — это описание конкретного объекта в памяти. Класс описывает свойства и методы, которые будут доступны у объекта, построенного по описанию, заложенному в класс. Экземпляры используют для представления (моделирования) конкретных сущностей реального мира. Например объектом может быть ваша стиральная машина, и иметь следующие атрибуты: компания-производитель «Вятка», наименование модели «Вятка-автомат», серийный номер изделия ВЯТ454647, емкость 20 л.

Имя объекта начинается обычно со строчной буквы.

Анонимный объект (англ. anonymous object ) — это объект который принадлежит некоторому классу, но не имеет имени.

Инициализация (англ. initialization ) — присвоение начальных значений полям объекта.

Практический подход

В большинстве объектно ориентированных языков программирования (таких как C++ или С#), объекты являются экземплярами некоторого заранее описанного класса (однако например в таком языке как конструктора класса, и уничтожаются либо с помощью деструктора класса (например, в C++), либо автоматически с использованием сборщика мусора (в C#). Объект хранится в виде данных всех его полей и ссылок на таблицу виртуальных методов и RTTI своего класса. Класс определяет набор функций и служебной информации для построения объекта, в том числе необходимый объем памяти для хранения объекта. В языке интерпретатор CPython) все значения являются объектами, даже классы. В этом языке можно построить класс, экземплярами которого будут классы. Такие классы называются метаклассами.

См. также

  • Класс
  • Объектно-ориентированное программирование
  • Множество
  • Элемент множества
  • Доменный объект

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

Параметрический полиморфизм — еще одно из проявлений принципа полиморфизма, которое позволяет многократно использовать один и тот же код применительно к разным типам. Реализация функции или класса осуществляется один раз, но при этом тип указывается как параметр этой функции или класса. При использовании этой функции или класса параметра типа заменяется на встроенный или абстрактный тип данных и универсальных код, не зависящий от типа, работает с указанным типом. Существуют целые библиотеки таких функций и классов (например, STL) и такая технология называется обобщенное программирование.

Шаблоны (templates) – средство для реализаций параметризированных классов и функций на языке С++.

6.3.1. Шаблоны функций. Пример 6.2 (шаблон функции)

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

void swap(int& x, int& y) < int temp=x; x=y; y=temp; >

templateclass TYPE> void swap(TYPE& x, TYPE& y)

Для создания шаблона мы заменили тип int на лексему TYPE с помощью ключевого слова template в скобках <>. Компилятор же выполнит замену наоборот, он заменит лексему TYPE на любой тип, который ему указать:

// использование шаблона double x=1.5, y=1.6; swapdouble>(x,y); // в данном случае тип double 

Генерация функции по шаблону и ее аргументу называется инстанцирование. Возможно выведение типа аргумента шаблона по типам его аргументов, но лучше явно указывать параметр в скобках <>.

///////////////////////////////////////////////////////////////////////////// // Прикладное программирование // Пример 6.2. Шаблон функции // // Кафедра Прикладной и компьютерной оптики, http://aco.ifmo.ru // Университет ИТМО ///////////////////////////////////////////////////////////////////////////// #include #include using namespace std; ///////////////////////////////////////////////////////////////////////////// // шаблон функции, меняющий местами значения двух переменных // TYPE — параметр шаблона (тип данных переменных) templateclass TYPE> void Swap(TYPE& x, TYPE& y) < TYPE temp=x; x=y; y=temp; >///////////////////////////////////////////////////////////////////////////// // тестирование шаблона функции void main() < double dx=1.5, dy=1.6; cout» «<>(dx, dy); // TYPE заменяется на double cout» «int ix=1, iy=5; cout» «<>(ix, iy); // TYPE заменяется на int cout» ««one», sy=«two»; cout» «(sx, sy); // TYPE заменяется на string cout» «

6.3.2. Шаблоны функций с несколькими параметрами. Пример 6.3 (шаблон функции с несколькими параметрами)

Часто возникает необходимость использовать несколько параметров у одной функции шаблона. При этом в качестве параметра может использоваться не только тип данных, но и числовое значение.

///////////////////////////////////////////////////////////////////////////// // Прикладное программирование // Пример 6.3. Шаблоны функций с несколькими параметрами // // Кафедра Прикладной и компьютерной оптики, http://aco.ifmo.ru // Университет ИТМО ///////////////////////////////////////////////////////////////////////////// #include using namespace std; ///////////////////////////////////////////////////////////////////////////// // шаблон функции с двумя параметрами TYPE1 и TYPE2 // преобразование переменно x типа TYPE1 к типу TYPE2 templateclass TYPE1, class TYPE2> bool transform(TYPE1 x, TYPE2& y) < if(sizeof(y) < sizeof(x)) return false; y=(TYPE2)x; return true; > ///////////////////////////////////////////////////////////////////////////// // шаблон функции с двумя параметрами: типом и числовым значением // функция вычисляет факториал числа n templateclass TYPE, int n> TYPE factorial() < TYPE sum=1; int i=1; while(i <=n) < sum*=i; i++; >return sum; > ///////////////////////////////////////////////////////////////////////////// // тестирование шаблона функции с несколькими параметрами void main() < int x=1; double y; // преобразование типа int в double transformint, double>(x, y); cout"double("<double res=factorialdouble, 4>(); cout"4!=" /////////////////////////////////////////////////////////////////////////////

6.3.3. Шаблоны классов. Пример 6.4 (шаблон класса Комплексное число)

Шаблоны позволяют реализуют в языке С++ такое отношение между классами, как инстанцирование. При создании шаблона класса тип одного или нескольких его переменных членов задаются в качестве параметра. При использовании этого шаблона необходимо будет указать, какой тип использовать в качестве параметра. Это и называется инстанцированием. Например, обобщенный класс Complex. Многие классы, которые используются для хранения и управления гомогенными структурами данных, реализуют в виде шаблонов. И действительно, независимо от типов хранимых в нем элементов, класс Complex должен выполнять одни и те же функции

Процесс генерации объявления класса по шаблону и аргументу называется инстанцированием шаблона.

///////////////////////////////////////////////////////////////////////////// // Прикладное программирование // Пример 6.4. Шаблон класса Комплексное число // complex.h // // Кафедра Прикладной и компьютерной оптики, http://aco.ifmo.ru // Университет ИТМО ///////////////////////////////////////////////////////////////////////////// // проверка на повторное подключение файла #if !defined COMPLEX_H #define COMPLEX_H #include using namespace std; ///////////////////////////////////////////////////////////////////////////// // шаблон класса Комплексное число template class PAR> class Complex < private: // вещественная и мнимая часть комплексного числа PAR m_re, m_im; public: // конструкторы Complex(); Complex(PAR re, PAR im=PAR(0)); Complex(const Complex& other); public: // получение параметров комплексного числа PAR GetRe() const; PAR GetIm() const; // изменение параметров комплексного числа void Set(PAR re, PAR im=PAR(0)); // оператор умножения Complex operator*(const Complex& other) const; // оператор умножения на число Complex operator*(const PAR& other) const; // оператор умножения с присваиванием Complex& operator*=(const Complex& other); // оператор присваивания Complex& operator=(const Complex& other); // оператор равенства bool operator== (const Complex& other) const; // оператор сопряжения комплексного числа Complex operator~() const; // унарный минус Complex operator-() const; // ввод/вывод комплексного числа template class PAR> friend ostream& operatorconst Complex& x); template class PAR> friend istream& operator>> (istream& out, Complex& x); >; ///////////////////////////////////////////////////////////////////////////// // конструктор по умолчанию template class PAR> Complex::Complex() : m_re(0.) , m_im(0.) < >///////////////////////////////////////////////////////////////////////////// // полный конструктор template class PAR> Complex::Complex(PAR re, PAR im) : m_re(re) , m_im(im) < >///////////////////////////////////////////////////////////////////////////// // конструктор копирования template class PAR> Complex::Complex(const Complex& x) : m_re(x.m_re) , m_im(x.m_im) < >//////////////////////////////////////////////////////////////////////////// // получение вещественной части комплексного числа template class PAR> PAR Complex::GetRe() const < return m_re; > ///////////////////////////////////////////////////////////////////////////// // получение мнимой части комплексного числа template class PAR> PAR Complex::GetIm() const < return m_im; > ///////////////////////////////////////////////////////////////////////////// // изменение параметров комплексного числа template class PAR> void Complex::Set(PAR re, PAR im) < m_re=re; m_im=im; >///////////////////////////////////////////////////////////////////////////// // оператор умножения template class PAR> Complex Complex::operator*(const Complex& other) const < return Complex(m_re*other.m_re-m_im*other.m_im, m_re*other.m_im-m_im*other.m_re); > ///////////////////////////////////////////////////////////////////////////// // оператор умножения на число template class PAR> Complex Complex::operator*(const PAR& other) const < return Complex(m_re*other, m_im*other); > ///////////////////////////////////////////////////////////////////////////// // оператор умножения с присваиванием template class PAR> Complex& Complex::operator*=(const Complex& other) < Complex temp(*this); m_re=temp.m_re*other.m_re - temp.m_im*other.m_im; m_im=temp.m_re*other.m_im + temp.m_im*other.m_re; return (*this); > ///////////////////////////////////////////////////////////////////////////// // унарный минус template class PAR> Complex Complex::operator-() const < return Complex(-m_re, -m_im); > ///////////////////////////////////////////////////////////////////////////// // оператор сопряжения комплексного числа template class PAR> Complex Complex::operator~() const < return Complex(m_re, -m_im); > ///////////////////////////////////////////////////////////////////////////// // оператор равенства template class PAR> bool Complex::operator== (const Complex& other) const < return (m_re == other.m_re && m_im == other.m_im); > ///////////////////////////////////////////////////////////////////////////// // оператор присваивания template class PAR> Complex& Complex::operator=(const Complex& other) < if(this != &other) < m_re=other.m_re; m_im=other.m_im; >return *this; > ///////////////////////////////////////////////////////////////////////////// // вывод комплексного числа на экран template class PAR> ostream& operatorconst Complex& x) < return (out"("<<); > ///////////////////////////////////////////////////////////////////////////// // ввод комплексного числа с клавиатуры template class PAR> istream& operator>> (istream& in, Complex& x) < return (in>>x.m_re>>x.m_im); > ///////////////////////////////////////////////////////////////////////////// #endif //defined Complex_H
///////////////////////////////////////////////////////////////////////////// // Прикладное программирование // Пример 6.4. Шаблон класса Комплексное число // test_complex.cpp // // Кафедра Прикладной и компьютерной оптики, http://aco.ifmo.ru // Университет ИТМО ///////////////////////////////////////////////////////////////////////////// #include using namespace std; // подключение описания класса #include "complex.h" ///////////////////////////////////////////////////////////////////////////// // пример использования шаблона класса Complex void main() < Complexint> a(5), b(3,3); Complexdouble> c(1.144, -0.155); cout" "" " /////////////////////////////////////////////////////////////////////////////

Объекты

Процесс создания объектов из класса называется инстанцированием. Объект является экземпляром класса. Объект создается на основе класса как формы и является экземпляром абстракции, которую представляет класс. Объект должен быть создан до того, как он будет использоваться в программе. В Java манипуляции с объектами производятся через объектные ссылки (также называемые ссылочными значениями или просто ссылками). Процесс создания объектов обычно включает следующие шаги.

    Объявление переменной для хранения ссылки на объект. Это включает объявление ссылочной переменной соответствующего класса для хранения ссылки на объект.

// Объявление двух ссылочных переменных, которые будут обозначать два разных объекта, а именно два стека символов CharStack stack1, stack2;
// Создание двух разных стеков символов stack1 = new CharStack(10); // длина стека 10 символов stack2 = new CharStack(5); // длина стека 5 символов

Оператор new возвращает ссылку на новый экземпляр класса CharStack . Эта ссылка может быть присвоена ссылочной переменной соответствующего типа. Каждый объект уникален и имеет свою собственную копию полей, объявленных в описании класса. Два стека, обозначенные как stack1 и stack2 , имеют свои собственные поля stackArray и topOfStack .

Цель вызова конструктора с правой стороны от оператора new в том, чтобы проинициализировать недавно созданный объект. В нашем частном случае для каждого нового экземпляра класса CharStack , созданного оператором new , конструктор создает массив символов. Длина массива задается значением аргумента конструктора. Конструктор также инициализирует поле topOfStack .

Объявление и инстанцирование можно объединить.

CharStack stack1 = new CharStack(10), stack2 = new CharStack(5);

На рис. 1.2 показана нотация UML для объектов. Графическое изображение объекта похоже на изображение класса. На рис. 1.2. приведена каноническая форма, в которой имя ссылочной переменной, обозначающей объект, является префиксом к имени класса, а разделителем имен служит двоеточие «:».

Рис. 1.2. Нотация UML для объектов

Стандартное обозначение объектов

Инстанцирование в 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
  • Программирование

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

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