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