-
Notifications
You must be signed in to change notification settings - Fork 0
/
klasar.tex
601 lines (480 loc) · 35.9 KB
/
klasar.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
\chapterimage{chapters13.png} % Chapter heading image
\chapter{Klasar og hlutir}\index{Klasar og hlutir}\label{k:klasar}
Forritun snýst um að meðhöndla gögn og hingað til höfum við kynnst nokkrum gagnatögum (t.d. strengjum og listum).
Þær gegna mismunandi hlutverkum og bjóða upp á mismunandi aðgerðir til að vinna með gögnin.
Þessar innbyggðu týpur duga þó ekki alltaf og því er mikilvægt að vita að þegar við forritum getum við smíðað okkar eigin.
Þannig getum við aðlagað týpurnar okkar að þeim gögnum sem forritið okkar meðhöndlar og útfært okkar eigin aðferðir á þær.
Klasar gera forriturum kleift að skilgreina sína eigin hluti í flestum hlutbundnum málum og Python er hlutbundið forritunarmál.
Til þess að læra að búa til klasa þarf að átta sig á til hvers þeir eru nytsamlegir.
Gagnlegt er að hugsa sér klasa sem skilgreiningu eða uppskrift alveg eins og föll.
Skilgreiningin ein og sér gerir ekki neitt, það er ekki fyrr en við búum okkur til ákveðna útgáfu sem við getum farið að vinna með hana.
Gott dæmi um það er skilgreiningin á rétti á matseðli á veitingastað, textinn á matseðlinum er eingöngu til að útskýra hvað er í boði en er ekki útgáfa af matnum sjálfum.
Klasar eru hlutir sem hugsaðir eru til þess að búa til eintök af og geyma þannig eitthvert ástand og mögulega hafa áhrif á það.
Hugmyndin er að eiga hlut eða \emph{tilvik}, eina tiltekna útgáfu, sem má framkvæma aðgerðir á og eitthvað ástand hlutarins breytist eftir því hvað var gert.
Þannig er hægt að búa til mörg eintök af sama klasanum og láta hvert tilvik verða fyrir mismunandi áhrifum.\footnote{Athuga þarf sérstaklega gildissvið þegar klasar eru annars vegar, gildissvið í Python geta verið ögn ruglingsleg en við munum ekki beita klösum á það sérhæfðan máta að við lendum í miklum vandræðum.}
\phantom{easter egg}
%\begin{wrapfigure}{i}{0.2\textwidth} %i o r l
\begin{center}
\includegraphics[scale=0.6]{doodles31-10.png}
\end{center}
%\end{wrapfigure}
\section{Klasar skilgreindir}\index{Klasar skilgreindir}\label{uk:klasar-skilgreindir}
Klasar nota lykilorðið \textbf{class} og eru skilgreindir með því orði, allt sem tilheyrir klasanum er inndregið undir honum.
Nöfn klasa eru með stórum staf í Python eins og flestum hlutbundnum forritunarmálum.
Þetta gerir kóðalestur auðveldari fyrir fólk, forritarar ættu því að temja sér þessa hefð sem og aðrar nafnavenjur.
Ekki bara tölvur lesa kóða.
\begin{lstlisting}[caption=Klasinn Bíll skilgreindur, label=lst:klasar-skilgreindir-tegund]
class Bíll:
tegund = "Citroen"
fyrsti_billinn = Bíll()
print(fyrsti_billinn.tegund)
\end{lstlisting}
\lstset{style=uttak}
\begin{lstlisting}
Citroen
\end{lstlisting}
\lstset{style=venjulegt}
Hugsum okkur að við búum til skilgreiningu á bíl, hann þarf að vera af einhverri tegund, skoðum línur 1-2 í kóðabút \ref{lst:klasar-skilgreindir-tegund}.
Svo viljum við fá tilvik af skilgreiningunni í hendurnar (lína 4).
Þá búum við til breytu sem fær gildi eins og við höfum gert hundrað sinnum áður, nema núna er gildið sem breytan fær nafnið á klasanum okkar ásamt svigum eins og við séum að kalla í hann.
Prófið núna að búa til annað tilvik af klasanum \texttt{Bíll} án þess að nota svigana og prófið þá að prenta út það sem \texttt{type} skilar fyrir breyturnar tvær.
\begin{lstlisting}[caption=Klasinn Bíll skilgreindur og tvö tilvik búin til, label=lst:klasar-skilgreindir-subaru]
class Bíll:
tegund = "Citroen"
fyrsti_billinn = Bíll()
print(fyrsti_billinn.tegund)
annar_bill = Bíll()
annar_bill.tegund = "Subaru"
print(annar_bill.tegund)
\end{lstlisting}
\lstset{style=uttak}
\begin{lstlisting}
Citroen
Subaru
\end{lstlisting}
\lstset{style=venjulegt}
Breytan \texttt{fyrsti\_bilinn} kemur ekki í veg fyrir það að við getum átt fleiri bíla, en hún heldur utan um ástandið á nákvæmlega þessum bíl okkar.
Segjum að við fáum okkur svo annan bíl, þá getum við búið til aðra breytu (lína 6) í kóðabút \ref{lst:klasar-skilgreindir-subaru} fyrir annað tilvik af klasanum.
Bílarnir eru, fyrir okkur, óaðgreinanlegir í línu 6\footnote{Þar sem \texttt{\_\_eq\_\_} aðferðin hefur ekki verið útfærð þá er notast við \texttt{id()} fallið úr \texttt{type} klasanum sem klasinn okkar erfir bak við tjöldin.
Við skoðum erfðir betur seinna í kaflanum.} en það breytist svo snarlega þegar við endurskilgreinum \emph{klasabreytuna}\footnote{Í öðrum hlutbundnum málum er venjulega talað um klasafasta en í Python er auðvelt að breyta þeim svo það er við hæfi að nota annað orð en klasa\textbf{fasti}.} í línu 7, \texttt{tegund}.
Prófið nú að skipta um gildi á klasabreytunni \texttt{tegund} fyrir ykkar eigið tilvik af \texttt{Bíll}.
Þá skulum við skoða dálítið sérkennilegt fyrirbæri í Python, það er að við getum endurskilgreint klasabreyturnar okkar, sem hefur áhrif á öll tilvikin okkar.
Til að skoða það skulum við nota aftur kóðann úr kóðabút \ref{lst:klasar-skilgreindir-tegund}.
\begin{lstlisting}[caption=Endurskilgreining á því sem klasinn býður upp á, label=lst:klasar-skilgreindir-tegund2]
class Bíll:
tegund = "Citroen"
fyrsti_billinn = Bíll()
print(fyrsti_billinn.tegund)
Bíll.tegund = "Volvo"
print(fyrsti_billinn.tegund)
\end{lstlisting}
\lstset{style=uttak}
\begin{lstlisting}
Citroen
Volvo
\end{lstlisting}
\lstset{style=venjulegt}
Hér sjáum við hvernig tilvikið okkar, \texttt{fyrsti\_billinn}, af bílaklasanum breytist.
Í línu 5 er \texttt{tegund} 'Citroen' en í línu 7 er það orðið að 'Volvo'.
Þetta gerist því að tilvikið okkar er af þessum klasa og klasinn breytist í línu 6.
Við endurskilgreindum klasann og því breytast öll tilvik af honum í samræmi.
Athugum að ef við gerum tvær breytur sem við getum unnið með sem eru af bílaklasanum og ef við ættum aðferð á borð við að fylla á rúðuvökva, sem hefur áhrif á tilvik af klasanum, þá getum við kallað í aðferðina á þann bíl sem okkur hentar, án þess að það hafi áhrif á hinn.
En þessi skilgreining innihélt engar aðferðir, við sjáum það í hluta \ref{uk:klasar-aðferðir}.
\comment{
--------------
Við munum svo beita klösum á hnitmiðaðri máta með svo kölluðum \textit{töfra aðferð} (e. magic method, double underscore method, eða dunder method\footnote{Þarna er orðunum double og under skeytt saman í dunder}) og skoða hvernig eigi að útbúa hlut með ákveðnum grunnupplýsingum.
Áður en við höldum svo langt skulum við byrja á að skoða orðið \texttt{self} sem er ekki frátekið lykilorð heldur er það hefð og venja að nota það orð til að segja klasanum að nú sé hann að nota sig sjálfan (sjá endurkvæmni í kafla \ref{k:reiknirit}).\footnote{Skipta má út orðinu \texttt{self} fyrir hvað sem er, en það þarf þá að halda samhengi, best er að venja sig á nota \texttt{self} svo kóðinn verði læsilegri.}
-----------
}
\comment{
------------------------
Við sáum þetta í \ref{lst:klasar-self} þegar búið að er að setja fall inn í klasann, við munum hvernig föll eru skilgreind úr kafla \ref{k:föll}, hvernig þau vinna með viðföng og hvernig á að kalla í þau.
\begin{lstlisting}[caption=Klasar , label=lst:klasar-self]
class Tala():
x = 5
def leggja_saman(self, x):
print(self.x + x)
talan_min = Tala()
talan_min.leggja_saman(6)
\end{lstlisting}
\lstset{style=uttak}
\begin{lstlisting}
11
\end{lstlisting}
\lstset{style=venjulegt}
Við sjáum að fallið tekur við tveimur viðföngum \texttt{self} og \texttt{x}.
Tökum eftir hvernig breytan \texttt{t} er skilgreind í kóðabút \ref{lst:klasar-skilgreindir2}, hún er skilgreind eins og hvaða önnur breyta sem við höfum búið til áður.
En það sem kemur hinu megin við jafnaðarmerkið er eins og verið sé að kalla í fall.
Eina sem gefur til kynna að þetta sé ekki fall er að Klasi er með stórum staf.
Ef við gleymum að gera svigana þá fáum við ekki eintak af klasanum til að vinna með heldur fáum við nýja vísun á klasann sjálfan.
Það er við erum með nýtt nafn sem gerir það sama og breytan \texttt{Tala} gerir, annan vísi á \texttt{Tala} en ekki útgáfu til að vinna með.
Það er nafnavenja í Python að klasar séu nefndir með stórum staf, það auðveldar lestur fyrir mannfólk.
Þá sjáum við að í línu 7 er kallað í aðferðina \texttt{leggja\_saman}, hún tekur við einu viðfangi.
En ef við skoðum skilgreininguna á aðferðinni þá eru þar skilgreind tvö viðföng.
Fyrra viðfangið \texttt{self} er þarna notað fyrir klasann til að vita að það sé verið að tala um hann sjálfan, svo þarna inni eru tvö mismunandi x.
Fyrra x-ið er úr línu 2 og seinna x-ið er úr viðfanginu.
Þetta getur verið ruglandi en við munum sjá fleiri dæmi um þetta og vonandi verður þetta skýrara.
----------------
}
\section{Tilviksbreytur}\index{Tilveiksbreytur}\label{uk:klasar-tilviksbreytur}
Nú höfum við séð hvernig hægt er að búa til tilvik af klasa, en klasinn úr kóðabút \ref{lst:klasar-skilgreindir-tegund2} er sérstaklega ber og gagnlítill.
En hvers eru klasar megnugir?
\phantom{easter egg}
%\begin{wrapfigure}{i}{0.2\textwidth} %i o r l
\begin{center}
\includegraphics[scale=0.6]{doodles31-25.png}
\end{center}
%\end{wrapfigure}
Athugum eftirfarandi samlíkingu áður en lengra er haldið.
Þegar við förum á veitingastað þá er okkur boðinn ákveðinn matseðill, við fáum að vita að það séu þrír réttir á matseðlinum (þrír klasar) og í þeim réttum eru ákveðin hráefni (tilviksbreytur).
Þegar við pöntum okkur mat fáum við í hendurnar eitt tiltekið tilvik af skilgreiningunni á matseðlinum (tilvik af klasa).
Nú eru hráefnin kannski ekki okkur að skapi og við viljum fá að hafa áhrif á þau hráefni fara í réttinn okkar (okkar tiltekna tilvik) svo við gefum upp hvað við viljum fá (inntak) sem skilar sér í okkar tiltekna rétti (úttak).
Í þessari samlíkingu er matreiðslufólkið smiðurinn á bak við klasann, í kóðabút \ref{lst:klasar-notkun} er aðferðin \texttt{\_\_init\_\_} sá smiður.
Aðferðin smíðar fyrir okkur tilvik af klasanum með því inntaki sem hún fær.
\begin{lstlisting}[caption=Klasar skilgreindir með töfraaðferðinni \_\_init\_\_, label=lst:klasar-notkun]
class Samloka():
def __init__(self, sosa, alegg):
self.sosa = sosa
self.alegg = alegg
samlokan_min = Samloka('bbq', ['skinka', 'ostur', 'paprika'])
class Samloka_med_skinku():
def __init__(self, sosa = "", alegg = ['skinka']):
self.sosa = sosa
self.alegg = alegg
skinku_samloka = Samloka_med_skinku('bbq')
\end{lstlisting}
Í fyrri klasanum, \texttt{Samloka}, verðum við að gefa upp inntak fyrir \texttt{sosa} og \texttt{alegg} þegar við búum okkur til hlut því annars fáum við villu.
En í seinni klasanum, \texttt{Samloka\_med\_skinku}, en þá eru tilviksbreyturnar með sjálfgefin gildi.
Þetta rímar ágætlega við raunheiminn, þar sem við verðum að tilgreina hvað við meinum með ,,samloka“ en ,,skinkusamloka“ er mun afmarkaðra.
Samlíkingin okkar með samlokur á veitingastað er ágæt en nú skulum við skoða hvað er eiginlega í gangi í kóðabút \ref{lst:klasar-notkun}.
Fyrir það fyrsta er klasinn núna skilgreindur sem \texttt{Samloka()} með svigum, það var ekki þannig í kóðabútum \ref{lst:klasar-skilgreindir-tegund}-\ref{lst:klasar-skilgreindir-tegund2}.
\begin{itarefni}
\textbf{Svigar eða ekki svigar?}\\
Ástæðan fyrir því að svigar eru valkvæmir er svipuð og í kafla \ref{k:segðir} þar sem mátti sleppa svigum utan um segðir fyrir skilyrðissetningar, nema það væri þörf á þeim til útreiknings.
Klasar eiga möguleika á að \textbf{erfa} (e. inherit) frá öðrum klösum, við munum tala um það í undirkafla \ref{uk:klasar-erfðir}, og þeir erfa í grunninn allir frá klasanum \textit{Object}.
Það sem tómur svigi þýðir (eða að sleppa sviganum alfarið) er að klasi erfir ekki frá öðrum klasa.
Það er því upp á einstaklinginn komið að venja sig á að gera alltaf annað hvort, höfundur hefur vanið sig á tóma sviga en er það enginn heilagur sannleikur.
\end{itarefni}
Næsta sem við þurfum að athuga er \emph{töfraaðferðin}\footnote{Töfraaðferðir (e. magic methods, double underscore methods, dunder methods (þarna er orðunum double og under skeytt saman í dunder)) eru hópur aðferða sem Python býður upp sem staðlað viðmót sem gera forriturum kleift að nýta grunnvirkni, eins og samanburður með samanburðarvirkja.} \texttt{\_\_init\_\_} og orðið \emph{self}.
Orðið self eitt og sér er ekki lykilorð, það má skipta því út fyrir eitthvað annnað.
Hins vegar hefur komist ákveðin venja á að nota það orð og gerir það kóða læsilegri að halda sig við það.
En hvað gerir orðið self?
Þetta orð er breyta sem inniheldur tilvik af klasanum sjálfum, í okkar tilfelli er það \texttt{samlokan\_min} í línu 6.
Þetta skýrist kannski þegar við skoðum endurkvæmni í kafla \ref{k:reiknirit}.
Í klasanum \texttt{Samloka} eru viðföngin \texttt{sosa} (sem við búumst við að sé strengur án þess að athuga það neitt sérstaklega, sjá kafla \ref{k:villur} um hvernig má taka á því) og \texttt{alegg} (sem við búumst við að sé listi af strengjum).
Ef notandinn gefur okkur ekkert inntak við gerð samlokunnar er ekki hægt að búa til tilvik af samlokunni, því \texttt{\_\_init\_\_} aðferðin, \emph{smiðurinn}, býst við tveimur stöðubundnum viðföngum og getur ekkert gert án þeirra nema skila villu að svo stöddu.
Þegar við skilgreindum \texttt{samlokan\_min} sögðum við smiðnum að við ætluðum að eiga eitt stykki samloku með bbq sósu, skinku, osti og papriku.
Þannig inniheldur \texttt{sosa} núna strenginn \texttt{bbq} fyrir þetta tiltekna tilvik af klasanum og \texttt{alegg} þennan tiltekna lista af áleggstegundum.
Það þriðja og kannski það flóknasta er að \texttt{init} aðferðin í klasanum \textit{Samloka\_med\_skinku} tekur við nefndum viðföngum, eins og við sáum í kafla \ref{uk:föll-sjálfgefin}, sem hafa einhver tiltekin gildi nú þegar skilgreind.
Það þýðir að við getum búið til einhverja óbreytta, staðlaða, sjálfgefna skinkusamloku.
Við þurfum ekki að gefa neitt upp til þess að fá tilvikið í hendurnar, en ef okkur langar hins vegar til þess að fá samloku með einhverri sósu og einhverju öðru áleggi þurfum við að gefa það upp og við getum gert það alveg eins og þegar við notum föll með sjálfgefnum/nefndum viðföngum.
\phantom{easter egg}
%\begin{wrapfigure}{i}{0.2\textwidth} %i o r l
\begin{center}
\includegraphics[scale=0.45]{doodles31-08.png}
\end{center}
%\end{wrapfigure}
\section{Aðferðir}\index{Aðferðir}\label{uk:klasar-aðferðir}
Við þekkjum aðferðir, við höfum séð þær notaðar á týpurnar sem við þekkjum, eins og \texttt{.capitalize()} á strengi, \texttt{.sort()} á lista og \texttt{.get("x", "y")} á orðabækur.
Aðferðir eru í raun föll sem eru skilgreind inni í klösum og verka á hlutinn sem klasinn skilgreinir (til upprifjunar sjá kafla \ref{uk:strengjaaðferðir}).
Nú ætlum við að skilgreina okkar eigin aðferðir á hlutina okkar.
Við ætlum að skoða aðferðir með tilliti til rafbíla.
Það sem við viljum geta gert þegar við búum til tilvik af rafbíl er að segja af hvaða tegund hann er, hvaða árgerð hann er af, hversu mikla drægni hann hefur á 100km, hversu margar kílówattstundir rafhlaðan er og hversu marga kílómetra er búið að aka bílnum.
\begin{lstlisting}[caption=Klasaaðferðir á rafbílaklasa, label=lst:klasar-aðferðir1]
class Rafbill():
def __init__(self, tegund, model, draegni = 16.7, kws = 40, akstur = 0):
self.tegund = tegund
self.argerd = argerd
self.eydsla = draegni/100 # hversu mörgum kw stundum bíllinn eyðir á 1 km
self.kws = kws # hversu mikil hleðsla kemst fyrir
self.akstur = akstur # km sem hafa verið eknir
def keyra_km(self, km):
self.akstur += km
self.kws -= self.eydsla * km
def hlada_bilinn(self, kw):
self.kws += kw
\end{lstlisting}
Við viljum að það að aka bílnum ákveðna kílómetra hafi áhrif á stöðu rafhlöðunnar.
Við viljum líka geta hlaðið bílinn.
En eins og sést í kóðabút \ref{lst:klasar-aðferðir1} þá er hægt að hlaða bílinn endalaust og það er hægt að keyra hann endalaust líka.
Við settum engin takmörk á það hvað má keyra marga kílómetra, við höldum bara áfram að lækka hleðsluna og við leyfðum okkur svo að hlaða bílinn langt umfram það hversu margar kílówattstundir komast fyrir í rafhlöðunni.
Einnig er galli á þessum klasa að engin leið er til að halda utan um hvert hámark hleðslu rafhlöðunnar er.
En þetta dugar til að sýna fram á hvernig aðferðir eru skilgreindar, hvernig á að kalla í þær, hvernig þær hafa áhrif á tilviksbreyturnar okkar og svo hvernig má kalla í tilviksbreyturnar til að sjá áhrifin.
Takið sérstaklega eftir því í línu 14 að \texttt{kw} og \texttt{self.kw} er ekki það sama, eins og kom fram áður er \texttt{self} að vísa í tilvik af klasanum og klasi af þessu tagi hefur \texttt{kws} sem tilviksbreytu.
Hér er þá verið að vísa í þær kw-stundir sem tilvikið bjó yfir þegar kallað var í aðferðina en seinna viðfangið, staka \texttt{kws}, er fengið frá notandanum sem kallaði í aðferðina.
Núna er það bara hugmynd að einhver muni á endanum gera það, svo sjáum við í kóðabút \ref{lst:klasar-aðferðir1-2} hvernig það er gert.
Aðferðir þurfa þó ekki endilega að hafa áhrif á tilvikið okkar heldur geta skilað okkur til baka einhverri niðurstöðu, eins og flestar aðferðir á strengi (því við munum að strengir eru óbreytanlegir).
Engin aðferðanna í þessum klasa skilaði nokkurri niðurstöðu.
En skoðum þó hvernig nota má aðferðirnar á eitthvað tiltekið tilvik.
\begin{lstlisting}[caption=Tilvik af rafbílaklasanum búið til og notað, label=lst:klasar-aðferðir1-2]
rafbill = Rafbill('Rafio', 2021)
rafbill.keyra_km(500)
print(rafbill.akstur)
rafbill.hlada_bilinn(900)
print(rafbill.kws)
rafbill.tegund = "Oiarf"
print('nú er bíllinn af tegundinni', rafbill.tegund)
rafbill.keyra_km(500)
print('nú er bíllinn búinn að keyra', rafbill.akstur)
\end{lstlisting}
\lstset{style=uttak}
\begin{lstlisting}
500
856.5
nú er bíllinn af tegundinni Oiarf
nú er bíllinn búinn að keyra 1000
\end{lstlisting}
\lstset{style=venjulegt}
Hérna er kallað í báðar aðferðirnar sem við skilgreindum í kóðabút \ref{lst:klasar-aðferðir1} með ákveðnu inntaki.
Takið einnig eftir að \texttt{rafbill} og \texttt{Rafbill} er ekki það sama, í Python skiptir máli hvort eru notaðir hástafir eða lágstafir.
Tökum nú nýtt dæmi þar sem við skoðum ímyndað lestarkerfi á Íslandi.
Í þessu dæmi höldum við utan um tvennt með klösum, annars vegar lestarstöðvar sem hafa nöfn og eru í ákveðinni fjarlægð frá upphafsstöðinni á leið sinni og hins vegar lestar sem eru á ákveðinni leið og eru staddar á ákveðinni stöð.
Í kóðabút \ref{lst:klasar-aðferðir-lestar} sjáum við hvernig aðferðir geta skilað einhverju án þess að hafa áhrif á tilviksbreytur og við sjáum einnig að smiðurinn \texttt{\_\_init\_\_} tekur bara við tveimur breytum frá notanda en skilgreinir þrjár tilviksbreytur.
Þetta er vegna þess að klasinn býður notandanum ekki að hafa áhrif á þessa breytu við smíð klasans.
Notandinn verður því að fá í hendurnar við grunnstillingu lest sem hefur ekki ferðast neitt.
\begin{lstlisting}[caption=Aðferðir kynntar með lestarkerfi, label=lst:klasar-aðferðir-lestar]
class Stod():
def __init__(self, nafn, fjarlaegd):
self.nafn = nafn
self.fjarlaegd = fjarlaegd
class Lest():
def __init__(self, leid, byrjunar_stod):
self.leid = leid
self.nuverandi_stod = byrjunar_stod
self.farnir_km = 0
def fara_til_numer(self, numer):
return abs(self.leid[numer].fjarlaegd - self.nuverandi_stod.fjarlaegd)
def fara_til_stod(self, stod):
return abs(stod.fjarlaegd - self.nuverandi_stod.fjarlaegd)
def fara_til_stodvarnafn(self, stodvarnafn):
for stod in self.leid:
if(stod.nafn == stodvarnafn):
return abs(stod.fjarlaegd - self.nuverandi_stod.fjarlaegd)
\end{lstlisting}
Skoðið hér að þrjár aðferðir eru til þess að segja til um fjarlægð lestar frá stöð og það er til þess fallið að sýna að oft eru margar leiðir til þess að leysa verkefni, sérstaklega þegar þau verða flóknari.
Aðferðirnar taka við mismunandi inntaki en þær skila allar sömu niðurstöðu, hversu langt er milli lestar og stöðvar.
Síðasta aðferðin er frábrugðin að því leyti að hún færir lestina, framkvæmir breytingu á ástandi lestarinnar.
\begin{lstlisting}[caption=Tilvik af lestum og stöðvum búin til og notuð, label=lst:klasar-aðferðir-lestar-2]
reykjavik = Stod("Reykjavík", 0)
borgarnes = Stod("Borgarnes", 76)
akureyri = Stod("Akureyri", 388)
egilsstadir = Stod('Egilsstaðir', 636)
leid1 = [reykjavik, borgarnes, akureyri, egilsstadir]
lest1 = Lest(leid1, reykjavik)
print(lest1.fara_til_numer(3))
print(lest1.fara_til_stod(egilsstadir))
print(lest1.fara_til_stodvarnafn('Egilsstaðir'))
print(lest1)
\end{lstlisting}
\lstset{style=uttak}
\begin{lstlisting}
636
636
636
<__main__.Lest object at 0x7f29f845fb50>
\end{lstlisting}
\lstset{style=venjulegt}
Gott er að sjá að allar aðferðirnar skiluðu sömu niðurstöðunni þegar spurt var hve langt væri frá Reykjavík til Egilsstaða, en af hverju fengum við svona ljóta útprentun þegar við prentuðum út lestina okkar?
Sjáum hvernig við leysum það í næsta kafla.
\section{Töfraaðferðir}\index{Töfra aðferðir}\label{uk:klasar-töfra-aðferðir}
\begin{wrapfigure}{i}{0.15\textwidth} %i o r l
\begin{center}
\includegraphics[scale=0.3]{doodles31-01.png}
\end{center}
\end{wrapfigure}
Nú höfum við séð hvernig á að skilgreina okkar eigin aðferðir á klasa og við höfum verið að nota eina töfraaðferð til þess að smíða klasana okkar, \texttt{init}.
En það er til mýgrútur af töfraaðferðum sem við getum nýtt okkur til þess að gera klasana okkar nothæfari.
Í þessum kafla verða nokkrar slíkar teknar fyrir (en alls ekki allar).
Við munum að töfraaðferðir eru aðferðir sem eru með tveimur undirstrikum fyrir framan sig og aftan og gegna því hlutverki að útfæra innbyggða virkni.
\phantom{}
\begin{wrapfigure}{o}{0.15\textwidth} %i o r l
\begin{center}
\includegraphics[scale=0.3]{doodles31-02.png}
\end{center}
\end{wrapfigure}
\vspace{10pt}
Helst ber að nefna \texttt{\_\_str\_\_} aðferðina, sem nemendur vilja oftast geta beitt strax og skilja ekki hvers vegna print skilar einhverju furðulegu.
Hingað til höfum við ekki verið að beita innbyggða fallinu print á klasana okkar í kóðabútum því að hún gerir ekkert skilmerkilegt enn þá (eins og í úttaki kóðabúts \ref{lst:klasar-aðferðir-lestar-2}).
Til þess að hún geri það þurfum við að útfæra töfraaðferðina \texttt{\_\_str\_\_}.
Það sem sú aðferð þarf að gera er að skila streng.
Nú er það upp á okkur komið hvað okkur finnst vera nógu merkilegar upplýsingar til þess að setja í strenginn sem á að prenta.
Þegar við beitum \texttt{print} fallinu höfum við hingað til verið að skoða úttak sem er af einhverri týpu sem við þekkjum, heiltölur eða strengir til dæmis.
En nú þegar við erum með okkar eigin klasa/hluti viljum við kannski fá einhverjar tilteknar upplýsingar í ákveðinni röð.
\begin{wrapfigure}{i}{0.15\textwidth} %i o r l
\begin{center}
\includegraphics[scale=0.3]{doodles31-03.png}
\end{center}
\end{wrapfigure}
Skoðum kóðabút \ref{lst:klasar-str} þar sem við skilgreinum klasa sem heldur utan um tölvuleikjapersónuna okkar aftur, en við ætlum þó að sleppa aðferðunum að sinni og bæta við nokkrum klasabreytum.
Klasabreytur eru skilgreindar efst í klasa og er nafnavenjan með þá að nota eingöngu hástafi.
Það sem klasabreytur gera fyrir okkur er að halda utan um breytur sem við viljum að séu aðgengilegar allsstaðar í klasanum, við viljum ekki endilega að þær séu hluti af inntaki frá notanda við smíð klasans og hástafirnir gera yfirferð og prófun klasans auðveldari.
Með auðveldari prófunum er átt við að gildi séu ekki harðkóðuð víðsvegar og erfitt að skipta þeim út (eins og ef nota ætti ákveðna námundun á pí) heldur eru þau skilgreind á einum stað og auðvelt að átta sig á notkun þeirra (ef breytuheitin eru skýr).
\begin{lstlisting}[caption=Töfraaðferðin \_\_str\_\_, label=lst:klasar-str]
class Leikur():
HAMARKS_LIF = 100
LAGMARKS_LIF = 0
HAMARKS_PENINGUR = 9999
LAGMARKS_PENINGUR = -9999
def __init__(self, nafn, peningur, lif):
self.nafn = nafn
if(lif > self.HAMARKS_LIF or lif < self.LAGMARKS_LIF):
self.lif = 100
else:
self.lif = lif
if(peningur > self.HAMARKS_PENINGUR or peningur < self.LAGMARKS_PENINGUR):
self.peningur = 0
else:
self.peningur = peningur
def __str__(self):
return "Persónan heitir {} og á {} gullpeninga og hefur {} í líf".format(self.nafn, self.peningur, self.lif)
valborg = Leikur('Valborg', 200, 90)
groblav = Leikur('Groblav', 1000000, -44444)
print(valborg)
print(groblav)
\end{lstlisting}
\lstset{style=uttak}
\begin{lstlisting}
Persónan heitir Valborg og á 200 gullpeninga og hefur 90 í líf
Persónan heitir Groblav og á 0 gullpeninga og hefur 100 í líf
\end{lstlisting}
\lstset{style=venjulegt}
Ef þessarar \texttt{str} töfraaðferðar nyti ekki við væri úttakið á þessa leið \textit{<\_\_main\_\_.Leikur object at *minnissvæði*}.
Einnig er nýtt í þessum kóðabút að við vinnum með inntakið frá notandanum áður en við stillum tilviksbreyturnar.
Þetta er ekki gert á nógu tryggan máta og við munum sjá í kafla \ref{k:villur} hvernig má meðhöndla inntak frá notanda þannig að vafalaust sé um rétt inntak að ræða.
Við ætlum þó enn sem komið er að skoða hlutina á einfaldan og brothættan máta því við erum að kynnast svo mörgu nýju og óþarfi að gera allt kórrétt frá upphafi, mikilvægara er að byggja upp skilning.
Töfraaðferðirnar gera okkur kleyft að beita innbyggðum föllum eins og \texttt{print} og \texttt{len} á tilvik af klösunum okkar, og að beita hinum ýmsu virkjum (reikni-, samanburðar- og rökvirkjum) milli tilvika eða annara gilda.
\section{Erfðir}\index{erfðir}\label{uk:klasar-erfðir}
\begin{wrapfigure}{i}{0.5\textwidth} %i o r l
\begin{center}
\includegraphics[scale=1.3]{doodles31-05.png}
\end{center}
\end{wrapfigure}
Klasarnir okkar hafa hingað til verið skilgreindir með tómum svigum sem segir vélinni að þeir erfi ekki frá neinum klasa nema \textit{object}, sem gerði það að verkum að við gátum útfært töfraaðferðir.
Það gerist sjálfkrafa bak við tjöldin.
Í kóðabút \ref{lst:klasar-erfðir} ætlum við að skoða hvernig á að búa til \textbf{yfirklasa} (e. superclass) og \textbf{undirklasa} (e. subclass).
Við skoðum dæmi þar sem prentari er tekinn fyrir, hann þarf að kunna að prenta út streng, segja til um blekhlutfallið sitt og minnka blekið um eitt prósentustig.
Þetta er alfarið æfing og því ekki endilega mjög raunhæft dæmi, en þar sem við erum að reyna að átta okkur á því hvað erfðir eru ætlum við að gera ráð fyrir því að við viljum að allir prentararnir okkar byrji með 100\% af bleki og hafi möguleikann á að lækka það.
Hins vegar er ekki útfært hvernig á að prenta út og því ætlum við að útfæra sérstaka prentara sem eru eins og grunnprentarinn okkar (með tilliti til bleks) en meðhöndla prentunina sjálfa á annan máta.
\begin{wrapfigure}{o}{0.5\textwidth} %i o r l
\begin{center}
\includegraphics[scale=0.95]{doodles31-19.png}
\end{center}
\end{wrapfigure}
Ástæða þess að við myndum vilja gera þetta er sú að við viljum að einhver grunnvirkni sé til staðar og sé aðgengileg, en það er einhver tiltekin virkni sem við viljum að sé öðruvísi.
Tökum dæmi um spilastokk með 52 spilum þar sem við viljum útfæra reglur fyrir nýtt spil sem minnir á ólsen ólsen.
Við viljum þó breyta reglunni um áttuna þannig að hún breyti ekki um lit heldur láti einhvern annan spilara draga tvö spil.
Í staðinn fyrir að skrifa upp allar reglurnar í ólsen ólsen skrifum við þá bara ,,alveg eins og ólsen ólsen nema áttan er öðruvísi á eftirfarandi máta“.
\newpage
\begin{lstlisting}[caption=Erfðir kynntar með klasanum Prentari, label=lst:klasar-erfðir]
class Prentari():
BLEK = 100
def prentun(self, strengur):
print(strengur)
def minnka_blek(self):
self.BLEK -= 1
def stada_bleks(self):
print(self.BLEK)
import random
class HandahofsPrentari(Prentari):
def prentun(self, strengur):
handahof = random.randint(1,5)
for i in range(handahof):
print(strengur)
class InntaksPrentari(Prentari):
def prentun(self):
strengur = input('hvað viltu prenta? ')
fjoldi = int(input('hversu oft viltu prenta það? '))
for i in range(fjoldi):
print(strengur)
\end{lstlisting}
Í kóðabút \ref{lst:klasar-erfðir} er einungis verið að yfirskrifa aðferðina \texttt{prentun} því að það er aðferðin sem við vildum að væri með einhverjum sértækum hætti.
Við vildum ekki bara prenta út einu sinni heldur fá notandann til að segja okkur hversu oft og hvað ætti að prenta, eða geta gert það handahófskennt oft.
\begin{lstlisting}[caption=Prentaraklasarnir notaðir, label=lst:klasar-erfðir-2]
p1 = Prentari()
p1.prentun('Fyrsti klasinn')
p1.minnka_blek()
print(p1.BLEK)
p2 = HandahofsPrentari()
p2.prentun('Handahóf')
p2.minnka_blek()
p2.stada_bleks()
print(p2.BLEK)
p3 = InntaksPrentari()
p3.prentun()
print(p3.BLEK)
\end{lstlisting}
\lstset{style=uttak}
\begin{lstlisting}
Fyrsti klasinn
99
Handahóf
Handahóf
Handahóf
Handahóf
99
99
hvað viltu prenta? Inntak
hversu oft viltu prenta það? 2
Inntak
Inntak
100
\end{lstlisting}
\lstset{style=venjulegt}
\subsubsection{Fjölmótun}
Þar sem við höfum rætt erfðir er þess virði að nefna \emph{fjölmótun} (e. polymorphism) í Python, því hún er fráburgðið t.d. C++ og Java forritunarmálunum.
Fjölmótun er sá eiginleiki að tagið sem inntakið okkar er af bindur okkur ekki lengur, notandinn á að geta sett inn ólíkar týpur án þess að skemma nokkuð.
Fjölmótun í Python virkar þannig að klasar þurfa ekki að erfa frá öðrum klösum til að haga sér eins og þeir.
Þetta er vegna þess að þegar vélin athugar hvort að einhver hlutur eigi einhver tiltekin eigindi skoðar hún klasann og þá klasa sem hann erfir frá (í röð) og skilar þeirri útgáfu af eigindinu sem finnst.
Sem dæmi getum við tekið \texttt{HandahofsPrentari} og eigindið \texttt{stada\_bleks()}, en er fyrst athugað innan klasans \texttt{HandahofsPrentari} og svo \texttt{Prentari} hvernig eigi að nota \texttt{stada\_bleks}.
Hins vegar ef við værum að vinna með eitthvað sem við vildum að hegðaði sér eins og prentari án þess að spá í öllu sem prentaraklasinn er hugsaður fyrir gætum við búið til hlut sem útfærir bara aðferðina \texttt{stada\_bleks} og erfir ekki frá neinum.
Hlutinn myndum við kannski kalla \texttt{Blekathugun}, og það sem aðferðin \texttt{stada\_bleks} gerir í þeim klasa er að skrifa stöðu bleksins, á einhverju tæki sem vill notfæra sér þessa aðferð, í tölvupóst.
Ef við tökum praktískara dæmi er hægt að sjá fyrir sér klasa sem sér um að vinna með gögn og til þess að geta sent gögnin frá þessum klasa á ákveðinn máta má láta hann fá hlut í hendurnar sem útfærir \textit{write} aðferð.
Klasinn sem útfærir \texttt{write} aðferðina þarf ekkert að gera annað en að útfæra þessa einu aðferð á einhvern ákveðinn máta og þá er hægt að fullvissa sig um að gögnin hafi verið skrifuð á þann máta.
Ef við viljum svo eiga nokkra mismunandi klasa sem allir kunna mismunandi \texttt{write} aðferðir þá þurfum við bara að ganga úr skugga um að gagnavinnsluklasinn okkar fékk \texttt{write} aðferðina sem við vildum nota úr viðeigandi klasa.
Þetta er kallað \textbf{duck typing} og ekki öll forritunarmál bjóða upp á það.
Hugtakið kemur úr frasanum ,,if it looks like a a duck, quacks like a duck and walks like a duck, it's a duck“.
Hugmyndin er að klasinn sem útfærir einungis aðferðina write fyrir okkur er alveg jafn mikil önd eins og kóðasafnið \textit{os} sem sér um að vinna með skráarsafnið og skrifa í skjöl.
Ef við höldum áfram með dæmið um klasana sem útfæra \texttt{write} gæti einn þeirra skrifað í skjal á tölvu úti í Þýskalandi, einn sent skjalið í tölvupósti og einn látið talgervil lesa það upp í strætó leið 14.
Upphaflegi gagnaklasinn veit ekkert um það heldur treystir bara á að fá einhvern hlut í hendurnar sem kann þessa aðferð sama hvernig hún er útfærð.
%-------------------------------ÆFINGAR------------------------%
\newpage
\section{Æfingar}
\begin{exercise}\label{kla1}
Útfærið nýja aðferð í klasann \texttt{Lest} úr kóðabút \ref{lst:klasar-aðferðir-lestar} sem uppfærir núverandi stöðu lestarinnar.
Aðferðin tekur við hlut af taginu \texttt{Stod} og notar hann til að uppfæra hvar lestin er staðsett, og hversu langt hún hefur farið.
Afritið kóðann úr kóðabútnum, og bætið þessari nýju aðferð inn í klasann og prófið hana.
Til þess að prófa hana þurfið þið nýja stöð, athugið kóðabút \ref{lst:klasar-aðferðir-lestar-2} til að sjá hvernig það var gert.
Til dæmis væri hægt að setja Höfn í Hornafirði sem er í 820 km fjarlægð frá Reykjavík ef farið er norður.
\end{exercise}
\setboolean{firstanswerofthechapter}{true}
\begin{Answer}[ref={kla1}]
Það sem þarf að athuga hér er að \texttt{Lest} á tilviksbreytuna \texttt{nuverandi\_stod} sem við viljum uppfæra en það má ekki gerast fyrr en við erum búin að reikna hversu langt er þangað.
Til þess að finna hvað er langt á milli getum við notað einhverja af þeim þremur aðferðum sem búið var að útfæra.
Einnig má ekki gleymast að uppfæra kílómetrastöðuna.
Eftirfarandi er kóðinn fyrir útfærsluna á aðferðinni en prófanir eru eftirlátnar lesanda.
\begin{lstlisting}
def ny_nuverandi_stod(self, stod):
km = self.fara_til_stod(stod)
self.farnir_km += km
self.nuverandi_stod = stod
return self.farnir_km\end{lstlisting}
\end{Answer}
\setboolean{firstanswerofthechapter}{false}
\begin{exercise}\label{kla2}
Notið klasann \texttt{Leikur} úr kóðabút \ref{lst:klasar-str} til þess að útfæra töfraaðferðirnar \texttt{\_\_eq\_\_} og \texttt{\_\_lt\_\_}, sem eru til þess að geta notað samanburðarvirkjana == og <.
Skilagildið úr þessum aðferðum sem við erum að vinna í að útfæra hér er fengið með þessum samanburðarvirkjum.
Búið svo til tvö eintök af klasanum og berið þau saman með þessum samanburðarvirkjum.
Skoðið vel kóðabútinn og sjáið hvernig \texttt{\_\_str\_\_} er útfærð, þar er einungis verið að skila streng.
\end{exercise}
\begin{Answer}[ref={kla2}]
Hér eru margar leiðir til þess að leysa verkefnið, hvað er það sem gerir tvo leiki jafna?
Hér er það útfært þannig að tveir leikir eru jafnir ef nöfnin eru þau sömu, sömuleiðis er útfærslan á < er þannig að einn leikur er strangt minni en annar ef þar er minni peningur.
Þetta mætti líka gera með rökvirkjum, til þess að tveir leikir séu jafnir þurfa allar tilviksbreytur að vera eins, eða einhvern veginn allt öðruvísi.
Prófanir eru eftirlátnar lesanda.
\begin{lstlisting}
def __eq__(self, other):
return self.nafn == other.nafn
def __lt__(self,other):
return self.peningur < other.peningur\end{lstlisting}
\newpage
\end{Answer}