Python i obiektowość

Język Python ma swoje własne podejście do obiektowości. Znajdziemy w nim rozwiązania niespotykane w innych językach, jak np. lista __slots__. Inne zaś, jak na przykład znane z języka C++ czy JAVA przeciążanie operatorów, rozwiązuje się w magiczny sposób.

Lista __slots__

Od wersji 3.3 Pythona w każdej klasie można stworzyć listę __slots__. Lista ta definiuje, jakie pola może mieć dana klasa. Próba stworzenia pola o innej nazwie nie powiedzie się:

 1class Vec3:
 2
 3    # __slots__ = [ 'x','y','z']
 4
 5    def __init__(self, x,y,z):
 6        self.x = x
 7        self.y = y
 8        self.z = z
 9
10if __name__ == "__main__" :
11    v1 = Vec3(1.2, 3.4, 2.6)
12    v1.c = "blue"
13    print(v1.c)
14

Funkcje specjalne

Każdy obiekt w Pythonie ma zdefiniowane pewne funkcje niejako “z definicji”. Nazwy tych funkcji zaczynają i kończą się na __ (magic functions) i mają specjalne znaczenie. Zerknijmy na taki przykład:

x = 5
print(x)

v = Vec3(1.2, 3.9, 3.4)
print(v)

Dlaczego wydruk obiektu Vec3 wygląda mało przekonująco? Aby wyglądał inaczej, trzeba przedefiniować funkcję __str__()

 1class Vec3:
 2
 3    def __init__(self, x,y,z):
 4        self.x = x
 5        self.y = y
 6        self.z = z
 7
 8    def __str__(self) :
 9    	return "%.3f,%.3f,%.3f" % (self.x, self.y, self.z)
10    
11if __name__ == "__main__" :
12    v1 = Vec3(1.2, 3.4, 2.6)
13    print(v1)
14

Inną ciekawą możliwością jest przedefiniowanie operatorów matematycznych, takich jak +, * czy +=. Umożliwia to “naturalny” zapis opercji matematycznych na zdefiniowanych przez siebie obiektach, jak np. poniżej na liczbach zespolonych:

 1class Complex:
 2
 3    __slots__ = [ '__re','__im']
 4
 5    def __init__(self, re,im):
 6        self.__re = re
 7        self.__im = im
 8    
 9    def __str__(self) :
10        return "%f + %fi" % (self.__re, self.__im)
11
12    def __iadd__(self,c2) :
13        
14        if isinstance(c2, Complex) : # the interesting case when c2 is complex
15            self.__re += c2.__re
16            self.__im += c2.__im
17        else :
18            self.__re += c2 # the case when c2 is just a real value
19        return self
20
21if __name__ == "__main__" :
22    c1 = Complex(1.2, 3.0)
23    c2 = Complex(0.8, -2.0)
24    c1 += c2
25    print(c1)
26

Kolejnym operatorem wartym uwagi jest operator wywołania (call operator). Jego nadpisanie umożliwia wykorzystanie obiektu w dokładnie ten sposób jak funkcja.

1class FunctionLike:
2    def __call__(self, a):
3        print("I got called with %r!" % (a))
4
5fn = FunctionLike()
6fn(10)

Przykładów zastosowania jest mnóstwo; weźmy chociażby pole siłowe dynamiki molekularnej:

 1from Vec3 import Vec3
 2
 3class LJ:
 4
 5    __slots__ = ['__vi', '__vj', '__epsilon4','__sigma2']
 6
 7    def __init__(self,v_i, v_j, **params) :
 8        self.__vi = v_i
 9        self.__vj = v_j
10        self.__epsilon4 = params["epsilon"] * 4
11        self.__sigma2 = params["sigma"] * params["sigma"]
12
13    def __call__(self):
14        d2 = self.__sigma2 / self.__vi.distance_square_to(self.__vj)
15        d6 = d2*d2*d2
16        d12 = d6*d6
17
18        return self.__epsilon4 * (d12 - d6)
19
20v_1 = Vec3(0,0,0)
21v_2 = Vec3(8,1,0)
22en = LJ(v_1, v_2, epsilon = 0.04, sigma = 3.4)
23
24for i in range(0,100) :
25    v_1.x = v_1.x + 0.1
26    print(v_1.distance_to(v_2), en())
27

