Які є класи обєктів

0 Comments

Зміст:

9. Класи¶

Класи надають засоби об’єднання даних і функціональних можливостей. Створення нового класу створює новий тип об’єкта, що дозволяє створювати нові примірники цього типу. Кожен екземпляр класу може мати атрибути, приєднані до нього для підтримки його стану. Екземпляри класу також можуть мати методи (визначені його класом) для зміни свого стану.

Порівняно з іншими мовами програмування, механізм класів Python додає класи з мінімумом нового синтаксису та семантики. Це суміш механізмів класів, знайдених у C++ і Modula-3. Класи Python забезпечують усі стандартні функції об’єктно-орієнтованого програмування: механізм успадкування класів дозволяє створювати кілька базових класів, похідний клас може перевизначати будь-які методи свого базового класу або класів, а метод може викликати метод базового класу з тим самим іменем. . Об’єкти можуть містити довільні обсяги та типи даних. Як і для модулів, класи мають динамічну природу Python: вони створюються під час виконання та можуть бути змінені далі після створення.

У термінології C++ зазвичай члени класу (включно з членами даних) є публічними (за винятком див. нижче Приватні змінні ), а всі функції-члени є віртуальними. Як і в Modula-3, немає скорочень для посилань на члени об’єкта з його методів: функція методу оголошується з явним першим аргументом, що представляє об’єкт, який неявно надається викликом. Як і в Smalltalk, класи самі є об’єктами. Це забезпечує семантику для імпортування та перейменування. На відміну від C++ і Modula-3, вбудовані типи можуть використовуватися як базові класи для розширення користувачем. Також, як і в C++, більшість вбудованих операторів зі спеціальним синтаксисом (арифметичні оператори, індексування тощо) можна перевизначати для екземплярів класу.

(Через відсутність загальноприйнятої термінології для розмови про класи, я час від часу використовую терміни Smalltalk і C++. Я б використовував терміни Modula-3, оскільки його об’єктно-орієнтована семантика ближча до семантики Python, ніж C++, але я очікую, що мало читачів чув про це.)

9.1. Слово про імена та предмети¶

Об’єкти мають індивідуальність, і кілька імен (у кількох областях) можуть бути прив’язані до одного об’єкта. Це відоме як псевдонім в інших мовах. Це зазвичай не оцінюється з першого погляду на Python, і його можна сміливо ігнорувати, коли маєте справу з незмінними базовими типами (числа, рядки, кортежі). Однак псевдонім має, можливо, дивовижний ефект на семантику коду Python, що включає змінні об’єкти, такі як списки, словники та більшість інших типів. Зазвичай це використовується на користь програми, оскільки псевдоніми в деяких аспектах поводяться як покажчики. Наприклад, передача об’єкта дешева, оскільки реалізація передає лише покажчик; і якщо функція змінює об’єкт, переданий як аргумент, абонент побачить зміни — це усуває потребу у двох різних механізмах передачі аргументів, як у Pascal.

9.2. Області та простори імен Python¶

Перш ніж представити класи, я спершу маю розповісти вам дещо про правила видимості Python. Визначення класів грають влучні трюки з просторами імен, і вам потрібно знати, як працюють області та простори імен, щоб повністю зрозуміти, що відбувається. До речі, знання з цієї теми знадобляться будь-якому досвідченому програмісту на Python.

Почнемо з деяких визначень.

Простір імен — це відображення імен на об’єкти. Більшість просторів імен наразі реалізовано як словники Python, але зазвичай це нічим не помітно (за винятком продуктивності), і це може змінитися в майбутньому. Прикладами просторів імен є: набір вбудованих імен (що містять такі функції, як abs() , і вбудовані імена винятків); глобальні імена в модулі; і локальні імена під час виклику функції. У певному сенсі набір атрибутів об’єкта також формує простір імен. Важливо знати про простори імен, що між іменами в різних просторах імен немає абсолютно ніякого зв’язку; наприклад, обидва різні модулі можуть визначати функцію maximize без плутанини — користувачі модулів повинні додавати перед нею назву модуля.

До речі, я використовую слово attribute для будь-якого імені після крапки — наприклад, у виразі z.real , real є атрибутом об’єкта z . Строго кажучи, посилання на імена в модулях є посиланнями на атрибути: у виразі modname.funcname , modname є об’єктом модуля, а funcname є його атрибутом. У цьому випадку відбувається пряме відображення між атрибутами модуля та глобальними іменами, визначеними в модулі: вони спільно використовують той самий простір імен! [ 1 ]

Attributes may be read-only or writable. In the latter case, assignment to attributes is possible. Module attributes are writable: you can write modname.the_answer = 42 . Writable attributes may also be deleted with the del statement. For example, del modname.the_answer will remove the attribute the_answer from the object named by modname .

Простори імен створюються в різний момент і мають різний час життя. Простір імен, що містить вбудовані імена, створюється під час запуску інтерпретатора Python і ніколи не видаляється. Глобальний простір імен для модуля створюється, коли зчитується визначення модуля; зазвичай простори імен модулів також зберігаються, доки інтерпретатор не завершить роботу. Інструкції, що виконуються за допомогою виклику інтерпретатора верхнього рівня, або прочитані з файлу сценарію, або в інтерактивному режимі, вважаються частиною модуля під назвою __main__ , тому вони мають власний глобальний простір імен. (Вбудовані імена насправді також містяться в модулі; це називається builtins .)

Локальний простір імен для функції створюється під час виклику функції та видаляється, коли функція повертає або викликає виняткову ситуацію, яка не обробляється цією функцією. (Насправді, забування було б кращим способом описати те, що насправді відбувається.) Звичайно, кожен рекурсивний виклик має свій власний локальний простір імен.

Область — це текстова область програми Python, де простір імен доступний безпосередньо. «Безпосередній доступ» тут означає, що некваліфіковане посилання на ім’я намагається знайти ім’я в просторі імен.

Хоча області визначаються статично, вони використовуються динамічно. У будь-який час під час виконання є 3 або 4 вкладені області, простори імен яких доступні безпосередньо:

  • внутрішня область, яка шукається першою, містить локальні назви
  • the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contain non-local, but also non-global names
  • передостання область містить глобальні імена поточного модуля
  • крайня область (шукається останньою) — це простір імен, що містить вбудовані імена

If a name is declared global, then all references and assignments go directly to the next-to-last scope containing the module’s global names. To rebind variables found outside of the innermost scope, the nonlocal statement can be used; if not declared nonlocal, those variables are read-only (an attempt to write to such a variable will simply create a new local variable in the innermost scope, leaving the identically named outer variable unchanged).

