Pennod 04: Rhaglennu Gwrthrych-Gyfeiriadol

Mae'r daflen lab yma yn cyflwyno rhaglennu gwrthrych-gyfeiriadol. Rydym wedi defnyddio rhaglennu gwerthrych-gyfeiriadol trwy gydol y taflennu lab. Fan hyn byddwn yn dysgu sut i greu gwrthrychau ein hunain (bydd yr hyn mae hwn yn golygu yn dod yn eglur cyn bo hir).

Tiwtorial

Gweithiwch trwy'r canlynol:


1. Ysgrifennu dosbarth.

Fideo yn disgrifio'r cysyniad.

Fideo demo.

Y prif syniad tu ôl i raglennu gwrthrych-gyfeiriadol (OOP) yw creu strwythurau haniaethol felly nid oes angen poeni am ddata. Alan Kay daeth i fyny gyda'r cysyniad, a dyfynnwyd yn dweud: 'I wanted to get rid of data'. Yn lle cadw trac ar newidynnau trwy ddefnyddio rhestrau ac araeau ac ysgrifennu ffwythiannau penodol ar gyfer pob gweithrediad, gallwn geisio defnyddio system yn debyg i strwythur cell mewn bioleg:

Mae'r ddelwedd yma yn dangos amryw o bethau byddwn yn ystyried yn y daflen lab yma:

Mae creu dosbarth yn Python yn debyg i greu ffwythiant: rydym yn ysgrifennu lawr y rheolau:


In [1]:
class Myfyriwr:
    """Dosbarth sylfaenol"""

Yna gallwn greu 'achosion' o'r dosbarth yma:


In [2]:
vince = Myfyriwr()
zoe = Myfyriwr()
vince


Out[2]:
<__main__.Myfyriwr at 0x11296e048>

In [3]:
zoe


Out[3]:
<__main__.Myfyriwr at 0x112452fd0>

Mae'r at ... yn bwyntydd i leoliad yr achos yma yn gof y cyfrifiadur. Os ail-rhedwn y cod yna bydd y lleoliad yna yn newid.

Rydym ni wedi gweld enghreifftiau o ddosbarthiadau yn barod yn Python:

  • Cyfanrifau;
  • Strings;
  • Rhestrau.

Mae llawer mwy, a byddwn yn gweld sut i adeiladu un ein hunain.

Arbrofwch gyda chreu achos o'r dosbarth Myfyriwr

2. Priodweddau

Fideo yn disgrifio'r cysyniad.

Fideo demo.

Nid yw'r dosbarth uchod yn ddefnyddiol iawn. Nawr gwelwn sut i wneud i'n wrthrychau 'gafael' ar wybodaeth. Mae'r cod canlynol yn ail-greu ein dosbarth Myfyriwr blaenorol ond gan roi'r dosbarth rhai 'briodweddau':


In [4]:
class Myfyriwr:
    cyrsiau = ['Bioleg', 'Mathemateg', 'Saesneg']
    oedran = 12
    rhyw = "Benyw"

Nawr mae gan y dosbarth ei hun bach o wybodaeth:


In [5]:
Myfyriwr.oedran


Out[5]:
12

Pasiwyd y wybodaeth yma i unrhyw achos o'r dosbarth:


In [6]:
vince = Myfyriwr()
vince.cyrsiau


Out[6]:
['Bioleg', 'Mathemateg', 'Saesneg']

In [7]:
vince.oedran


Out[7]:
12

In [8]:
vince.rhyw


Out[8]:
'Benyw'

Gallwn ddefnyddio ac/neu newid y priodweddau yma yn union fel unrhyw newidyn Python arall:


In [9]:
vince.oedran += 1
vince.oedran


Out[9]:
13

In [10]:
vince.rhyw = 'Gwryw'
vince.rhyw


Out[10]:
'Gwryw'

In [11]:
vince.cyrsiau.append('Cemeg')
vince.cyrsiau


Out[11]:
['Bioleg', 'Mathemateg', 'Saesneg', 'Cemeg']

Crëwch achosion gyda phriodweddau ac arbrofwch gyda nhw.

3. Dulliau

Fideo yn disgrifio'r cysyniad.

Fideo demo.

Nawr byddwn yn gweld sut i wneud i ddosbarthiadau 'gwneud' pethau. Fe elwir y rhain yn 'dulliau' a jyst ffwythiannau ydynt sydd 'ynghlwm' a'r dosbarthau.