Metody i pola statyczne

Funkcje (i pola) statyczne istnieją nawet wtedy, kiedy nie utworzono ani jednego obiektu danej klasy. Jeżeli jednak obiekt już jest, to mają do niego pełen dostęp - jako pełnoprawny członek klasy. Zauważ, że składowe statyczne również mogą być prywatne lub publiczne.

  • w JAVA są konieczne: nie ma innej możliwości zdefiniowania funkcji

  • w Pythonie, C++ : ułatwiają organizację kodu.

Statyczne pole klasy w Pythonie implementujemy następująco:

 1class StaticR :
 2
 3    r = 50
 4    __slots__ = [ 'x', 'y' ] 
 5    def __init__(self,x,y) :
 6        self.x = x
 7        self.y = y
 8    
 9    @staticmethod
10    def scale_by(factor) :
11        StaticR.r *= factor
12
13c1 = StaticR(1,2)
14c2 = StaticR(3,4)
15#c1.r = 20      # This line should fail !!!
16print(c2.r)
17print(StaticR.r)
18StaticR.scale_by(1.2)
19print(c1.r)

Zamiast podsumowania

 1from math import sqrt
 2
 3class Vec3:
 4
 5    __slots__ = [ '__x','__y','__z']
 6  
 7    def __init__(self,*args) :
 8      
 9      if args is None or len(args) == 0:
10        self.__x, self.__y, self.__z = 0.0, 0.0, 0.0 # Default constructor
11      elif len(args) == 3 :                # support for Vec3(1.2,3.8,0.1)
12        self.__x, self.__y, self.__z = args[0], args[1], args[2]
13      elif len(args) == 1 :            
14        if isinstance(args[0], Vec3) :   # support for Vec3(v)
15          self.__x, self.__y, self.__z = args[0].__x, args[0].__y, args[0].__z
16        elif isinstance(args[0], list) : # support for Vec3( [1.2,3.8,0.1] )
17          self.__x, self.__y, self.__z = args[0][0], args[0][1], args[0][2]
18        else :                           # support for Vec3(0)
19          self.__x, self.__y, self.__z = args[0], args[0], args[0]
20    
21    @property
22    def x(self) : return self.__x
23
24    @property
25    def y(self) : return self.__y
26
27    @property
28    def z(self) : return self.__z
29
30    @x.setter
31    def x(self,x) : self.__x = x
32
33    @y.setter
34    def y(self,y) : self.__y = y
35
36    @z.setter
37    def z(self,z) : self.__z = z
38
39    def __str__(self) :
40        return "%.3f, %.3f, %.3f" % (self.__x, self.__y, self.__z)
41
42    def distance_square_to(self, vj):
43        d2 = (self.__x - vj.__x) * (self.__x - vj.__x)
44        d2 += (self.__y - vj.__y) * (self.__y - vj.__y)
45        return d2 + (self.__z - vj.__z) * (self.__z - vj.__z)
46
47    def distance_to(self, vj):
48        return sqrt(self.distance_square_to(vj))
49
50    def __isub__(self, v):
51        self.x -= v.x
52        self.y -= v.y
53        self.z -= v.z
54        return self
55
56    def __iadd__(self, v):
57        self.x += v.x
58        self.y += v.y
59        self.z += v.z
60        return self
61
62    def __add__(self, rhs):
63        v = Vec3(self)
64        v += rhs
65        return v
66
67    def __sub__(self, rhs):
68        v = Vec3(self)
69        v -= rhs
70        return v
71
72    def __imul__(self, rhs):
73        self.x *= rhs
74        self.y *= rhs
75        self.z *= rhs
76        return self
77
78
79if __name__ == "__main__" :
80    v0 = Vec3()
81    v1 = Vec3(1.2, 3.4, 2.6)
82    v1.z = 8.2
83    print(v1.x)
84    print(v1.y)
85    print(v1)
86    v2 = Vec3(-1.2, 0.4, 3.0)
87    print(v1 - v2)
88