Зазвичай локальна область посилається на локальні імена поточної (текстової) функції. Поза функціями локальна область посилається на той самий простір імен, що й глобальна область: простір імен модуля. Визначення класу розміщують ще один простір імен у локальній області.

Важливо розуміти, що області визначаються текстово: глобальна область дії функції, визначеної в модулі, є простором імен цього модуля, незалежно від того, звідки чи яким псевдонімом викликається функція. З іншого боку, фактичний пошук імен виконується динамічно, під час виконання — однак визначення мови розвивається в бік статичного дозволу імен під час «компіляції», тому не покладайтеся на динамічне дозвіл імен! (Насправді локальні змінні вже визначені статично.)

Особлива примха Python полягає в тому, що, якщо не діють оператори global або nonlocal , призначення імен завжди потрапляють у найглибшу область видимості. Призначення не копіюють дані — вони просто прив’язують імена до об’єктів. Те саме стосується видалень: оператор del x видаляє прив’язку x із простору імен, на який посилається локальна область видимості. Насправді всі операції, які вводять нові імена, використовують локальну область видимості: зокрема, оператори import і визначення функцій прив’язують назву модуля або функції в локальній області видимості.

Інструкція global може бути використана, щоб вказати, що певні змінні живуть у глобальній області видимості та мають бути перенаправлені туди; оператор nonlocal вказує на те, що певні змінні живуть в охоплюючій області видимості і мають бути перенаправлені туди.

9.2.1. Приклад областей і просторів імен¶

Це приклад, який демонструє, як посилатися на різні області та простори імен, і як global і nonlocal впливають на зв’язування змінних:

def scope_test(): def do_local(): spam = "local spam" def do_nonlocal(): nonlocal spam spam = "nonlocal spam" def do_global(): global spam spam = "global spam" spam = "test spam" do_local() print("After local assignment:", spam) do_nonlocal() print("After nonlocal assignment:", spam) do_global() print("After global assignment:", spam) scope_test() print("In global scope:", spam) 

Результат коду прикладу:

After local assignment: test spam After nonlocal assignment: nonlocal spam After global assignment: nonlocal spam In global scope: global spam

Зверніть увагу, що призначення local (яке є типовим) не змінило прив’язки scope_test до spam. Призначення nonlocal змінило прив’язку scope_testдо спаму, а призначення global змінило прив’язку на рівні модуля.

Ви також можете побачити, що не було попереднього зв’язування для spam перед призначенням global .

9.3. Перший погляд на заняття¶

Класи представляють трохи нового синтаксису, три нових типи об’єктів і нову семантику.

9.3.1. Синтаксис визначення класу¶

Найпростіша форма визначення класу виглядає так:

class ClassName: statement-1> . . . statement-N> 

Визначення класів, як і визначення функцій (оператори def ), мають бути виконані, перш ніж вони матимуть будь-який ефект. (Імовірно, ви можете розмістити визначення класу в гілці оператора if або всередині функції.)

На практиці оператори всередині визначення класу зазвичай є визначеннями функцій, але допускаються й інші оператори, іноді корисні — ми повернемося до цього пізніше. Визначення функції всередині класу зазвичай мають особливу форму списку аргументів, продиктовану умовами виклику методів — знову ж таки, це пояснюється пізніше.

Коли вводиться визначення класу, створюється новий простір імен і використовується як локальна область — таким чином, усі призначення локальним змінним переходять до цього нового простору імен. Зокрема, визначення функції прив’язують тут назву нової функції.

When a class definition is left normally (via the end), a class object is created. This is basically a wrapper around the contents of the namespace created by the class definition; we’ll learn more about class objects in the next section. The original local scope (the one in effect just before the class definition was entered) is reinstated, and the class object is bound here to the class name given in the class definition header ( ClassName in the example).

9.3.2. Об’єкти класу¶

Об’єкти класу підтримують два типи операцій: посилання на атрибути та інстанціювання.

Посилання на атрибути використовують стандартний синтаксис, який використовується для всіх посилань на атрибути в Python: obj.name . Дійсні імена атрибутів — це всі імена, які були в просторі імен класу під час створення об’єкта класу. Отже, якби визначення класу виглядало так:

class MyClass: """A simple example class""" i = 12345 def f(self): return 'hello world' 

then MyClass.i and MyClass.f are valid attribute references, returning an integer and a function object, respectively. Class attributes can also be assigned to, so you can change the value of MyClass.i by assignment. __doc__ is also a valid attribute, returning the docstring belonging to the class: “A simple example class” .

Клас instantiation використовує нотацію функції. Просто уявіть, що об’єкт класу є функцією без параметрів, яка повертає новий екземпляр класу. Наприклад (припускаючи вищезгаданий клас):

створює новий примірник класу та призначає цей об’єкт локальній змінній x .

The instantiation operation («calling» a class object) creates an empty object. Many classes like to create objects with instances customized to a specific initial state. Therefore a class may define a special method named __init__() , like this:

def __init__(self): self.data = [] 

When a class defines an __init__() method, class instantiation automatically invokes __init__() for the newly created class instance. So in this example, a new, initialized instance can be obtained by:

Of course, the __init__() method may have arguments for greater flexibility. In that case, arguments given to the class instantiation operator are passed on to __init__() . For example,

>>> class Complex: . def __init__(self, realpart, imagpart): . self.r = realpart . self.i = imagpart . >>> x = Complex(3.0, -4.5) >>> x.r, x.i (3.0, -4.5) 

9.3.3. Об’єкти екземпляра¶

Тепер що ми можемо робити з об’єктами екземплярів? Єдиними операціями, які розуміють об’єкти екземплярів, є посилання на атрибути. Є два типи дійсних імен атрибутів: атрибути даних і методи.

data attributes correspond to «instance variables» in Smalltalk, and to «data members» in C++. Data attributes need not be declared; like local variables, they spring into existence when they are first assigned to. For example, if x is the instance of MyClass created above, the following piece of code will print the value 16 , without leaving a trace:

x.counter = 1 while x.counter  10: x.counter = x.counter * 2 print(x.counter) del x.counter 

Іншим типом посилання на атрибут екземпляра є метод. Метод — це функція, яка «належить» об’єкту. (У Python термін метод не є унікальним для екземплярів класу: інші типи об’єктів також можуть мати методи. Наприклад, об’єкти списку мають методи, що називаються додаванням, вставкою, видаленням, сортуванням тощо. Однак у наступному обговоренні, ми будемо використовувати термін метод виключно для позначення методів об’єктів екземпляра класу, якщо явно не вказано інше.)