In [12]:
class Myfyriwr:
    """Dosbarth i gynrychioli myfyriwr"""
    cyrsiau = ['Bioleg', 'Mathemateg', 'Saesneg']
    oedran = 12
    rhyw = "Female"
    def cael_penblwydd(self, blynyddoedd=1):
        """Cynyddu'r oedran"""
        self.oedran += blynyddoedd  # Mae 'self' yn cyfateb a'r achos
vince = Myfyriwr()
vince.cael_penblwydd()
vince.oedran


Out[12]:
13

In [13]:
vince.cael_penblwydd(blynyddoedd=10)
vince.oedran


Out[13]:
23

Mae nifer o enwau dull 'arbennig' sy'n ymddwyn mewn ffordd benodol. Un o rain yw __init__, ac mae'r dull yma yn cael ei alw ar y foment crëir yr achos ('initialised'):


In [14]:
class Myfyriwr:
    """Dosbarth i gynrychioli myfyriwr"""
    def __init__(self, cyrsiau, oedran, rhyw):
        self.cyrsiau = cyrsiau
        self.oedran = oedran
        self.rhyw = rhyw
    def cael_penblwydd(self, blynyddoedd=1):
        """Cynyddu'r oedran"""
        self.oedran += blynyddoedd  # Mae 'self' yn cyfateb a'r achos

Nawr mae'n hawdd creu achos gyda rhyw briodweddau penodol:


In [15]:
vince = Myfyriwr(["Maths"], 32, "Gwryw")
zoe = Myfyriwr(["Bioleg"], 31, "Benyw")
vince.cyrsiau, vince.oedran, vince.rhyw


Out[15]:
(['Maths'], 32, 'Gwryw')

In [16]:
zoe.cyrsiau, zoe.oedran, zoe.rhyw


Out[16]:
(['Bioleg'], 31, 'Benyw')

Mae nifer o ddulliau 'arbennig' eraill, byddwn yn gweld un ohonynt yn yr enghraifft weithredol.

Crëwch achosion o'r dosbarth Myfyriwr gyda'r dulliau newydd yma.

4. Etifeddiaeth

Fideo yn disgrifio'r cysyniad.

Fideo demo.

Un agwedd olaf (ond bwysig iawn) o raglennu gwrthrych-gyfeiriadol yw'r cysyniad o etifeddiaeth. Mae hwn yn galluogi ni i greu dosbarthau newydd o rhai eraill. Yn ymarferol mae hwn yn arbed dyblygu cod oherwydd gallwn newid ond rhai dulliau fel sydd angen.

I wneud hwn rydym yn creu dosbarth newydd fel arfer ond trwy basio'r hen ddosbarth iddo:

class DosbarthNewydd(HenDdosbarth):
       ...

Er enghraifft, gadewch i ni greu myfyriwr sydd â phen-blwydd ar Chwefror 29 (dyddiad sydd ond yn digwydd unwaith pob 4 blwyddyn):


In [17]:
class MyfyriwrBlwyddynNaid(Myfyriwr):
    """Dosbarth ar gyfer myfyriwr a chafodd ei eni ar Chwefror 29"""
    # Nodwch does dim angen ailysgrifennu'r dull init
    def cael_penblwydd(self, blynyddoedd=1):
        self.oedran += int(blynyddoedd / 4)
    def cwyno(self):
        """Allbynnu string yn cwyno am ei phenblwydd"""
        # MAe hwn yn dull newydd nad oes gan y dosbarth Myfyriwr
        return "Dymunaf ni chefais fy ngeni ar Chwef 29"

Dyma sut mae'r dosbarth newydd yn ymddwyn:


In [18]:
geraint = MyfyriwrBlwyddynNaid(["Maths"], 22, "Gwryw")
geraint.cael_penblwydd()
geraint.oedran  # Dal yn 22


Out[18]:
22

In [19]:
geraint.cael_penblwydd(8)
geraint.oedran


Out[19]:
24

In [20]:
geraint.cwyno()


Out[20]:
'Dymunaf ni chefais fy ngeni ar Chwef 29'

Arbrofwch gyda'r cod uchod: sut bydd yn gweithio os oedd blwyddyn nai pob 3 blwyddyn?


Enghraifft weithredol

Fideo yn disgrifio'r cysyniad.

Fideo demo.

Tybiwch rydym ni eisiau astudio mynegiannau llinol. Mae'r mynegiannau ar ffurf:

$$ ax+b $$

Mae gennym ddiddordeb, er enghraifft, yn y gwerth $x$ lle mae'r mynegiant llinol yn 0. Fe elwir hwn yn 'gwraidd' ac mae'n ddatrysiad i'r hafaliad canlynol:

$$ ax+b=0 $$

Yn amlwg mae hwn yn beth hawdd i'w astudio ond tybiwn fod e ddim, ac adeiladwn ddosbarth i gynrychioli a thrin mynegiannau llinol.


In [21]:
class MynegiantLlinol:
    """Dosbarth ar gyfer mynegiant llinol"""
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
    def gwraidd(self):
        """Allbynnu gwraidd y mynegiant llinol"""
        return - self.b / self.a
    
    def __add__(self, mynegiant):
        """Dull arbennig: yn galluogi ni i adio mynegiannau"""
        return MynegiantLlinol(self.a + mynegiant.a, self.b + mynegiant.b)

    def __repr__(self):
        """Dull arbennig: yn newid sut arddangosir yr achos"""
        return "Mynegiant llinol: " + str(self.a) + "x + " + str(self.b)

In [22]:
myn = MynegiantLlinol(2, 4)
myn  # rhoddir yr allwbn yma gan y dull '__repr__'


Out[22]:
Mynegiant llinol: 2x + 4

In [23]:
myn.a


Out[23]:
2

In [24]:
myn.b


Out[24]:
4

In [25]:
myn.gwraidd()


Out[25]:
-2.0

In [26]:
myn2 = MynegiantLlinol(5, -2)
myn2


Out[26]:
Mynegiant llinol: 5x + -2

In [27]:
myn + myn2  # Mae hwn yn gweithio oherwydd y dull `__add__`


Out[27]:
Mynegiant llinol: 7x + 2

Mae'r dosbarth yma yn gweithio'r iawn, ond rydym yn gweld problem yn eithaf cloi:


In [28]:
myn1 = MynegiantLlinol(2, 4)
myn2 = MynegiantLlinol(-2, 4)
myn3 = myn1 + myn2
myn3


Out[28]:
Mynegiant llinol: 0x + 8

In [29]:
myn3.gwraidd()


---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-29-e15c207507f7> in <module>()
----> 1 myn3.gwraidd()

<ipython-input-21-74e7a72ae826> in gwraidd(self)
      7     def gwraidd(self):
      8         """Allbynnu gwraidd y mynegiant llinol"""
----> 9         return - self.b / self.a
     10 
     11     def __add__(self, mynegiant):

ZeroDivisionError: division by zero

Cawn wall gan fod y dull gwraidd yn ceisio rhannu gyda sero. Cywirwn ni hwn:


In [30]:
class MynegiantLlinol:
    """Dosbarth ar gyfer mynegiant llinol"""
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
    def gwraidd(self):
        """Allbynnu gwraidd y mynegiant llinol"""
        if self.a != 0:
            return - self.b / self.a
        return False
    
    def __add__(self, mynegiant):
        """Dull arbennig: yn galluogi ni i adio mynegiannau"""
        return MynegiantLlinol(self.a + mynegiant.a, self.b + mynegiant.b)

    def __repr__(self):
        """Dull arbennig: yn newid sut arddangosir yr achos"""
        return "Mynegiant llinol: " + str(self.a) + "x + " + str(self.b)

In [31]:
myn3 = MynegiantLlinol(0, 8)
myn3.gwraidd()


Out[31]:
False

Defnyddiwn ni hwn i wirio'r faith syml canlynol. Os yw $f(x) = a_1x+b_1$ ac $g(x) = a_2x+b_2$, yna rhoddir gwraidd $f(x) + g(x)$ gan:

$$ \frac{a_1x_1 + a_2x_2}{a_1+a_2} $$

Lle $x_1$ yw gwraidd $f$ ac $x_2$ yw gwraidd $g$ (os ydynt yn bodoli).

Yn gyntaf ysgrifennwn ffwythiant sy'n gwirio hwn ar gyfer rhyw set o $a_1, a_2, b_1, b_2$.


In [32]:
def gwirio_canlyniad(a1, a2, b1, b2):
    """Gwirio bod y canlyniad wedi bodloni"""
    f = MynegiantLlinol(a1, b1)
    g = MynegiantLlinol(a2, b2)
    k = f + g
    x1 = f.gwraidd()
    x2 = g.gwraidd()
    x3 = k.gwraidd()
    if (x1 is not False) and (x2 is not False) and (x3 is not False):
        # Yn tybio bod gennym tair mynegiant ar gyfer y gwraidd
        return (a1 * x1 + a2 * x2) / (a1 + a2) == x3
    return True  # Os nad oes gan f, g gwreiddiau mae'r perthynas dal yn wir
gwirio_canlyniad(2, 3, 4, 5)


