Python Language
Klassen
Zoeken…
Invoering
Python biedt zichzelf niet alleen aan als een populaire scripttaal, maar ondersteunt ook het objectgeoriënteerde programmeerparadigma. Klassen beschrijven gegevens en bieden methoden om die gegevens te manipuleren, allemaal onder een enkel object. Bovendien laten klassen abstractie toe door concrete implementatiedetails te scheiden van abstracte representaties van gegevens.
Code die klassen gebruikt, is over het algemeen gemakkelijker te lezen, begrijpen en onderhouden.
Fundamentele erfenis
Overerving in Python is gebaseerd op vergelijkbare ideeën die worden gebruikt in andere objectgeoriënteerde talen zoals Java, C ++ enz. Een nieuwe klasse kan als volgt worden afgeleid uit een bestaande klasse.
class BaseClass(object):
pass
class DerivedClass(BaseClass):
pass
De BaseClass
is de reeds bestaande ( bovenliggende ) klasse en de DerivedClass
is de nieuwe ( onderliggende ) klasse die attributen van BaseClass
(of subklassen ). Opmerking : Vanaf Python 2.2 nemen alle klassen impliciet over van de object
, die de basisklasse is voor alle ingebouwde typen.
We definiëren een bovenliggende Rectangle
in het onderstaande voorbeeld, die impliciet van het object
overneemt:
class Rectangle():
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
def perimeter(self):
return 2 * (self.w + self.h)
De klasse Rectangle
kan worden gebruikt als een basisklasse voor het definiëren van een klasse Square
, omdat een vierkant een speciaal geval van een rechthoek is.
class Square(Rectangle):
def __init__(self, s):
# call parent constructor, w and h are both s
super(Square, self).__init__(s, s)
self.s = s
De klasse Square
neemt automatisch alle attributen van de klasse Rectangle
en de objectklasse over. super()
wordt gebruikt om de methode __init__()
van de klasse Rectangle
roepen, die in wezen elke overschreven methode van de basisklasse aanroept. Opmerking : in Python 3 vereist super()
geen argumenten.
Afgeleide klasseobjecten hebben toegang tot en kunnen de kenmerken van de basisklassen wijzigen:
r.area()
# Output: 12
r.perimeter()
# Output: 14
s.area()
# Output: 4
s.perimeter()
# Output: 8
Ingebouwde functies die werken met overerving
issubclass(DerivedClass, BaseClass)
: geeft True
terug als DerivedClass
een subklasse is van de BaseClass
isinstance(s, Class)
: geeft True
terug als s een instantie van Class
of een van de afgeleide klassen van Class
# subclass check
issubclass(Square, Rectangle)
# Output: True
# instantiate
r = Rectangle(3, 4)
s = Square(2)
isinstance(r, Rectangle)
# Output: True
isinstance(r, Square)
# Output: False
# A rectangle is not a square
isinstance(s, Rectangle)
# Output: True
# A square is a rectangle
isinstance(s, Square)
# Output: True
Klasse- en instantievariabelen
Instantievariabelen zijn uniek voor elke instantie, terwijl klassenvariabelen door alle instanties worden gedeeld.
class C:
x = 2 # class variable
def __init__(self, y):
self.y = y # instance variable
C.x
# 2
C.y
# AttributeError: type object 'C' has no attribute 'y'
c1 = C(3)
c1.x
# 2
c1.y
# 3
c2 = C(4)
c2.x
# 2
c2.y
# 4
Klasse-variabelen zijn toegankelijk op instanties van deze klasse, maar door aan het klasse-attribuut toe te wijzen, wordt een instantievariabele gemaakt die de klasse-variabele in de schaduw stelt
c2.x = 4
c2.x
# 4
C.x
# 2
Merk op dat het muteren van klassenvariabelen uit instanties kan leiden tot enkele onverwachte gevolgen.
class D:
x = []
def __init__(self, item):
self.x.append(item) # note that this is not an assigment!
d1 = D(1)
d2 = D(2)
d1.x
# [1, 2]
d2.x
# [1, 2]
D.x
# [1, 2]
Gebonden, ongebonden en statische methoden
Het idee van gebonden en ongebonden methoden werd verwijderd in Python 3 . Wanneer u in Python 3 een methode binnen een klasse declareert, gebruikt u een sleutelwoord def
, waardoor een functieobject wordt gemaakt. Dit is een normale functie en de omringende klasse werkt als naamruimte. In het volgende voorbeeld verklaren we methode f
binnen klasse A
, en het wordt een functie Af
:
class A(object):
def f(self, x):
return 2 * x
A.f
# <function A.f at ...> (in Python 3.x)
In Python 2 was het gedrag anders: functieobjecten binnen de klasse werden impliciet vervangen door objecten van het type instancemethod
, die ongebonden methoden werden genoemd omdat ze niet aan een bepaalde klasse-instantie waren gebonden. Het was mogelijk om toegang te krijgen tot de onderliggende functie met de .__func__
.
A.f
# <unbound method A.f> (in Python 2.x)
A.f.__class__
# <type 'instancemethod'>
A.f.__func__
# <function f at ...>
Dit laatste gedrag wordt bevestigd door inspectie - methoden worden als functies herkend in Python 3, terwijl het onderscheid wordt gehandhaafd in Python 2.
import inspect
inspect.isfunction(A.f)
# True
inspect.ismethod(A.f)
# False
import inspect
inspect.isfunction(A.f)
# False
inspect.ismethod(A.f)
# True
In beide versies van Python kan functie / methode Af
rechtstreeks worden aangeroepen, op voorwaarde dat u een instantie van klasse A
als eerste argument doorgeeft.
A.f(1, 7)
# Python 2: TypeError: unbound method f() must be called with
# A instance as first argument (got int instance instead)
# Python 3: 14
a = A()
A.f(a, 20)
# Python 2 & 3: 40
Stel nu dat a
is een instantie van de klasse A
, wat is af
dan? Welnu, intuïtief zou dit dezelfde methode f
van klasse A
, alleen zou het op de een of andere manier moeten "weten" dat het op het object a
werd toegepast - in Python wordt dit methode gebonden aan a
.
De nitty-gritty details zijn als volgt: schrijven af
roept de magische __getattribute__
methode van a
, die eerst controleert of a
een attribuut met de naam f
(dat doet het niet), dan controleert de klasse A
of het een methode met een dergelijke naam bevat (wel), en maakt een nieuw object m
van het type method
met de verwijzing naar de oorspronkelijke Af
in m.__func__
, en een verwijzing naar het object a
in m.__self__
. Wanneer dit object als functie wordt aangeroepen, doet het eenvoudig het volgende: m(...) => m.__func__(m.__self__, ...)
. Dit object wordt dus een gebonden methode genoemd omdat het, wanneer het wordt aangeroepen, het object kan leveren waaraan het was gebonden als het eerste argument. (Deze dingen werken op dezelfde manier in Python 2 en 3).
a = A()
a.f
# <bound method A.f of <__main__.A object at ...>>
a.f(2)
# 4
# Note: the bound method object a.f is recreated *every time* you call it:
a.f is a.f # False
# As a performance optimization you can store the bound method in the object's
# __dict__, in which case the method object will remain fixed:
a.f = a.f
a.f is a.f # True
Ten slotte heeft Python klassemethoden en statische methoden - speciale soorten methoden. Class-methoden werken op dezelfde manier als reguliere methoden, behalve dat wanneer ze op een object worden aangeroepen, ze binden aan de klasse van het object in plaats van aan het object. Dus m.__self__ = type(a)
. Wanneer u een dergelijke gebonden methode aanroept, wordt de klasse a
als eerste argument doorgegeven. Statische methoden zijn nog eenvoudiger: ze binden helemaal niets en geven eenvoudig de onderliggende functie terug zonder enige transformaties.
class D(object):
multiplier = 2
@classmethod
def f(cls, x):
return cls.multiplier * x
@staticmethod
def g(name):
print("Hello, %s" % name)
D.f
# <bound method type.f of <class '__main__.D'>>
D.f(12)
# 24
D.g
# <function D.g at ...>
D.g("world")
# Hello, world
Merk op dat klassemethoden aan de klas zijn gebonden, zelfs wanneer ze in de instantie worden gebruikt:
d = D()
d.multiplier = 1337
(D.multiplier, d.multiplier)
# (2, 1337)
d.f
# <bound method D.f of <class '__main__.D'>>
d.f(10)
# 20
Het is vermeldenswaard dat op het laagste niveau, functies, methoden, statische methoden, enz. In feite descriptoren zijn die __get__
, __set
__ en optioneel __del__
speciale methoden __del__
. Voor meer informatie over klassemethoden en statische methoden:
- Wat is het verschil tussen @staticmethod en @classmethod in Python?
- Betekenis van @classmethod en @staticmethod voor beginners?
Nieuwe stijl versus oude stijl klassen
Nieuwe klassen werden geïntroduceerd in Python 2.2 om klassen en typen te verenigen. Ze erven van het object
het hoogste niveau. Een klasse in nieuwe stijl is een door de gebruiker gedefinieerd type en lijkt sterk op ingebouwde typen.
# new-style class
class New(object):
pass
# new-style instance
new = New()
new.__class__
# <class '__main__.New'>
type(new)
# <class '__main__.New'>
issubclass(New, object)
# True
Klassen in oude stijl erven niet van een object
. Oude exemplaren worden altijd geïmplementeerd met een ingebouwd instance
.
# old-style class
class Old:
pass
# old-style instance
old = Old()
old.__class__
# <class __main__.Old at ...>
type(old)
# <type 'instance'>
issubclass(Old, object)
# False
In Python 3 werden klassen in oude stijl verwijderd.
Nieuwe- MyClass(object)
in Python 3 nemen impliciet van het object
, dus het is niet meer nodig om MyClass(object)
te geven.
class MyClass:
pass
my_inst = MyClass()
type(my_inst)
# <class '__main__.MyClass'>
my_inst.__class__
# <class '__main__.MyClass'>
issubclass(MyClass, object)
# True
Standaardwaarden voor bijvoorbeeld variabelen
Als de variabele een waarde van een onveranderlijk type (bijvoorbeeld een tekenreeks) bevat, is het prima om een standaardwaarde zoals deze toe te wijzen
class Rectangle(object):
def __init__(self, width, height, color='blue'):
self.width = width
self.height = height
self.color = color
def area(self):
return self.width * self.height
# Create some instances of the class
default_rectangle = Rectangle(2, 3)
print(default_rectangle.color) # blue
red_rectangle = Rectangle(2, 3, 'red')
print(red_rectangle.color) # red
Men moet voorzichtig zijn bij het initialiseren van veranderlijke objecten zoals lijsten in de constructor. Overweeg het volgende voorbeeld:
class Rectangle2D(object):
def __init__(self, width, height, pos=[0,0], color='blue'):
self.width = width
self.height = height
self.pos = pos
self.color = color
r1 = Rectangle2D(5,3)
r2 = Rectangle2D(7,8)
r1.pos[0] = 4
r1.pos # [4, 0]
r2.pos # [4, 0] r2's pos has changed as well
Dit gedrag wordt veroorzaakt door het feit dat in Python standaardparameters zijn gebonden aan functie-uitvoering en niet aan functieverklaring. Om een standaardinstellingsvariabele te krijgen die niet wordt gedeeld tussen instanties, moet u een constructie als deze gebruiken:
class Rectangle2D(object):
def __init__(self, width, height, pos=None, color='blue'):
self.width = width
self.height = height
self.pos = pos or [0, 0] # default value is [0, 0]
self.color = color
r1 = Rectangle2D(5,3)
r2 = Rectangle2D(7,8)
r1.pos[0] = 4
r1.pos # [4, 0]
r2.pos # [0, 0] r2's pos hasn't changed
Zie ook Veranderlijke standaardargumenten en "Minste verbazing" en het Veranderlijke standaardargument .
Meerdere overerving
Python gebruikt het C3-linearisatie- algoritme om de volgorde te bepalen waarin klassenattributen moeten worden opgelost, inclusief methoden. Dit staat bekend als de Method Resolution Order (MRO).
Hier is een eenvoudig voorbeeld:
class Foo(object):
foo = 'attr foo of Foo'
class Bar(object):
foo = 'attr foo of Bar' # we won't see this.
bar = 'attr bar of Bar'
class FooBar(Foo, Bar):
foobar = 'attr foobar of FooBar'
Als we nu FooBar instantiëren, als we het foo-kenmerk opzoeken, zien we dat het kenmerk van Foo eerst wordt gevonden
fb = FooBar()
en
>>> fb.foo
'attr foo of Foo'
Hier is de MRO van FooBar:
>>> FooBar.mro()
[<class '__main__.FooBar'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <type 'object'>]
Men kan eenvoudig stellen dat het MRO-algoritme van Python dat is
- Diepte eerst (bijv.
FooBar
danFoo
) tenzij - een gedeelde ouder (
object
) wordt geblokkeerd door een kind (Bar
) en - geen circulaire relaties toegestaan.
Dat wil zeggen, Bar kan bijvoorbeeld niet erven van FooBar terwijl FooBar erft van Bar.
Zie het wikipedia-item voor een uitgebreid voorbeeld in Python.
Een andere krachtige eigenschap in overerving is super
. super kan functies van bovenliggende klassen ophalen.
class Foo(object):
def foo_method(self):
print "foo Method"
class Bar(object):
def bar_method(self):
print "bar Method"
class FooBar(Foo, Bar):
def foo_method(self):
super(FooBar, self).foo_method()
Meerdere overerving met init methode van klasse, wanneer elke klasse een eigen init methode heeft, dan proberen we voor meervoudige ineritance dan wordt alleen init methode van klasse aangeroepen die eerst overgeërfd wordt.
voor onderstaand voorbeeld wordt alleen Foo class init methode aangeroepen Bar class init wordt niet aangeroepen
class Foo(object):
def __init__(self):
print "foo init"
class Bar(object):
def __init__(self):
print "bar init"
class FooBar(Foo, Bar):
def __init__(self):
print "foobar init"
super(FooBar, self).__init__()
a = FooBar()
Output:
foobar init
foo init
Maar het betekent niet dat Bar- klasse niet erven is. Exemplaar van de laatste klasse FooBar is ook exemplaar van de klasse Bar en Foo .
print isinstance(a,FooBar)
print isinstance(a,Foo)
print isinstance(a,Bar)
Output:
True
True
True
Descriptors en gestippelde lookups
Beschrijvingen zijn objecten die (meestal) attributen van klassen zijn en die een van de speciale methoden __get__
, __set__
of __delete__
hebben.
Gegevensbeschrijvingen hebben een van __set__
of __delete__
Hierdoor kan de gestippelde opzoeken van een instantie controle en worden gebruikt voor het implementeren functies staticmethod
, classmethod
en property
. Een gestippelde lookup (bijv bijvoorbeeld foo
van de klasse Foo
opzoeken attribuut bar
- dwz foo.bar
) maakt gebruik van de volgende algoritme:
bar
wordt opgezocht in de klas,Foo
. Als het er is en het is een Data Descriptor , dan wordt de data descriptor gebruikt. Zo kanproperty
de toegang tot gegevens in een exemplaar beheren en kunnen instanties dit niet overschrijven. Als een Data Descriptor er niet is, danbar
wordt opgezocht in het exemplaar__dict__
. Daarom kunnen we methoden die worden aangeroepen vanuit een instantie met een gestippelde opzoeking negeren of blokkeren. Als in de instantie eenbar
bestaat, wordt deze gebruikt. Zo niet, dan welkijk in de klasse
Foo
voorbar
. Als het een descriptor is , wordt het descriptorprotocol gebruikt. Dit is hoe functies (in deze context ongebonden methoden),classmethod
enstaticmethod
worden geïmplementeerd. Anders retourneert het eenvoudig het object daar, of is er eenAttributeError
Klasse methoden: alternatieve initializers
Klasse methoden presenteren alternatieve manieren om instanties van klassen te bouwen. Laten we ter illustratie een voorbeeld bekijken.
Laten we aannemen dat we een relatief eenvoudige Person
:
class Person(object):
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
self.full_name = first_name + " " + last_name
def greet(self):
print("Hello, my name is " + self.full_name + ".")
Het kan handig zijn om een manier te hebben om instanties van deze klasse te bouwen door een volledige naam op te geven in plaats van de voor- en achternaam afzonderlijk. Een manier om dit te doen zou zijn om last_name
een optionele parameter te hebben, en ervan uitgaande dat als deze niet wordt gegeven, we de volledige naam hebben doorgegeven in:
class Person(object):
def __init__(self, first_name, age, last_name=None):
if last_name is None:
self.first_name, self.last_name = first_name.split(" ", 2)
else:
self.first_name = first_name
self.last_name = last_name
self.full_name = self.first_name + " " + self.last_name
self.age = age
def greet(self):
print("Hello, my name is " + self.full_name + ".")
Er zijn echter twee hoofdproblemen met dit stukje code:
De parameters
first_name
enlast_name
worden nu misleidend, omdat u een volledige naam kunt invoerenfirst_name
. Als er meer gevallen en / of meer parameters zijn met dit soort flexibiliteit, kan de if / elif / else-vertakking snel vervelend worden.Niet zo belangrijk, maar het is toch de moeite waard erop te wijzen: wat als
last_name
None
, maarfirst_name
niet in twee of meer dingen splitst via spaties? We hebben nog een andere laag invoervalidatie en / of uitzonderingsbehandeling ...
Voer klassenmethoden in. In plaats van een enkele initialisatie, maken we een afzonderlijke initialisatie, genaamd from_full_name
, en decoreren deze met de (ingebouwde) classmethod
decorateur.
class Person(object):
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
self.full_name = first_name + " " + last_name
@classmethod
def from_full_name(cls, name, age):
if " " not in name:
raise ValueError
first_name, last_name = name.split(" ", 2)
return cls(first_name, last_name, age)
def greet(self):
print("Hello, my name is " + self.full_name + ".")
Let op cls
plaats van self
als het eerste argument voor from_full_name
. Class-methoden worden toegepast op de algehele klasse, niet op een instantie van een bepaalde klasse (hetgeen self
meestal aangeeft). Als cls
dus onze klasse Person
, is de geretourneerde waarde van de methode from_full_name
class Person(first_name, last_name, age)
, die de Person
__init__
gebruikt om een instantie van de klasse Person
. In het bijzonder, als we een subklasse Employee
of Person
, zou from_full_name
ook in de klasse Employee
werken.
Om te laten zien dat dit werkt zoals verwacht, laten we creëren gevallen van Person
in meer dan één manier, zonder de vertakking in __init__
:
In [2]: bob = Person("Bob", "Bobberson", 42)
In [3]: alice = Person.from_full_name("Alice Henderson", 31)
In [4]: bob.greet()
Hello, my name is Bob Bobberson.
In [5]: alice.greet()
Hello, my name is Alice Henderson.
Andere referenties:
https://docs.python.org/2/library/functions.html#classmethod
https://docs.python.org/3.5/library/functions.html#classmethod
Klasse samenstelling
Klasse samenstelling maakt expliciete relaties tussen objecten mogelijk. In dit voorbeeld wonen mensen in steden die tot landen behoren. Samenstelling geeft mensen toegang tot het aantal mensen dat in hun land woont:
class Country(object):
def __init__(self):
self.cities=[]
def addCity(self,city):
self.cities.append(city)
class City(object):
def __init__(self, numPeople):
self.people = []
self.numPeople = numPeople
def addPerson(self, person):
self.people.append(person)
def join_country(self,country):
self.country = country
country.addCity(self)
for i in range(self.numPeople):
person(i).join_city(self)
class Person(object):
def __init__(self, ID):
self.ID=ID
def join_city(self, city):
self.city = city
city.addPerson(self)
def people_in_my_country(self):
x= sum([len(c.people) for c in self.city.country.cities])
return x
US=Country()
NYC=City(10).join_country(US)
SF=City(5).join_country(US)
print(US.cities[0].people[0].people_in_my_country())
# 15
Monkey Patching
In dit geval betekent "patching van de aap" het toevoegen van een nieuwe variabele of methode aan een klasse nadat deze is gedefinieerd. Stel bijvoorbeeld dat we klasse A
hebben gedefinieerd als
class A(object):
def __init__(self, num):
self.num = num
def __add__(self, other):
return A(self.num + other.num)
Maar nu willen we later een andere functie in de code toevoegen. Stel dat deze functie als volgt is.
def get_num(self):
return self.num
Maar hoe voegen we dit toe als methode in A
? Dat is eenvoudig, we plaatsen die functie in wezen gewoon in A
met een toewijzingsverklaring.
A.get_num = get_num
Waarom werkt dit Omdat functies objecten zijn zoals elk ander object, en methoden functies zijn die tot de klasse behoren.
De functie get_num
is beschikbaar voor alle bestaande (al gemaakte) en nieuwe instanties van A
Deze toevoegingen zijn automatisch beschikbaar op alle instanties van die klasse (of de subklassen). Bijvoorbeeld:
foo = A(42)
A.get_num = get_num
bar = A(6);
foo.get_num() # 42
bar.get_num() # 6
Merk op dat, in tegenstelling tot sommige andere talen, deze techniek niet werkt voor bepaalde ingebouwde typen en dat deze niet als een goede stijl wordt beschouwd.
Lijst met alle klasleden
De functie dir()
kan worden gebruikt om een lijst met de leden van een klasse op te halen:
dir(Class)
Bijvoorbeeld:
>>> dir(list)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Het is gebruikelijk om alleen naar "niet-magische" leden te zoeken. Dit kan worden gedaan met behulp van een eenvoudig begrip dat leden weergeeft met namen die niet beginnen met __
:
>>> [m for m in dir(list) if not m.startswith('__')]
['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Voorbehoud:
Klassen kunnen een __dir__()
-methode definiëren. Als die methode bestaat, roept dir()
__dir__()
, anders probeert Python een lijst met leden van de klasse te maken. Dit betekent dat de dir-functie onverwachte resultaten kan hebben. Twee belangrijke citaten uit de officiële python-documentatie :
Als het object geen dir () levert, probeert de functie zijn best om informatie te verzamelen uit het kenmerk dict van het object, indien gedefinieerd, en uit het type object. De resulterende lijst is niet noodzakelijk volledig en kan onnauwkeurig zijn wanneer het object een aangepaste getattr () heeft.
Opmerking: omdat dir () voornamelijk wordt geleverd als gebruiksgemak bij een interactieve prompt, probeert het meer een interessante set namen te leveren dan een rigoureus of consistent gedefinieerde set namen probeert op te leveren, en het gedetailleerde gedrag kan veranderen releases. Metaclass-attributen staan bijvoorbeeld niet in de resultatenlijst wanneer het argument een klasse is.
Inleiding tot lessen
Een klasse functioneert als een sjabloon die de basiskenmerken van een bepaald object definieert. Hier is een voorbeeld:
class Person(object):
"""A simple class.""" # docstring
species = "Homo Sapiens" # class attribute
def __init__(self, name): # special method
"""This is the initializer. It's a special
method (see below).
"""
self.name = name # instance attribute
def __str__(self): # special method
"""This method is run when Python tries
to cast the object to a string. Return
this string when using print(), etc.
"""
return self.name
def rename(self, renamed): # regular method
"""Reassign and print the name attribute."""
self.name = renamed
print("Now my name is {}".format(self.name))
Er zijn een paar dingen waar u op moet letten bij het bekijken van het bovenstaande voorbeeld.
- De klasse bestaat uit attributen (gegevens) en methoden (functies).
- Attributen en methoden worden eenvoudig gedefinieerd als normale variabelen en functies.
- Zoals vermeld in de bijbehorende docstring, wordt de
__init__()
-methode de initializer genoemd . Het is equivalent aan de constructor in andere objectgeoriënteerde talen en is de methode die voor het eerst wordt uitgevoerd wanneer u een nieuw object of een nieuw exemplaar van de klasse maakt. - Attributen die van toepassing zijn op de hele klasse worden eerst gedefinieerd en worden class-attributen genoemd .
- Attributen die van toepassing zijn op een specifieke instantie van een klasse (een object) worden instantiekenmerken genoemd. Ze worden meestal gedefinieerd in
__init__()
; dit is niet nodig, maar het wordt aanbevolen (omdat attributen die zijn gedefinieerd buiten__init__()
het risico lopen om te worden benaderd voordat ze worden gedefinieerd). - Elke methode, opgenomen in de klassedefinitie, geeft het betreffende object door als zijn eerste parameter. Het woord
self
wordt gebruikt voor deze parameter (gebruik vanself
is eigenlijk volgens afspraak, omdat het woordself
geen inherente betekenis heeft in Python, maar dit is een van de meest gerespecteerde conventies van Python, en u moet het altijd volgen). - Degenen die gewend waren aan objectgeoriënteerd programmeren in andere talen, kunnen verrast zijn door een paar dingen. Een daarvan is dat Python geen echt concept van
private
elementen heeft, dus alles imiteert standaard het gedrag van hetpublic
sleutelwoord C ++ / Java. Voor meer informatie, zie het voorbeeld van "Private Class Members" op deze pagina. - Sommige methoden van de klasse hebben de volgende vorm:
__functionname__(self, other_stuff)
. Al dergelijke methoden worden "magische methoden" genoemd en vormen een belangrijk onderdeel van klassen in Python. Overbelasting van de operator in Python is bijvoorbeeld geïmplementeerd met magische methoden. Zie de relevante documentatie voor meer informatie.
Laten we nu een paar voorbeelden maken van onze Person
klasse!
>>> # Instances
>>> kelly = Person("Kelly")
>>> joseph = Person("Joseph")
>>> john_doe = Person("John Doe")
We hebben momenteel drie Person
objecten, kelly
, joseph
, en john_doe
.
We hebben toegang tot de attributen van de klasse vanuit elke instantie met behulp van de puntoperator .
Let nogmaals op het verschil tussen klasse- en instantiekenmerken:
>>> # Attributes
>>> kelly.species
'Homo Sapiens'
>>> john_doe.species
'Homo Sapiens'
>>> joseph.species
'Homo Sapiens'
>>> kelly.name
'Kelly'
>>> joseph.name
'Joseph'
We kunnen de methoden van de klasse uitvoeren met dezelfde dot-operator .
:
>>> # Methods
>>> john_doe.__str__()
'John Doe'
>>> print(john_doe)
'John Doe'
>>> john_doe.rename("John")
'Now my name is John'
Eigendommen
Python-klassen ondersteunen eigenschappen , die eruit zien als normale objectvariabelen, maar met de mogelijkheid om aangepast gedrag en documentatie te koppelen.
class MyClass(object):
def __init__(self):
self._my_string = ""
@property
def string(self):
"""A profoundly important string."""
return self._my_string
@string.setter
def string(self, new_value):
assert isinstance(new_value, str), \
"Give me a string, not a %r!" % type(new_value)
self._my_string = new_value
@string.deleter
def x(self):
self._my_string = None
De objecten van klasse MyClass
lijken een eigenschap .string
, maar het gedrag wordt nu strak gecontroleerd:
mc = MyClass()
mc.string = "String!"
print(mc.string)
del mc.string
Naast de handige syntaxis zoals hierboven, maakt de eigenschapssyntaxis het mogelijk om validatie of andere augmentaties aan die attributen toe te voegen. Dit kan met name handig zijn bij openbare API's - waarbij een niveau van hulp moet worden geboden aan de gebruiker.
Een ander veelgebruikt gebruik van eigenschappen is om de klasse in staat te stellen 'virtuele attributen' te presenteren - attributen die niet daadwerkelijk worden opgeslagen maar alleen worden berekend op verzoek.
class Character(object):
def __init__(name, max_hp):
self._name = name
self._hp = max_hp
self._max_hp = max_hp
# Make hp read only by not providing a set method
@property
def hp(self):
return self._hp
# Make name read only by not providing a set method
@property
def name(self):
return self.name
def take_damage(self, damage):
self.hp -= damage
self.hp = 0 if self.hp <0 else self.hp
@property
def is_alive(self):
return self.hp != 0
@property
def is_wounded(self):
return self.hp < self.max_hp if self.hp > 0 else False
@property
def is_dead(self):
return not self.is_alive
bilbo = Character('Bilbo Baggins', 100)
bilbo.hp
# out : 100
bilbo.hp = 200
# out : AttributeError: can't set attribute
# hp attribute is read only.
bilbo.is_alive
# out : True
bilbo.is_wounded
# out : False
bilbo.is_dead
# out : False
bilbo.take_damage( 50 )
bilbo.hp
# out : 50
bilbo.is_alive
# out : True
bilbo.is_wounded
# out : True
bilbo.is_dead
# out : False
bilbo.take_damage( 50 )
bilbo.hp
# out : 0
bilbo.is_alive
# out : False
bilbo.is_wounded
# out : False
bilbo.is_dead
# out : True
Singleton-klasse
Een singleton is een patroon dat de instantiatie van een klasse beperkt tot één instantie / object. Voor meer informatie over python singleton design patterns, zie hier .
class Singleton:
def __new__(cls):
try:
it = cls.__it__
except AttributeError:
it = cls.__it__ = object.__new__(cls)
return it
def __repr__(self):
return '<{}>'.format(self.__class__.__name__.upper())
def __eq__(self, other):
return other is self
Een andere methode is om je klas te versieren. Volg het voorbeeld van dit antwoord en maak een Singleton-klasse:
class Singleton:
"""
A non-thread-safe helper class to ease implementing singletons.
This should be used as a decorator -- not a metaclass -- to the
class that should be a singleton.
The decorated class can define one `__init__` function that
takes only the `self` argument. Other than that, there are
no restrictions that apply to the decorated class.
To get the singleton instance, use the `Instance` method. Trying
to use `__call__` will result in a `TypeError` being raised.
Limitations: The decorated class cannot be inherited from.
"""
def __init__(self, decorated):
self._decorated = decorated
def Instance(self):
"""
Returns the singleton instance. Upon its first call, it creates a
new instance of the decorated class and calls its `__init__` method.
On all subsequent calls, the already created instance is returned.
"""
try:
return self._instance
except AttributeError:
self._instance = self._decorated()
return self._instance
def __call__(self):
raise TypeError('Singletons must be accessed through `Instance()`.')
def __instancecheck__(self, inst):
return isinstance(inst, self._decorated)
U kunt de methode Instance
gebruiken
@Singleton
class Single:
def __init__(self):
self.name=None
self.val=0
def getName(self):
print(self.name)
x=Single.Instance()
y=Single.Instance()
x.name='I\'m single'
x.getName() # outputs I'm single
y.getName() # outputs I'm single