Дійсні імена методів екземпляра об’єкта залежать від його класу. За визначенням, усі атрибути класу, які є функціональними об’єктами, визначають відповідні методи його екземплярів. Отже, у нашому прикладі x.f є дійсним посиланням на метод, оскільки MyClass.f є функцією, але x.i ні, оскільки MyClass.i не є таким. Але x.f — це не те саме, що MyClass.f — це об’єкт методу, а не об’єкт функції.

9.3.4. Об’єкти методу¶

Зазвичай метод викликається одразу після його зв’язування:

In the MyClass example, this will return the string ‘hello world’ . However, it is not necessary to call a method right away: x.f is a method object, and can be stored away and called at a later time. For example:

xf = x.f while True: print(xf()) 

продовжуватиме друкувати hello world до кінця часу.

What exactly happens when a method is called? You may have noticed that x.f() was called without an argument above, even though the function definition for f() specified an argument. What happened to the argument? Surely Python raises an exception when a function that requires an argument is called without any — even if the argument isn’t actually used…

Насправді, ви, можливо, здогадалися відповідь: особливість методів полягає в тому, що об’єкт екземпляра передається як перший аргумент функції. У нашому прикладі виклик x.f() точно еквівалентний MyClass.f(x) . Загалом, виклик методу зі списком n аргументів еквівалентний виклику відповідної функції зі списком аргументів, який створюється шляхом вставки об’єкта екземпляра методу перед першим аргументом.

In general, methods work as follows. When a non-data attribute of an instance is referenced, the instance’s class is searched. If the name denotes a valid class attribute that is a function object, references to both the instance object and the function object are packed into a method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.

9.3.5. Змінні класу та екземпляра¶

Загалом, змінні екземпляра призначені для даних, унікальних для кожного екземпляра, а змінні класу призначені для атрибутів і методів, спільних для всіх екземплярів класу:

class Dog: kind = 'canine' # class variable shared by all instances def __init__(self, name): self.name = name # instance variable unique to each instance >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.kind # shared by all dogs 'canine' >>> e.kind # shared by all dogs 'canine' >>> d.name # unique to d 'Fido' >>> e.name # unique to e 'Buddy' 

Як обговорювалося в Слово про імена та предмети , спільні дані можуть мати несподіваний ефект із залученням mutable об’єктів, таких як списки та словники. Наприклад, список tricks у наступному коді не слід використовувати як змінну класу, тому що лише один список буде спільний для всіх екземплярів Dog:

class Dog: tricks = [] # mistaken use of a class variable def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks # unexpectedly shared by all dogs ['roll over', 'play dead'] 

Правильний дизайн класу повинен використовувати замість змінної екземпляра:

class Dog: def __init__(self, name): self.name = name self.tricks = [] # creates a new empty list for each dog def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks ['roll over'] >>> e.tricks ['play dead'] 

9.4. Випадкові зауваження¶

Якщо однакове ім’я атрибута зустрічається як в екземплярі, так і в класі, пошук атрибутів визначає пріоритет екземпляру:

>>> class Warehouse: . purpose = 'storage' . region = 'west' . >>> w1 = Warehouse() >>> print(w1.purpose, w1.region) storage west >>> w2 = Warehouse() >>> w2.region = 'east' >>> print(w2.purpose, w2.region) storage east 

На атрибути даних можуть посилатися як методи, так і звичайні користувачі («клієнти») об’єкта. Іншими словами, класи не можна використовувати для реалізації чистих абстрактних типів даних. Фактично, ніщо в Python не дозволяє примусово приховувати дані — все базується на конвенції. (З іншого боку, реалізація Python, написана мовою C, може повністю приховати деталі реалізації та контролювати доступ до об’єкта, якщо це необхідно; це можна використовувати розширеннями Python, написаними мовою C.)

Клієнти повинні використовувати атрибути даних обережно — клієнти можуть зіпсувати інваріанти, які підтримуються методами, штампуючи свої атрибути даних. Зауважте, що клієнти можуть додавати власні атрибути даних до об’єкта екземпляра, не впливаючи на валідність методів, за умови уникнення конфліктів імен — знову ж таки, угода про іменування може позбавити тут багато головного болю.

Не існує скорочення для посилання на атрибути даних (або інші методи!) з методів. Я вважаю, що це фактично підвищує читабельність методів: немає жодного шансу сплутати локальні змінні та змінні екземпляра під час перегляду методу.

Часто перший аргумент методу називається self . Це не що інше, як конвенція: назва self не має абсолютно ніякого особливого значення для Python. Зауважте, однак, що через недотримання угоди ваш код може бути менш читабельним для інших програмістів на Python, і також можливо, що програма браузера класів може бути написана, яка спирається на таку угоду.

Будь-який функціональний об’єкт, який є атрибутом класу, визначає метод для екземплярів цього класу. Не обов’язково, щоб визначення функції було текстово укладено у визначення класу: присвоєння об’єкта функції локальній змінній у класі також є нормальним. Наприклад:

# Function defined outside the class def f1(self, x, y): return min(x, x+y) class C: f = f1 def g(self): return 'hello world' h = g 

Now f , g and h are all attributes of class C that refer to function objects, and consequently they are all methods of instances of C — h being exactly equivalent to g . Note that this practice usually only serves to confuse the reader of a program.

Методи можуть викликати інші методи, використовуючи атрибути методу аргументу self :

class Bag: def __init__(self): self.data = [] def add(self, x): self.data.append(x) def addtwice(self, x): self.add(x) self.add(x) 

Методи можуть посилатися на глобальні імена так само, як і звичайні функції. Глобальною областю, пов’язаною з методом, є модуль, що містить його визначення. (Клас ніколи не використовується як глобальна область видимості.) Хоча рідко можна зустріти вагому причину для використання глобальних даних у методі, існує багато законних застосувань глобальної області видимості: з одного боку, функції та модулі, імпортовані в глобальну область видимості, можуть використовуватися методами, а також функціями та класами, визначеними в ньому. Зазвичай клас, що містить метод, сам визначений у цій глобальній області видимості, і в наступному розділі ми знайдемо кілька вагомих причин, чому метод хоче посилатися на власний клас.

Кожне значення є об’єктом і, отже, має клас (також називається його типом). Він зберігається як object.__class__ .

9.5. Спадщина¶

Звичайно, функція мови не була б гідною назви «клас» без підтримки успадкування. Синтаксис для визначення похідного класу виглядає так:

class DerivedClassName(BaseClassName): statement-1> . . . statement-N> 

The name BaseClassName must be defined in a namespace accessible from the scope containing the derived class definition. In place of a base class name, other arbitrary expressions are also allowed. This can be useful, for example, when the base class is defined in another module:

class DerivedClassName(modname.BaseClassName): 

Виконання визначення похідного класу відбувається так само, як і для базового класу. Коли об’єкт класу створено, базовий клас запам’ятовується. Це використовується для вирішення посилань на атрибути: якщо запитуваний атрибут не знайдено в класі, пошук продовжується до базового класу. Це правило застосовується рекурсивно, якщо сам базовий клас є похідним від якогось іншого класу.

У створенні похідних класів немає нічого особливого: DerivedClassName() створює новий екземпляр класу. Посилання на метод розв’язуються наступним чином: виконується пошук відповідного атрибута класу, спускаючись вниз по ланцюжку базових класів, якщо необхідно, і посилання на метод є дійсним, якщо це дає об’єкт функції.

Похідні класи можуть перевизначати методи своїх базових класів. Оскільки методи не мають спеціальних привілеїв під час виклику інших методів того самого об’єкта, метод базового класу, який викликає інший метод, визначений у тому самому базовому класі, може призвести до виклику методу похідного класу, який замінює його. (Для програмістів C++: усі методи в Python фактично «віртуальні».)

Перевизначений метод у похідному класі може фактично захотіти розширити, а не просто замінити однойменний метод базового класу. Існує простий спосіб безпосередньо викликати метод базового класу: просто викличте BaseClassName.methodname(self, arguments) . Це також іноді корисно для клієнтів. (Зауважте, що це працює, лише якщо базовий клас доступний як BaseClassName у глобальній області.)

Python має дві вбудовані функції, які працюють із успадкуванням:

  • Використовуйте isinstance() , щоб перевірити тип екземпляра: isinstance(obj, int) буде True лише якщо obj.__class__ є int або якийсь похідний клас з int .
  • Використовуйте issubclass() , щоб перевірити успадкування класу: issubclass(bool, int) має значення True , оскільки bool є підкласом int . Однак issubclass(float, int) є False , оскільки float не є підкласом int .

9.5.1. Множинне успадкування¶

Python також підтримує форму множинного успадкування. Визначення класу з кількома базовими класами виглядає так:

class DerivedClassName(Base1, Base2, Base3): statement-1> . . . statement-N> 

For most purposes, in the simplest cases, you can think of the search for attributes inherited from a parent class as depth-first, left-to-right, not searching twice in the same class where there is an overlap in the hierarchy. Thus, if an attribute is not found in DerivedClassName , it is searched for in Base1 , then (recursively) in the base classes of Base1 , and if it was not found there, it was searched for in Base2 , and so on.

Насправді це трохи складніше; порядок вирішення методів динамічно змінюється для підтримки кооперативних викликів super() . Цей підхід відомий у деяких інших мовах із множинним успадкуванням як call-next-method і є потужнішим, ніж супервиклик у мовах з одним успадкуванням.

Динамічне впорядкування є необхідним, тому що всі випадки множинного успадкування демонструють один або більше ромбоподібних зв’язків (де принаймні до одного з батьківських класів можна отримати доступ через кілька шляхів із найнижчого класу). Наприклад, усі класи успадковуються від object , тому будь-який випадок множинного успадкування забезпечує більше ніж один шлях для досягнення object . Щоб запобігти повторному доступу до базових класів, динамічний алгоритм лінеаризує порядок пошуку таким чином, що зберігає порядок зліва направо, визначений у кожному класі, що викликає кожного батьківського елемента лише один раз, і це є монотонним (це означає, що клас може бути підкласом, не впливаючи на порядок пріоритету його батьків). У сукупності ці властивості дозволяють створювати надійні та розширювані класи з множинним успадкуванням. Для отримання додаткової інформації див. https://www.python.org/download/releases/2.3/mro/.

9.6. Приватні змінні¶

«Приватні» змінні екземпляра, до яких можна отримати доступ лише зсередини об’єкта, не існують у Python. Проте існує конвенція, якої дотримується більшість коду Python: ім’я з префіксом підкреслення (наприклад, _spam ) має розглядатися як непублічна частина API (незалежно від того, чи це функція, метод чи член даних). Це слід розглядати як деталь впровадження та може бути змінено без попередження.

Оскільки існує дійсний варіант використання для приватних членів класу (а саме, щоб уникнути зіткнень імен імен з іменами, визначеними підкласами), існує обмежена підтримка такого механізму, що називається name mangling. Будь-який ідентифікатор у формі __spam (принаймні два символи підкреслення на початку, не більше одного символу підкреслення в кінці) текстово замінюється на _classname__spam , де classname є поточною назвою класу з підкресленням на початку оголений. Це спотворення виконується без урахування синтаксичної позиції ідентифікатора, доки воно трапляється у визначенні класу.

Викривлення імен корисне для того, щоб дозволити підкласам перевизначати методи, не порушуючи виклики внутрішньокласових методів. Наприклад:

class Mapping: def __init__(self, iterable): self.items_list = [] self.__update(iterable) def update(self, iterable): for item in iterable: self.items_list.append(item) __update = update # private copy of original update() method class MappingSubclass(Mapping): def update(self, keys, values): # provides new signature for update() # but does not break __init__() for item in zip(keys, values): self.items_list.append(item) 

Наведений вище приклад працював би, навіть якби MappingSubclass вводив ідентифікатор __update , оскільки він замінений на _Mapping__update у Mapping класі _MappingSubclass__update у Клас MappingSubclass відповідно.

Зауважте, що правила псування призначені здебільшого для уникнення нещасних випадків; все ще можна отримати доступ або змінити змінну, яка вважається приватною. Це навіть може бути корисним за особливих обставин, наприклад, у налагоджувачі.

Зауважте, що код, переданий у exec() або eval() , не вважає назву класу викликаного класу поточним класом; це схоже на дію оператора global , дія якого так само обмежена кодом, який скомпільовано разом. Те саме обмеження стосується getattr(), setattr() і delattr(), а також прямого посилання на __dict__.

9.7. Обривки¶

Sometimes it is useful to have a data type similar to the Pascal «record» or C «struct», bundling together a few named data items. The idiomatic approach is to use dataclasses for this purpose:

from dataclasses import dataclass @dataclass class Employee: name: str dept: str salary: int 
>>> john = Employee('john', 'computer lab', 1000) >>> john.dept 'computer lab' >>> john.salary 1000 

A piece of Python code that expects a particular abstract data type can often be passed a class that emulates the methods of that data type instead. For instance, if you have a function that formats some data from a file object, you can define a class with methods read() and readline() that get the data from a string buffer instead, and pass it as an argument.

Instance method objects have attributes, too: m.__self__ is the instance object with the method m() , and m.__func__ is the function object corresponding to the method.

9.8. Ітератори¶

Наразі ви, мабуть, помітили, що більшість об’єктів-контейнерів можна зациклювати за допомогою оператора for :

for element in [1, 2, 3]: print(element) for element in (1, 2, 3): print(element) for key in 'one':1, 'two':2>: print(key) for char in "123": print(char) for line in open("myfile.txt"): print(line, end='') 

Цей стиль доступу є зрозумілим, лаконічним і зручним. Використання ітераторів пронизує й уніфікує Python. За лаштунками оператор for викликає iter() об’єкта контейнера. Функція повертає об’єкт-ітератор, який визначає метод __next__() , який отримує доступ до елементів у контейнері по одному. Коли елементів більше немає, __next__() викликає виняток StopIteration , який повідомляє циклу for про завершення. Ви можете викликати метод __next__() за допомогою вбудованої функції next() ; цей приклад показує, як це все працює:

>>> s = 'abc' >>> it = iter(s) >>> it >>> next(it) 'a' >>> next(it) 'b' >>> next(it) 'c' >>> next(it) Traceback (most recent call last): File "", line 1, in next(it) StopIteration 

Having seen the mechanics behind the iterator protocol, it is easy to add iterator behavior to your classes. Define an __iter__() method which returns an object with a __next__() method. If the class defines __next__() , then __iter__() can just return self :

class Reverse: """Iterator for looping over a sequence backwards.""" def __init__(self, data): self.data = data self.index = len(data) def __iter__(self): return self def __next__(self): if self.index == 0: raise StopIteration self.index = self.index - 1 return self.data[self.index] 
>>> rev = Reverse('spam') >>> iter(rev) >>> for char in rev: . print(char) . m a p s 

9.9. Генератори¶

Generators — простий і потужний інструмент для створення ітераторів. Вони написані як звичайні функції, але використовують оператор yield , коли вони хочуть повернути дані. Кожного разу, коли на ньому викликається next() , генератор продовжує роботу з того місця, де зупинився (він запам’ятовує всі значення даних і який оператор був виконаний останнім). Приклад показує, що генератори можна тривіально легко створити:

def reverse(data): for index in range(len(data)-1, -1, -1): yield data[index] 
>>> for char in reverse('golf'): . print(char) . f l o g 

Anything that can be done with generators can also be done with class-based iterators as described in the previous section. What makes generators so compact is that the __iter__() and __next__() methods are created automatically.

Інша ключова особливість полягає в тому, що локальні змінні та стан виконання автоматично зберігаються між викликами. Це зробило функцію легшою для написання та набагато зрозумілішою, ніж підхід із використанням змінних екземплярів, таких як self.index і self.data .

На додаток до автоматичного створення методу та збереження стану програми, коли генератори завершуються, вони автоматично викликають StopIteration . У поєднанні ці функції дозволяють легко створювати ітератори без зусиль, ніж написання звичайної функції.

9.10. Генератор виразів¶

Деякі прості генератори можна коротко закодувати як вирази за допомогою синтаксису, схожого на розуміння списків, але з круглими дужками замість квадратних. Ці вирази розроблені для ситуацій, коли генератор використовується одразу функцією охоплення. Вирази генератора є більш компактними, але менш універсальними, ніж повні визначення генераторів, і, як правило, більш зручні для пам’яті, ніж еквівалентні списки.

>>> sum(i*i for i in range(10)) # sum of squares 285 >>> xvec = [10, 20, 30] >>> yvec = [7, 5, 3] >>> sum(x*y for x,y in zip(xvec, yvec)) # dot product 260 >>> unique_words = set(word for line in page for word in line.split()) >>> valedictorian = max((student.gpa, student.name) for student in graduates) >>> data = 'golf' >>> list(data[i] for i in range(len(data)-1, -1, -1)) ['f', 'l', 'o', 'g'] 

За винятком одного. Об’єкти модуля мають секретний атрибут лише для читання під назвою __dict__ , який повертає словник, використаний для реалізації простору імен модуля; ім’я __dict__ є атрибутом, але не глобальним іменем. Очевидно, що використання цього порушує абстракцію реалізації простору імен, і його слід обмежити такими речами, як посмертні відладчики.