Out[32]:
True

Byddwn yn gwirio hwn trwy samplu hap-werthoedd ar gyfer $a_1, a_2, b_1, b_2$.


In [33]:
import random  # Mewnforio'r modiwl random
N = 1000  # Nifer o samplau
gwiriadau = []
for _ in range(N):
    a1 = random.randint(-10, 10)
    a2 = random.randint(-10, 10)
    b1 = random.randint(-10, 10)
    b2 = random.randint(-10, 10)
    gwiriadau.append(gwirio_canlyniad(a1, a2, b1, b2))
all(gwiriadau)


Out[33]:
True

Ymarferion

Dyma nifer o ymarferion sy'n bosib eu cwblhau trwy ddefnyddio'r cysyniadau a thrafodwyd:

  • Dosbarthau: creu set o reolau sy'n disgrifio rhyw "peth" haniaethol
  • Priodweddau: newidynnau ar ddosbarth
  • Dulliau: ffwythiannau ar ddosbarth
  • Etifeddiaeth: creu dosbarthau newydd o rhai hen

Ymarfer 1

Ymarfer datbygio

Mae'r canlynol yn gais i ysgrifennu dosbarth ar gyfer petryal, canfyddwch a chywirwch yr holl fygs.

class Petryal:
       """Dosbarth ar gyfer petryal""

       def __init__(lled, hyd)
           self.lled = lled
           self.hyd = lled

       def cael_arwynebedd(self:
           """Cael arwynebedd y petryal"""
           return self.lled * self.hyd

       def yn_sgwar():
           """Gwirio os yw'r petryal yn sgwar"""
           return self.lled == self.hyd

In [34]:
class Petryal:
    # """Dosbarth ar gyfer petryal""  Nifer anghywir o "
    """Dosbarth ar gyfer petryal"""
    #def __init__(lled, hyd)  Does dim self nac :
    def __init__(self, lled, hyd):
        self.lled = lled
        #self.hyd = lled  # Aseiniad anghywir
        self.hyd = hyd
    #def cael_arwynebedd(self:  Cromfach ar goll
    def cael_arwynebedd(self):
        """Cael arwynebedd y petryal"""
        return self.lled * self.hyd
    #def yn_sgwar():  Does dim self
    def yn_sgwar(self):
        """Gwirio os yw'r petryal yn sgwar"""
        return self.lled == self.hyd

Creu petryal nad yw'n sgwâr:


In [35]:
petryal = Petryal(5, 4)
petryal.cael_arwynebedd(), petryal.yn_sgwar()


Out[35]:
(20, False)

Creu petryal sgwâr:


In [36]:
sgwar = Petryal(3, 3)
sgwar.cael_arwynebedd(), sgwar.yn_sgwar()


Out[36]:
(9, True)

Ymarfer 2

Adeiladwch ddosbarth ar gyfer y mynegiant cwadratig:

$$ax^2+bx+c$$

Fe ddylai cynnwys dull i adio dau fynegiant cwadratig gyda'i gilydd a hefyd i gyfrifo gwreiddiau'r mynegiant.


In [37]:
import math

class MynegiantCwadratig:
    """Dosbarth ar gyfer mynegiant cwadratig"""
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c
    def gwraidd(self):
        """Allbynnu gwreiddiau'r mynegiant cwadratig"""
        gwahanolyn = self.b ** 2 - 4 * self.a * self.c
        if gwahanolyn >= 0:
            x1 = - (self.b + math.sqrt(gwahanolyn))/ (2 * self.a)
            x2 = - (self.b - math.sqrt(gwahanolyn))/ (2 * self.a)
            return x1, x2
        return False
    def __add__(self, myncwad):
        """Dull arbennig sy'n galluogi adio mynegiannau"""
        return MynegiantCwadratig(self.a + myncwad.a, self.b + myncwad.b, self.c + myncwad.c)
    def __repr__(self):
        """Dull arbennig: yn newid sut arddangosir yr achos"""
        return "Mynegiant cwadratig: " + str(self.a) + "x ^ 2 + " + str(self.b) + "x + " + str(self.c)

In [38]:
cwad = MynegiantCwadratig(1, 5, 2)
cwad


Out[38]:
Mynegiant cwadratig: 1x ^ 2 + 5x + 2

In [39]:
cwad.gwraidd()


Out[39]:
(-4.561552812808831, -0.4384471871911697)

In [40]:
cwad2 = MynegiantCwadratig(4, 5, 2)
cwad + cwad2


Out[40]:
Mynegiant cwadratig: 5x ^ 2 + 10x + 4

