Welkom bij les vier in onze Python from Scratch serie. Deze tutorial veronderstelt enige voorkennis van variabelen, gegevenstypen, functies en afdrukuitvoer. Als je niet op de hoogte bent, bekijk dan de vorige drie artikelen in de serie om bij te praten.
Vandaag zullen we ons verdiepen in het onderwerp Object Oriented Programming (OOP). OOP is een zeer krachtige manier om uw code te organiseren, en een goed begrip van de concepten erachter kan u echt helpen om het meeste uit uw codering te halen.
Python is voornamelijk ontworpen als een object-georiënteerde programmeertaal - maar wat betekent 'objectgericht' eigenlijk?
Er zijn verschillende definities voor de term, en je zou letterlijk uren kunnen praten om de ingewikkelde ins en outs, nuances en verschillen in implementaties uit te leggen, maar ik zal proberen een snel overzicht te geven.
In grote lijnen is objectgeoriënteerd programmeren het concept dat bij het programmeren de objecten die we manipuleren belangrijker zijn dan de logica die nodig is om die objecten te manipuleren. Traditioneel wordt een programma gezien als een recept - een reeks instructies die je van begin tot einde volgt om een taak te voltooien. Dat kan nog steeds waar zijn, en voor veel eenvoudige programma's is dat alles wat nodig is. Die aanpak is ook bekend als procedureel programmeren.
OOP plaatst objecten centraal in het proces.
Aan de andere kant, naarmate programma's steeds complexer en ingewikkelder worden, wordt de logica die nodig is om ze op een zuiver procedurele manier te schrijven steeds meer verdraaid en moeilijk te begrijpen. Vaak kunnen objectgerichte benaderingen daarbij helpen.
Wanneer we het hebben over objectgerichte benaderingen, dan stellen we de objecten centraal in het proces, in plaats van ze gewoon te gebruiken als noodzakelijke containers voor informatie als onderdeel van onze procedurele instructies. Eerst definiëren we de objecten die we willen manipuleren en hoe ze zich tot elkaar verhouden, en dan beginnen we het met logica uit te werken om het programma daadwerkelijk te laten werken.
Als ik het heb over 'objecten', kan ik het hebben over allerlei dingen. Een 'object' kan een persoon vertegenwoordigen (zoals gedefinieerd door eigenschappen zoals naam, leeftijd, adres etc.), of een bedrijf (zoals gedefinieerd door zaken als het aantal werknemers enzovoort), of zelfs iets veel abstracts, zoals een knop in een computerinterface.
In deze inleiding gaan we niet alle concepten in dit onderwerp behandelen, omdat we hier de hele nacht zouden zijn, maar aan het einde van de tutorial hoop ik dat je een goed begrip hebt van de principes die je nodig hebt om meteen te beginnen met enkele eenvoudige object-georiënteerde technieken in je Python-programma's. Beter nog, deze concepten komen in veel programmeeromgevingen redelijk overeen. De kennis gaat redelijk goed over van taal naar taal.
Ik heb eerder gezegd dat het eerste wat we moeten doen als we voor een OOP-benadering gaan, is om de objecten te definiëren die we gaan gebruiken. De manier waarop we dit doen, is om eerst de eigenschappen te definiëren die het bezit door een klasse te gebruiken. Je kunt een klasse zien als een soort sjabloon; een gids voor de manier waarop een object moet worden gestructureerd. Elk object hoort bij een klasse en erft de eigenschappen van die klasse, maar handelt afzonderlijk voor de andere objecten van die klasse.
Een object wordt soms een 'instantie' van een klasse genoemd.
Als een eenvoudig voorbeeld, zou u een klasse kunnen hebben met de naam 'persoon' met bijvoorbeeld een leeftijd en een eigenschap name, en een instantie van die klasse (een object) zou een enkele persoon zijn. Die persoon heeft misschien een naam? Andy? en een leeftijd van 23, maar je zou tegelijkertijd een andere persoon kunnen hebben die tot dezelfde klasse behoort met de naam van? Lucy? en een leeftijd van 18.
Het is moeilijk om dit te begrijpen zonder het in de praktijk te zien, dus laten we wat echte code gaan gebruiken.
Om een klasse te definiëren, op een typische eenvoudige Python-manier, gebruiken we het woord 'klasse', gevolgd door de naam van je nieuwe klas. Ik ga hier een nieuwe klas maken, genaamd 'huisdier'. We gebruiken een dubbele punt achter de naam en alles wat zich in de klassedefinitie bevindt, is ingesprongen. Met een klasse zijn er echter geen haakjes:
klasse huisdier:
Dus nu hebben we een klas, maar het is tamelijk nutteloos zonder dat er iets in zit. Om te beginnen, laten we het een paar eigenschappen geven. Om dit te doen, definieer je eenvoudig enkele variabelen in de klas - ik ga om te beginnen met het aantal benen. Zoals gewoonlijk, moet u altijd uw variabelen een naam geven, zodat u gemakkelijk kunt zien wat ze zijn. Laten we origineel zijn en noemen het 'number_of_legs'. We moeten een waarde definiëren of we krijgen een foutmelding. Ik gebruik hier 0 (het maakt in dit geval niet zoveel uit, omdat het aantal pootjes specifiek is voor elke instantie van de klas - een vis heeft niet hetzelfde aantal poten als een hond of een eend, enz. - dus we zullen hoe dan ook die waarde voor elk object moeten veranderen).
klasse huisdier: number_of_legs = 0
Een klasse op zichzelf is niet iets dat je direct kunt manipuleren; eerst moeten we een instantie van de klasse maken om mee te spelen. We kunnen die instantie opslaan in een variabele. Buiten de klasse (zonder enige inspringing), laten we een instantie van de klasse maken en opslaan in de variabele, 'doug'. Als u een nieuwe instantie van een klasse wilt maken, typt u eenvoudig de naam van de klasse en vervolgens een paar haakjes. Op dit moment hoef je je geen zorgen te maken over de haakjes, maar later zie je dat ze er zijn omdat, net als een functie, een variabele kan worden doorgegeven voor gebruik door de klas wanneer je de instantie voor het eerst maakt.
Een klasse op zich is niet iets dat je direct kunt manipuleren.
class pet: number_of_legs = 0 doug = pet ()
Nu we een instantie van een klasse hebben, hoe kunnen we dan de eigenschappen ervan benaderen en manipuleren? Om naar een eigenschap van een object te verwijzen, moeten we eerst Python vertellen aan welk object (of welke instantie van een klasse) we het hebben, dus we beginnen met 'doug'. Vervolgens gaan we een punt schrijven om aan te geven dat we verwijzen naar iets dat is opgenomen in onze doug-instantie. Na de periode voegen we de naam van onze variabele toe. Als we toegang hebben tot de number_of_legs
variabel, het ziet er als volgt uit:
doug.number_of_legs
We kunnen dat nu precies behandelen zoals we elke andere variabele behandelen - hier ga ik aannemen dat doug een hond is, en zal die variabele de waarde van 4 geven..
Om toegang tot deze variabele te krijgen, gaan we hem weer precies gebruiken zoals we elke andere variabele zouden behandelen, maar die gebruiken doug.number_of_legs
eigenschap in plaats van de naam van de normale variabele. Laten we een streep zetten om uit te printen hoeveel poten doug heeft, zodat we kunnen laten zien dat het werkt zoals het hoort:
class pet: number_of_legs = 0 doug = pet () doug.number_of_legs = 4 print "Doug heeft% s legs." % doug.number_of_legs
Als u de bovenstaande code uitvoert, ziet u dat deze voor ons is afgedrukt. Het definieerde onze 'huisdier' klasse, creëerde een nieuw exemplaar van die klasse en bewaarde het in de variabele 'doug', en vervolgens kreeg het binnen dat exemplaar de waarde 4 toegewezen aan de number_of_legs
variabele die het van zijn klasse heeft geërfd.
Dus je kunt aan dat zeer vereenvoudigde voorbeeld zien hoe je kunt beginnen met het bouwen van mooie, modulaire datastructuren die duidelijk en gemakkelijk te gebruiken zijn, en die aardig kunnen beginnen schalen.
Oké, dus dat is de basis van klassen en objecten, maar op dit moment kunnen we klassen alleen echt gebruiken als datastructuren - of als containers voor variabelen. Dat is allemaal goed en wel, maar als we willen beginnen met het uitvoeren van meer complexe taken met de gegevens die we manipuleren, hebben we een manier nodig om wat logica in deze objecten te introduceren. De manier waarop we dat doen is met methoden.
Methoden zijn in wezen functies binnen een klasse. Je definieert er een op precies dezelfde manier als een functie, maar het verschil is dat je het in een klasse plaatst, en het hoort bij die klasse. Als u die methode ooit wilt gebruiken, moet u eerst naar een object van die klasse verwijzen, net als de variabelen waarnaar we eerder keken.
Methoden zijn in wezen functies binnen een klasse.
Ik ga hier een snel voorbeeld geven van onze dierenklasse om te demonstreren; laten we een methode maken, 'slaap' genoemd, die een bericht gaat afdrukken wanneer het voor het eerst wordt gebeld. Net als een functie, ga ik 'def' voor 'define' zetten, en dan ga ik de naam schrijven van de methode die ik wil creëren. Vervolgens plaatsen we onze haakjes en puntkomma en beginnen we een nieuwe regel. Zoals gebruikelijk zal alles wat in deze methode is opgenomen een extra niveau inspringen.
Nu is er nog een verschil tussen een methode en een functie: een methode altijd, altijd, moet altijd een argument hebben, 'zelf' genoemd tussen de haakjes. Wanneer Python een methode aanroept, wordt het huidige object doorgegeven aan die methode als het eerste argument. Met andere woorden, als we bellen doug.sleep ()
, Python gaat het object 'doug' daadwerkelijk doorgeven als argument voor de slaapmethode.
We zullen zien waarom dat later is, maar voor nu moet je weten dat, met een methode, je altijd eerst een argument genaamd 'zelf' moet opnemen in de lijst (als je meer argumenten wilt toevoegen, kun je ze toevoegen achteraf, precies zoals wanneer je meerdere argumenten doorgeeft aan een functie). Als je dat argument niet opneemt, krijg je bij het uitvoeren van de code een foutmelding omdat Python een argument doorgeeft (dit 'zelf'-object) en de methode zegt:' Hé, man, Ik neem geen argumenten, waar heb je het over? '. Het is hetzelfde als wanneer je een argument probeert door te geven aan een functie die geen argumenten accepteert.
Dus dit is wat we tot nu toe hebben:
class pet: number_of_legs = 0 def sleep (self): doug = pet ()
Binnen deze methode gaan we een print-statement schrijven zoals:
class pet: number_of_legs = 0 def sleep (self): print "zzz" doug = pet ()
Als we deze methode willen gebruiken, gebruiken we eenvoudig een instantie van de huisdierenklasse om ernaar te verwijzen. Net als de number_of_legs
variabele, we schrijven de naam van de instantie (we hebben er een genaamd doug), dan een punt, dan de naam van de methode inclusief haakjes. Merk op dat we slaap aanroepen met geen argumenten, maar Python zal dat argument alleen toevoegen, dus we zullen eindigen met de juiste hoeveelheid argumenten in totaal.
class pet: number_of_legs = 0 def sleep (self): print "zzz" doug = pet () doug.sleep ()
Als u deze code uitvoert, zou u moeten zien dat deze het bericht dat we hebben geschreven, afdrukt.
Geweldig, dus nu, hoe zit het met het schrijven van een nieuwe methode om uit te printen hoeveel benen het huisdier heeft, om te demonstreren hoe je methoden kunt gebruiken om te beginnen met het manipuleren van de gegevens in de klas, en om aan te tonen waarom we dit verwarrende 'zelf' moeten opnemen argument. Laten we een nieuwe methode maken, genaamd 'count_legs
'.
Dit is waar het 'zelf'-argument binnenkomt. Weet je nog toen we toegang hadden number_of_legs
van buiten de klas en moesten we 'doug.number_of_legs' gebruiken in plaats van alleen 'number_of_legs'? Hetzelfde principe is van toepassing; als we willen weten wat er in die variabele zit, moeten we ernaar verwijzen door eerst het exemplaar op te geven dat die variabele bevat.
We weten echter niet wat de instantie wordt genoemd wanneer we de klasse schrijven, dus we komen erachter dat we de 'zelf'-variabele gebruiken. 'zelf' is slechts een verwijzing naar het object dat momenteel wordt gemanipuleerd. Dus om toegang te krijgen tot een variabele in de huidige klasse, moet je het gewoon voorwoord geven met 'zelf' en dan een punt, zoals zo:
class pet: number_of_legs = 0 def sleep (self): print "zzz" def count_legs (self): print "I have% s legs"% self.umber_of_legs doug = pet () doug.number_of_legs = 4 doug.count_legs ()
In de praktijk betekent dit dat overal waar je 'zelf' schrijft in je methode, wanneer je de methode uitvoert dat zelf wordt vervangen door de naam van het object, dus wanneer we 'doug.count_legs ()' noemen, is het 'zelf' vervangen door 'doug'. Om aan te tonen hoe dit werkt met meerdere instanties, laten we een tweede exemplaar toevoegen, dat een ander huisdier vertegenwoordigt, 'nemo' genaamd:
class pet: number_of_legs = 0 def sleep (self): print "zzz" def count_legs (self): print "I have% s legs"% self.umber_of_legs doug = pet () doug.number_of_legs = 4 doug.count_legs () nemo = pet () nemo.number_of_legs = 0 nemo.count_legs ()
Dit zal een bericht voor 4 en vervolgens 0 legs afdrukken, precies zoals we wilden, want als we 'nemo.count_legs ()' noemen, wordt 'het' zelf 'vervangen door' nemo 'in plaats van' doug '.
Op deze manier werkt onze methode precies zoals bedoeld omdat de 'zelf'-referentie dynamisch zal veranderen afhankelijk van de context en ons in staat stelt om de gegevens alleen binnen het huidige object te manipuleren.
De belangrijkste dingen die je moet onthouden over methoden is dat ze precies op functies lijken, behalve dat het eerste argument 'zelf' moet zijn en dat om naar een interne variabele te verwijzen je de naam van de variabele moet invoeren met 'zelf'.
Net als een opmerking: je kunt elke naam in plaats van 'zelf' gebruiken voor je methoden. -De methoden hier zouden net zo goed werken als we het variabele 'zelf' een andere naam geven. Het gebruik van de naam 'zelf' is eenvoudig een conventie die handig is voor programmeurs van Python omdat het de code veel normaler en gemakkelijker te begrijpen maakt, zelfs als het door iemand anders is geschreven. Mijn advies zou zijn om vast te houden aan de conventies.
Nu we de basis hebben doorgenomen, laten we een paar meer geavanceerde functies van klassen bekijken, en hoe ze kunnen helpen om je programmering eenvoudiger te structureren.
Het volgende waar we het over gaan hebben, is erfenis. Zoals de naam al doet vermoeden, is overerving het proces waarbij een nieuwe klasse wordt gemaakt op basis van een bovenliggende klasse en de nieuwe klasse de kenmerken van de bovenliggende klasse overneemt. De nieuwe klasse kan alle methoden en variabelen uit de bovenliggende klasse gebruiken (vaak de 'basisklasse' genoemd).
Overerving is het proces waarbij een nieuwe klasse wordt gemaakt op basis van een bovenliggende klasse.
Laten we ons voorbeeld van een huisdier uitbreiden om te kijken hoe dit nuttig zou kunnen zijn. Als we 'huisdier' gebruiken als onze bovenliggende klasse, kunnen we een onderliggende klas maken die is geërfd van de huisdierenklasse. De kinderklasse kan zoiets zijn als 'hond' of 'vis' - iets dat nog steeds een 'huisdier' is, maar specifieker is dan dat. Een hond is een huisdier en doet dezelfde dingen die alle huisdieren doen - bijvoorbeeld het eet en slaapt en heeft een leeftijd en een aantal benen - maar het doet andere dingen die specifiek zijn voor het zijn van een hond, of op zijn minst specifieker dan een huisdier te zijn: honden hebben pels, maar niet alle huisdieren. Een hond blaft, of haalt een stok, maar niet alle huisdieren.
Om terug te komen op het punt, laten we zeggen dat we een klas wilden maken in ons programma om een hond te vertegenwoordigen. We kunnen overerving gebruiken om de methoden en variabelen in 'huisdieren' te erven, zodat onze hond een 'aantal benen' en het vermogen om te 'slapen' heeft, naast alle hondenspecifieke dingen die we misschien opslaan of doen..
Nu vraag je je misschien af waarom we die methoden en variabelen niet in de hondenklasse stoppen en de huisdierencursus volledig verwijderen? Welnu, erfenis geeft ons twee duidelijke voordelen ten opzichte van die benadering: één, als we een object willen dat een huisdier is, maar geen hond is - een generiek huisdier, als je wilt - kunnen we dat nog steeds doen. Twee, misschien later willen we een tweede type huisdier toevoegen - misschien een vis. We kunnen ervoor zorgen dat die tweede klas ook erven van huisdier, en dus kunnen beide klassen alles in huisdier delen, maar hebben ze tegelijkertijd hun eigen meer specifieke methoden en variabelen die alleen van toepassing zijn op dat type object.
We worden hier een beetje verzand in de theorie, dus laten we iets schrijven om het een beetje duidelijker te maken. Eerst gaan we een nieuwe klasse schrijven, 'hond' genaamd, maar deze keer, tussen de klassenaam en de dubbele punt, zullen we wat haakjes zetten, en daarin gaan we de naam schrijven van de klas waarvan we willen erven, een soort van alsof we deze nieuwe klas een argument doorgeven, zoals we een functie zouden hebben.
Laten we vervolgens deze klasse een eenvoudige methode geven om te laten zien hoe het werkt. Ik ga een 'toevoegenschors
'methode die' woof 'zal afdrukken:
class pet: number_of_legs = 0 def sleep (self): print "zzz" def count_legs (self): print "I have% s legs"% self.number_of_legs class dog (pet): def bark (self): print "Woof"
Laten we nu eens kijken wat er gebeurt als we een instantie van deze klasse maken. Ik ga onze nieuwe hond 'doug' opnieuw bellen. Nu, als we bellen doug.bark ()
:
class pet: number_of_legs = 0 def sleep (self): print "zzz" def count_legs (self): print "I have% s legs"% self.number_of_legs class dog (pet): def bark (self): print "Woof" doug = dog () doug.bark ()
Zoals verwacht blaft doug. Dat is geweldig, maar we hebben nog niets nieuws gezien - alleen een klasse met een methode. Wat voor ons heeft overleefd, is echter om alle huisdierenfuncties en -variabelen beschikbaar te maken via ons 'doug'-object, dus als ik zoiets doe als dit:
class pet: number_of_legs = 0 def sleep (self): print "zzz" def count_legs (self): print "I have% s legs"% self.number_of_legs class dog (pet): def bark (self): print "Woof" doug = dog () doug.sleep ()
Dan wordt de slaapmethode ook correct uitgevoerd. Eigenlijk behoort ons doug-object tot de klasse 'huisdier' en de 'hond'. Om ervoor te zorgen dat de variabelen hetzelfde doen als de methoden, laten we dit eens proberen:
class pet: number_of_legs = 0 def sleep (self): print "zzz" def count_legs (self): print "I have% s legs"% self.number_of_legs class dog (pet): def bark (self): print "Woof" doug = dog () doug.number_of_legs = 4 doug.count_legs ()
U ziet dat doug precies zo werkt als voorheen, wat aantoont dat onze variabelen worden geërfd. Onze nieuwe kinderklasse is eenvoudig een gespecialiseerde versie van de eerste, met wat extra functionaliteit maar met behoud van alle voorgaande functies.
Dus daar heb je het, een snelle introductie tot objectgeoriënteerd programmeren. Blijf op de hoogte voor de volgende aflevering in deze serie, waar we met Python op internet gaan werken!