Table of Contents

  • 9. Класи
    • 9.1. Слово про імена та предмети
    • 9.2. Області та простори імен Python
      • 9.2.1. Приклад областей і просторів імен
      • 9.3.1. Синтаксис визначення класу
      • 9.3.2. Об’єкти класу
      • 9.3.3. Об’єкти екземпляра
      • 9.3.4. Об’єкти методу
      • 9.3.5. Змінні класу та екземпляра
      • 9.5.1. Множинне успадкування

      Які є класи обєктів

      В об’єктно-орієнтованій моделі дані та методи, що їх обробляють, об’єднуються в структури, які називаються об’єктами . Типи об’єктів називаються класами . З точки зору баз даних є такі важливі особливості об’єктно-орієнтованої моедлі (далі ООМ):

      • підтримка структур даних, що мають довільний рівень складності;
      • ідентифікованість та унікальність об’єктів;
      • належність об’єктів класам;
      • інкапсуляція;
      • успадкування та ієрархії класів;
      • поліморфізм.

      Складні структури даних. Складні об’єкти будуються з простіших за допомогою конструкторів. Найпростішими об’єктами є: числа, символи, символьні рядки довільної довжини, булеві змінні тощо. Існують різні конструктори складних об’єктів (кортежів, множин, мультимножин, списків та масивів). Мінімальний набір конструкторів, який повинна мати система, – це конструктори множин, списків і кортежів. Множини необхідні, оскільки завдяки їм набори об’єктів реального світу зображуються в природний спосіб. Кортежі надають спосіб зображення властивостей сутностей. Списки та масиви потрібні для відображення впорядкованості об’єктів реального світу, а також для зображення широкого кола математичних об’єктів.

      Будь-який конструктор має бути застосовним до будь-якого об’єкту (наприклад, повинна надаватися можливість побудови множини з масивів або масиву з множин). Конструктори реляційної моделі не мають такої властивості, оскільки конструкція множини може бути застосована лише до кортежів, а конструкція кортежу – лише до атомарних значень. Навіть реляційна модель, що підтримує ненормалізовані відношення (тобто відношення, які не перебувають у першій нормальній формі), не має вказаної властивості, оскільки конструкцією верхнього рівня завжди має бути відношення.

      Маніпулювання складними об’єктами забезпечується відповідними операціями, які часто розповсюджуються на всі компоненти таких об’єктів. Прикладом може бути вибирання чи видалення складного об’єкту або створення його копії. Існує можливість визначати додаткові операції над складними об’єктами.

      Ідентифікованість, унікальність і стан об’єктів. Кожний об’єкт є унікальним, тобто забезпечується унікальна ідентифікація об’єктів (для мов програмування унікальними ідентифікаторами можуть бути адреси пам’яті, за якими зберігаються об’єкти).

      Стан об’єкта — це поточне значення, приписане об’єкту. Об’єкт може мати єдиний стан протягом свого життєвого циклу або переходити з одного стану в інший. Оскільки об’єкти мають властивість інкапсуляції (що буде розглянута нижче), то стан об’єкта є абстракцією, яка визначається лише через його поведінку (методи).

      Унікальність об’єкта не залежить від його стану. Два об’єкти, що перебувають в одному й тому ж стані, є рівними, але не ідентичними. Не може існувати двох або більше екземплярів одного об’єкта (двох копій одного й того самого об’єкта з одним і тим самим ідентифікатором) У моделі з ідентифікованістю об’єктів об’єкт існує незалежно від свого значення. Отже, є два поняття еквівалентності об’єктів: об’єкти можуть бути ідентичними (бути одним і тим самим об’єктом) або вони можуть бути рівними (перебувати в одному й тому самому стані). У цьому контексті слід розглянути такі два аспекти: розрізнення та змінення об’єктів.

      Розрізнення об’єктів. У моделі з об’єктами, що ідентифікуються, два об’єкти можуть спільно використовувати компоненти (зокрема інші об’єкти). Отже, схематичним відображенням складного об’єкта є граф, натомість у системі без ідентифікованості об’єктів – це дерево.

      Розглянемо такий приклад: людина має ім’я, вік і дітей. Припустимо, що Іван і Марія мають сина на ім’я Петро. У реальному житті можуть мати місце дві ситуації: Іван і Марія є батьками однієї і тієї самої дитини або кожний із них має по сину. У моделі з ідентифікованістю об’єктів адекватно відображуються обидві ситуації.

      Зміна об’єктів. Припустимо, що Іван і Марія є батьками хлопчика на ім’я Петро. Тоді зміни, що стосуються сина Марії, будуть виконуватися з об’єктом Петро, відтак і з сином Івана.

      Поведінка об’єктів. Поведінка об’єкта це сукупність операцій (методів), які він надає. Лише через ці операції розкривається семантика об’єкта. Виконання операцій є єдиним способом взаємодії між об’єктами. Всі можливі операції об’єкта утворюють його інтерфейс. Лише використовуючи операції, можна змінити стан об’єкта.

      Класи об’єктів. В об’єктно-орієнтованій моделі клас узагальнює спільні риси об’єктів, що мають однакові властивості, й відповідає поняттю абстрактного типу даних. Клас означує спосіб реалізації множини об’єктів, встановлюючи їхню структуру, поведінку та інтерфейс, тобто спосіб запам’ятовування інформації про їхні стани. Проте власне стан має запам’ятовувати сам об’єкт.

      Клас є водночас фабрикою та сховищем об’єктів. Як фабрика об’єктів клас використовується для створення нових об’єктів. Термін сховище об’єктів означає, що до класу приєднується набір об’єктів, які є його екземплярами. Отже, класи використовуються для створення об’єктів і маніпулювання ними.

      Однією з основних властивостей класу, відтак і його об’єктів, є інкапсуляція.

      Інкапсуляція. Інкапсуляція вимагає, щоб дані та програмні коди для маніпулювання даними були приховані. З цієї точки зору об’єкт поділяється на інтерфейсну й реалізаційну частини. Інтерфейсна частина є специфікацією набору операцій, допустимих над об’єктом. Лише ця частина об’єкта видима для методів інших об’єктів. Реалізаційна частина складається з даних, що описують стан об’єкта, і процедур, що реалізують операції над об’єктом. Інкапсуляція специфікується на рівні оголошення класу.

      Успадкування. Успадкування є механізмом, що дає змогу створювати нові класи з використанням даних і методів інших класів. Це дає можливість деякі властивості, спільні для багатьох класів, описувати в базовому класі.

      Наприклад, якщо необхідно додати новий клас Одяг, що повністю збігається з класом Товар, за винятком наявності додаткових відомостей про розмір, мовою Java це можна записати так:

      public class Одяг extends Товар

      У такий спосіб ми вказали, що Одяг — це Товар, тому перший має всі дані й методи другого, а також власні дані, що зберігаються в змінній розмір. Успадкування дає змогу будувати ієрархію класів.

      Поліморфізм. Принцип поліморфізму є розширенням принципу успадкування й дає змогу переозначувати методи в успадкованих класах.

      Наприклад, є кілька різновидів товарів, для яких властиві специфічні правила обчислення ціни. Зокрема товарами можуть бути продукти, на які не діють націнки, та одяг, на який націнка встановлена. Всі товари успадковують метод Сумарна ціна() з базового класу Товар, але правила обчислення ціни можуть бути різними. Це означає, що кожний клас, який є нащадком класу Товар, може мати власну реалізацію методу Сумарна_ціна(). Тому вираз х. Сумарна ціна () може означати різні правила обчислення ціни залежно від того, екземпляром якого класу є об’єкт х.

      10.2 Мова опису об’єктів ODL ODMG

      Будь-яка СКБД має мову опису даних (МОД), що використовується для опису схем баз даних. Мова опису об’єктів ODL ODMG розглядається як розширення МОД, призначене для опису об’єктів, їхніх атрибутів, зв’язків та операцій. Основою цієї мови стала мова IDL (Interface Definition Language), розроблена групою OMG.

      Мова ODL є абстрактною в тому розумінні, що згенерована ODL-схема має бути незалежною від мов програмування та конкретної СКБД. У зв’язку з цим в ODL розглядаються лише аспекти означення об’єктів різних типів і повністю ігноруються питання реалізації методів. Згенерована ODL-схема може вільно переміщуватися між СКБД, що підтримують концепцію ODMG, використовуватися програмами, записаними різними мовами програмування, і навіть транслюватися в конкретні МОД, наприклад ті, які базуються на стандарті SQL-1999.

      10.2.1. Основні положення

      ODL – це мова, призначена насамперед для специфікації класів. Вона підтримує об’єктну модель ODMG і не є мовою програмування. Більше того, ODL незалежна від мов програмування. Основна мета розробки цієї мови – створити єдину основу для опису об’єктів і тим самим забезпечити перенесення схем об’єктних даних між різними ООСКБД.

      Основні положення об’єктної моделі даних ODMG:

      • базовим поняттям моделі є об’єкт;
      • поведінка об’єкта визначається за допомогою множини його операцій;
      • стан об’єкта визначається за допомогою множини його властивостей:
      • об’єкти належать до класів, саме через класи вони специфікуються;
      • клас має інтерфейсну та реалізаційну частини;
      • клас є об’єктом;
      • ODL специфікує класи.

      В об’єктній моделі ODMG мова йде насамперед про типи, а вже потім про класи. Клас розглядається як різновид типу, що має одну реалізацію. У загальному випадку допускається, що тип має кілька реалізацій. Множинність реалізацій типу необхідна для підтримки неоднорідних баз даних, розподілених у мережі, та середовищ з різними мовами програмування й компіляторами. Нас не пікавить реалізація, тому далі основна увага приділятиметься класам.

      10.2.2. Система типів ODL

      Базовими типами мови є: integer, float, string, boolean, перелічувані типи, що створюються за допомогою ключового слова enum, та класи. Похідні типи створюються за допомогою конструкторів типів. Є конструктор Struct для структур і чотири конструктори для типів колекцій: Set, Bag, List, Array. Опис структур і колекцій наведено далі.

      10.2.3. Об’єкти

      Основні характеристики об’єктів:

      • OID унікально ідентифікує об’єкт, відрізняючи його від інших об’єктів тієї предметної області, де він був створений. Будь-який об’єкт має лише один OID, але може мати більше одного імені. Об’єкти можуть ідентифікуватися предикатами, визначеними на їхніх властивостях.
      • Видалення об’єкта не призводить до рекурсивного видалення пов’язаних із ним об’єктів.

      На рис. 10.1 наведена класифікація об’єктів.

      Рисунок 10.1 – Класифікація об’єктів у ODL

      10.2.4. Літерали

      Літерали — це об’єкти, екземпляри яких не можна змінювати. Для наперед визначених типів літералів не можна змінювати операції. На рис. 10.2 наведена класифікація літералів.

      Рисунок 10.2 – Класифікація літералів

      10.3 Об’єктна мова запитів OQL ODMG

      OQL ODMG – це незалежна мова запитів до об’єктної моделі даних ODMG, синтаксис якої базується на мові SQL. Окрім того, передбачається можливість її використання в мовах програмування.

      Мова запитів орієнтована на побудову виразів, її конструкції мають такі властивості:

      • будь-який запит є виразом, що має тип — об’єкт або літерал;
      • вирази та операції над ними можуть вкладатися одне в одне;
      • результатом виконання запиту є об’єкти, що належать типам, означеним у моделі ODMG, і можуть брати участь у формуванні виразів.

      Мова має високорівневі примітиви для маніпулювання множинами, об’єктами, структурами, масивами і списками. У ній відсутні оператори оновлення, замість них використовуються операції, визначені для об’єктів. Передбачається, що всі створювані об’єкти мають OID, а літерали унікально ідентифікуються своїм значенням.

      10.3.1. Запити OQL

      В OQL запитом може бути будь-який вираз, що повертає об’єкт, колекцію об’єктів, літерал або колекцію літералів. Оскільки запит — це вираз, а будь-який вираз має тип, то й будь-якому запиту ставиться у відповідність тип.

      Найпоширенішим різновидом запиту, як і в SQL, є select-запит.

      Приведений нижче запит знаходить множину осіб чоловічої статі, й при цьому типом значення, що повертається, буде Bag:

      Зазначимо, що select-вираз повертає колекцію типу Bag, тобто мультимножину (множина зі значеннями, що повторюються). Використання слова DISTINCT приводить до того, що тип колекції, яка повертається, – Set, тобто множина. Наведений нижче запит повертає список номерів факультетів для людей на ім’я Pete, тобто літерал типу Set.

      SELECT DISTINCT x.faculty_id FROM persons x WHERE x.name = ‘Pete’

      Зауважте, що в запитах слід звертатися саме до екстентів, а не до класів.

      10.3.2. Обчислення проміжних результатів

      Запит можна не лише сформулювати для виконання, але й визначити для подальшого використання в інших запитах (певний аналог віртуальних таблиць, створюваних командою CREATE VIEW у мові SQL). Це досягається за допомогою означення запиту, що має такий вигляд:

      define ім’я_запиту as вираз

      define Joe as element (

      FROM students x

      WHERE x.name = ‘Joe’)

      Тут element є перетворювачем типу, що зводить тип одноелементної колекції до типу елементу. Отже, Joe є іменем об’єкта класу students із значенням атрибута name рівним ‘Joe’. Тепер до об’єкта Joe можна звернутися, наприклад, для отримання значень його атрибутів. Так, вирази Joe. В іrthdate. Joe.faculty_id. Joe.passport повертають дату народження, номер факультету і номер паспорта студента на ім’я Joe.

      Ім’я запиту розглядається як елементарний вираз, як і змінні, атоми та пойменовані об’єкти. Приклади елементарних виразів: 27, nil, students, Joe.

      10.4 Архітектура ООСКБД

      Є різні підходи до створення ООСКБД. Крім розробки власне об’єктно- орієнтованих СКБД, є численні способи поєднання об’єктно-орієнтованої та реляційної моделей даних. Усі ці підходи ми розглянемо далі.

      10.4.1. Розширення реляційних СКБД

      Реляційні СКБД надають можливість звертатися до них програмам, написаним різними, зокрема об’єктно-орієнтованими, мовами програмування. У цьому випадку об’єктно-орієнтовані прикладні програми виконують усі функції, пов’язані з відображенням об’єктної моделі в реляційну, тобто перетворюють об’єкти на структури даних, які можуть бути безпосередньо записані в табличні БД, підтримують властивості успадкування, інкапсуляції, зв’язування з об’єктами їхніх методів.

      РСКБД бере на себе єдину функцію – зберігання даних, які пов’язані з об’єктами, причому зберігання у вигляді реляційних таблиць, усе інше виконує прикладна програма.

      Даний підхід передбачає включення до складу РСКБД засобів, які полегшують процес відображення об’єктів у базі даних і маніпулювання ними. Тобто сама РСКБД удосконалюється, полегшуючи обробку об’єктів, але залишається при цьому реляційною. До можливих розширень РСКБД належать такі.

      • Надання можливості автоматичного створення унікальних ідентифікаторів кортежів відношень. Наприклад, в Огасlе є тип rowid, який виконує цю функцію. Такий тип сприяє вирішенню проблеми підтримки OID, хоча й не вирішує її повністю, оскільки OID має бути унікальним у всій базі даних, а не в межах одного відношення, як rowid.
      • Створення механізмів означення нових типів даних (такі механізми є в мовах програмування). Введення нових типів, навіть якщо вони відповідають досить складним структурам даних, не суперечить реляційній моделі. Інша річ, що реляційна модель в «чистому» вигляді не надає можливості працювати зі складеними структурами даних, вони розглядаються як атомарні.

      Перевага підходу, який базується на розширенні реляційних СКБД, полягає в тому, що надається можливість використовувати всю потужність реляційних систем баз даних. Недолік – слабка розвиненість засобів зображення об’єктів і маніпулювання ними, багато з цих функцій виконують прикладні програми.

      10.4.2. Створення самостійних ООСКБД

      Об’єктно-орієнтовані СКБД реалізують гнучку модель даних, яка базується на тій же парадигмі, що й об’єктно-орієнтовані мови програмування. ООБД забезпечують глибшу інтеграцію з об’єктно-орієнтованими додатками, ніж реляційні бази даних, і мінімізують обсяг роботи з програмування збереження і вибирання об’єктно-орієнтованих даних.

      Переваги використання однакових моделей у додатках та базі даних виявляються тоді, коли об’єктно-орієнтовані моделі є складними. Якщо ієрархія успадкування є багаторівневою, колекції перетворюють граф об’єкта на павутину, застосуванням складно використовувати поліморфізм та посилання, об’єктна база даних робить такі застосування меншими і їхня продуктивність підвищується. Це знижує витрати на написання і налагодження програмного коду й підвищує загальну продуктивність застосувань

      Самостійні ООСКБД забезпечують повну підтримку об’єктно-орієнтованої парадигми. Це передбачає безпосередню інтеграцію з об’єктно-орієнтованими мовами програмування, підтримку об’єктних типів, зв’язків між об’єктами та операцій бази даних, які інтерпретують об’єкти (найчастіше об’єкти інтерпретуються як записи) Прикладами об’єктно-орієнтованих операцій бази даних можуть бути: створення посилань на об’єкти і завантаження з бази даних об’єктів, на які є посилання, забезпечення блокування на об’єктному рівні й повернення об’єктів як результатів запитів або операцій з курсорами.

      Інкапсуляція. Стабільна (persistent) поведінка класу є незалежною від проектування бази даних, яке не потребує змінення реалізацій класів.

      Успадкування. Об’єкт інтерпретується одним записом незалежно від того, наскільки глибокою є ієрархія успадкування. Всі дані об’єкту запам’ятовуються в єдиному записі. Коли об’єкт вибирається з бази даних, усі дані базового класу та всі дані похідних класів завжди доступні.

      Поліморфізм. Концепція поліморфізму тісно пов’язана з успадкуванням. Стосовно об’єктно- орієнтованих баз даних це означає, що можна оперувати різними класами за допомогою спільного базового класу, одержуючи дані з об’єктів необхідного похідного класу. Тобто об’єкт базового класу може мати тип похідного класу. Якщо ви послідовно переглядаєте всі екземпляри базового класу, то будете одержувати всі об’єкти цього класу, незалежно від того, якого вони типу. Видалення екземпляра базового класу призводить до видалення всього об’єкту, включаючи дані похідних об’єктів.

      Ідентифікованість об’єкта. ООСБД поєднують ідентифікованість об’єкта в базі даних з ідентифіковашстю об’єкта в оперативній пам’яті. Якщо виконується збереження об’єкта, то ООСБД «знає», чи відповідає він об’єкту з бази даних (чи є він у базі даних), а під час вибирання об’єкта з бази даних – чи є він у пам’яті. Програмісту не потрібно власноруч підтримувати відповідність між об’єктами бази даних і об’єктами в пам’яті.

      Посилання на об’єкти. Самостійні ООСКБД дають можливість створювати в пам’яті посилання на об’єкти, а потім відображувати їх у базі даних і навпаки.

      Переваги та недоліки. Перевагою ООСКБД є їхня повна узгодженість із об’єктно-орієнтованою парадигмою програмування, що знімає всі проблеми, пов’язані зі зберіганням і маніпулюванням об’єктами у базі даних.

      Основний недолік пов’язаний з тим, що для самостійних ООСКБД слід вирішувати весь комплекс проблем, пов’язаних із СКБД, які вже вирішені в наявних реляційних СКБД.

      10.4.3. Об’єктно-реляційні СКБД

      Особливість даного підходу полягає в тому, що на базі наявних реляційних СКБД реалізується об’єктно-орієнтований інтерфейс. Робота з цим інтерфейсом здійснюється так само, як і в ООСКБД, але всі проблеми, пов’язані зі створенням і веденням баз даних, вирішуються в реляційній СКБД.

      Основна проблема, пов’язана зі створенням такого інтерфейсу, — відображення об’єктно-орієнтованої моделі в реляційну. Є кілька способів інтеграції об’єктного і реляційного підходів, що будуть розглянуті далі.

      Об’єктно-реляційний шлюз

      Об’єктно-реляційний шлюз автоматично виділяє об’єкти програми й зберігає їх у реляційній базі даних.

      Об’єктно-орієнтований додаток працює як звичайний користувач СКБД (рис. 10.3). Такий варіант дає змогу програмістам повністю сконцентруватися на об’єктно-орієнтованому проектуванні

      Рисунок 10.3 – Використання об’єктно-реляційного шлюзу

      Об’єктно-реляційний прошарок між об’єктною та реляційною СКБД

      У разі використання об’єктно-орієнтованого прошарку програма взаємодіє з БД за допомогою мови ООСКБД, а прошарок замінює всі об’єктно-орієнтовані елементи цієї мови на їхні реляційні еквіваленти (рис. 10.4).

      Рисунок 1 0 . 4 – Використання об’єктно-реляційного прошарку

      За це доводиться розплачуватися продуктивністю. Окрім іншого, прошарок має перетворювати об’єкти на набори зв’язаних відношень, генерувати унікальні OID об’єктів і передавати ці дані до реляційної БД.

      Уніфікована об’єктно-реляційна СКБД

      Цей підхід передбачає створення гібридних об’єктно-реляційних СКБД, що можуть зберігати як табличні дані, так і об’єкти. Вважається, що майбутнє саме за гібридними СКБД. Сьогодні розробники реляційних СКБД починають додавати до своїх продуктів об’єктно-орієнтовані засоби.