Ymarfer 3

Os yw diferion glaw yn cwympo ar hap ar sgwâr gyda hyd $2r$ yna rhoddir tebygolrwydd o'r diferion yn glanio mewn cylch gyda radiws $r$ gan:

$$ P = \frac{\text{Arwynebedd cylch}}{\text{Arwynebedd sgwâr}}=\frac{\pi r ^2}{4r^2}=\frac{\pi}{4} $$

Felly gallwn amcangyfrif $P$ ac felly amcangyfrif $\pi$ fel $4P$. Yn y cwestiwn yma byddwn yn ysgrifennu cod i amcangyfrif $P$ trwy ddefnyddio'r llyfrgell random.

Yn gyntaf, crëwn ddosbarth ar gyfer diferyn o law (sicrhewch eich bod yn deall y cod yma!):

class Diferyn():
    def __init__(self, r=1):
        self.x = (.5 - random.random()) * 2 * r
        self.y = (.5 - random.random()) * 2 * r
        self.mewncylch = (self.y) ** 2 + (self.x) ** 2 <= (r) ** 2

Nodwch fod y cod uchod yn defnyddio'r hafaliad yma ar gyfer cylch gyda chanol yn $(0,0)$ a radiws $r$:

$$ x^2+y^2≤r^2 $$

I amcangyfrif $P$ crëwn $N=1000$ achos o Diferyn a chyfri'r nifer o rheini sydd o fewn y cylch. Defnyddiwch hwn i amcangyfrif $\pi$.

(Mae hwn yn enghraifft o dechneg a elwir yn efelychiad Monte Carlo.)


In [41]:
import random
import math  # Nid yw hwn yn angenrheidiol ond byddwn yn ei defnyddio ar gyfer cymhariaethau nes ymlaen.


class Diferyn():
    """ 
    Dosbarth ar gyfer diferyn o law ar leoliad ar hap ar sgwar
    """
    def __init__(self, r=1):
        self.x = (.5 - random.random()) * 2 * r
        self.y = (.5 - random.random()) * 2 * r
        self.mewncylch = (self.y) ** 2 + (self.x) ** 2 <= (r) ** 2
        # Mae hwn yn allbynnu boolean yn cyfateb gyda os yw'r pwynt o fewn y cylch neu peidio


def amcangyfrifpi(N=1000):
    """
    Ffwythiant sy'n allbynnu amcangyfrif ar gyfer pi gan ddefnyddio efelychiad monte carlo
    """
    niferpwyntiaumewncylch = 0
    for i in range(N):  # Lwp ar gyfer nifer i diferion
        diferyn = Diferyn()  # Generadu diferyn newydd
        if diferyn.mewncylch:  # Gwirio os yw o fewn y cylch
            niferpwyntiaumewncylch += 1
    return 4 * niferpwyntiaumewncylch / float(N)

In [42]:
amcangyfrifpi()


Out[42]:
3.188

Ymarfer 4

Mewn modd tebyg i ymarfer 3, amcangyfrifwch yr integryn $\int_{0}^11-x^2\;dx$.

Cofiwch fod integryn yn cyfateb ag arwynebedd o dan gromlin. Gall hyn fod yn ddefnyddiol:


In [43]:
class Pwynt():
    """
    Dosbarth ar gyfer pwynt sy'n disgyn yn leoliad ar hap o fewn sgwar

    Priodweddau:
        x: y gyfesuryn 'x' y pwynt
        y: y gyfesuryn 'y' y pwynt
        odangraff: boolean yn nodi os yw'r pwynt o dan y graph neu peidio
    """
    def __init__(self, r=1):
        self.x = random.random()
        self.y = random.random()
        self.odangraff = 1 - (self.x) ** 2 >= self.y
        # Mae hwn yn allbynnu boolean sy'n gwirio os yw'r pwynt o dan y graff neu beidio

def amcangyfrifint(N=1000):
    """
    Ffwythiant sy'n allbynnu amcangyfrif yr integryn gan ddefnyddio efelychiad monte carlo

    Dadleuon: N (diofyn=1000) sef nifer o bwyntiau

    Allbynnau: Amcangyfrif yr integryn
    """
    niferodanygraff = 0
    for i in range(N):  # Lwp i ostwng nifer o bwyntiau
        pwynt = Pwynt()  # Generadu pwynt newydd
        if pwynt.odangraff:  # Gwirio os yw o dan y graff
            niferodanygraff += 1
    return niferodanygraff / float(N)

In [44]:
amcangyfrifint()


Out[44]:
0.68

In [ ]: