Najlepszą odpowiedzią na gniew jest cisza.
Marek Aureliusz
W niedalekiej przeszłości zainteresował mnie temat tworzenia i inicjalizacji wielu instancji klas na podstawie danych otrzymanych formacie JSON i voilà. Pierwsze co przychodzi na myśl to mozolne dopasowywanie odpowiednich danych, do odpowiednich atrybutów obiektu. Tu górę wzięło lenistwo podpowiadając mi, że musi być szybszy i mniej pracochłonny sposób. Zagłębiając się w temat w jaki Python przechowuje dane klas i ich instancji natrafiłem na oświecającą wiedzę. Kolejny raz zaskoczyłem się jak wygodny w użyciu może być Python, a przynajmniej jak wygodny jest jest wygodny w mych zastosowaniach.
Nim zaczniemy zabawę na dobre potrzebny jest nam moduł z deklaracją prostej klasy na której instancjach przeprowadzimy eksperymenty pomagające zrozumieć co dzieje się pod maską.
#!/usr/bin/env python3
# -*- coding: 'utf-8' -*-
from datetime import datetime
import random
random.seed(datetime.now()) # Initialize seed with time
class Prototype(object):
    cls_var1 = "Class variable"
    cls_var2 = random.random()
    cls_var3 = ["Share", "It"]
    def __init__(self, var):
        self.instance_var1 = "Instance variable"
        self.instance_var2 = 3.14156
        self.instance_var3 = var
Na początku tworzymy dwie instancje naszej klasy Prototype:
P1 = Prototype(random.random())
P2 = Prototype(299792458)
Możemy teraz sprawdzić co zwierają słowniki opisujące instancje jak i klasę z
których zostały utworzone. Zawartość P1.__class__.__dict__ prezentuje się w
następujący sposób:
{'cls_var2': 0.462571030469879, 'cls_var3': ['Share', 'It'], '__module__':
'__main__',  'cls_var1': 'Class variable', '__dict__': <attribute '__dict__' of
'Prototype' objects>,   '__weakref__': <attribute '__weakref__' of 'Prototype'
objects>, '__doc__': None,  '__init__': <function __init__ at 0x7f61add5b1b8>}
Oprócz metod zdefiniowanych w klasie słownik P1.__class__.__dict__ umożliwia
bezpośredni dostęp do wszystkich zmiennych cls_var*. Rodzi się pytanie co ze
zmiennymi instance_var*? Otóż te przechowywane są w P1.__dict__. Rzućmy
okiem na zawartość wspomnianego słownika:
{'instance_var1': 'Instance variable', 'instance_var2': 3.14156,
'instance_var3': 0.5605434189263687}
Analogicznie wykonajmy inspekcje słowników naszej drugiej instancji.
P2.__class__.__dict__ prezentuje się w następujący sposób:
{'cls_var2': 0.462571030469879, 'cls_var3': ['Share', 'It'], '__module__':
'__main__',  'cls_var1': 'Class variable', '__dict__': <attribute '__dict__' of
'Prototype' objects>,  '__weakref__': <attribute '__weakref__' of 'Prototype'
objects>, '__doc__': None, '__init__': <function __init__ at 0x7f61add5b1b8>}
Słownik P2.__dict__:
{'instance_var1': 'Instance variable', 'instance_var2': 3.14156,
'instance_var3': 299792458} 
Ważna obserwacja to dostrzeżenie, że między instancjami różnice widoczne są
tylko w słownikach instancji, czyli P1.__dict__ i P2.__dict__, które
przechowują wartości zmiennych tworzonych w czasie inicjalizacji instancji.
Bazując na wiedzy jaką posiedliśmy będziemy eksplorować wspomniane obszary, poszukując możliwości jakie nam oferują. Krok pierwszy to poznanie sposobów w jakie możemy odczytać i zapisać zmienne instancji. Tu wyróżnić możemy dwa podejścia:
P1.instance_var1 = "New instance value"P1.__dict__["instance_var1"] = "New
  instance value"Wynik działań obu podejść jest tożsamy. Poza możliwością edycji i dobierania
się do zmiennych możemy wykorzystując którąkolwiek z metod tworzyć nowe
argumenty dla już istniejących instancji P1.instance_var1 = "New instance
variable"
Zupełnie inaczej wygląda korzystanie ze słownika P1.__class__.__dict__.
Traceback (most recent call last):
  File "lesson.py", line 59, in <module>
    P1.__class__.__dict__["cls_var1"] = "New value"
TypeError: 'dictproxy' object does not support item assignment 
W oczy powinien rzucić się nam typ dictproxy jakim jest słownik
P1.__class__.__dict__. Za przypisaniem wspomnianego typu do słownika klasy
stoi umożliwienie optymalizacji w interpreterze Pythona, oraz zapewnienie
stabilności. dictproxy wymusza tryb tylko do odczytu, dlatego też możemy
czytać dane natomiast próba zapisu wywołuje wyjątek.
W jaki sposób możemy, zmienić wartość zmiennej w słowniku klasy? Interpreter
Pythona zachowa się inaczej jeśli do atrybutu cls_var1 odwołamy się używając
notacji z kropką: P1.cls_var1 = "New value". Efekt działania nie do końca
będzie spełniał nasze oczekiwania. Szybka inspekcja słowników wszystko nam
wyjaśni:
{'cls_var1': 'New value', 'instance_var1': 'New instance
  variable', 'instance_var2': 3.14156, 'instance_var3': 0.9764029825441902}{'cls_var2': 0.5508205843860968, 'cls_var3':
  ['Share', 'It'], '__module__': '__main__', 'cls_var1': 'Class variable',
  '__dict__': <attribute '__dict__' of 'Prototype' objects>, '__weakref__':
  <attribute '__weakref__' of 'Prototype' objects>, '__doc__': None,
  '__init__': <function __init__ at 0x7f00c7cdf230>}Zmienna cls_var1 w słowniku klasy w dalszym ciągu pozostała niezmieniona, a
jedynie przesłonięta przez dodanie zmiennej o tej samej nazwie do słownika
instancji. Reasumując, referencje do obierków w słowniku klasy pozostają zawsze
stałe i mogą zostać jedynie przesłonięte.
Kończąc temat wspomnę o atrybucie __slots__. W wielkim skrócie zadeklarowanie
klasy w ten sposób:
class Prototype(object):
    __slots__ = ['x', 'y']
ograniczy zmienne instancji do zdeklarowanych x oraz  y.  Wyjście poza ten
zakres wywoła wyjątek:
Traceback (most recent call last):
  File "lesson.py", line 65, in <module>
    P3.z = 1
AttributeError: 'Prototype' object has no attribute 'z'