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 :

Python 3.x 3.0
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__ .

Python 2.x 2.3
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.

Python 3.x 3.0
import inspect

inspect.isfunction(A.f)
# True
inspect.ismethod(A.f)
# False
Python 2.x 2.3
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:

Nieuwe stijl versus oude stijl klassen

Python 2.x 2.2.0

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
Python 3.x 3.0.0

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

  1. Diepte eerst (bijv. FooBar dan Foo ) tenzij
  2. een gedeelde ouder ( object ) wordt geblokkeerd door een kind ( Bar ) en
  3. 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:

  1. bar wordt opgezocht in de klas, Foo . Als het er is en het is een Data Descriptor , dan wordt de data descriptor gebruikt. Zo kan property de toegang tot gegevens in een exemplaar beheren en kunnen instanties dit niet overschrijven. Als een Data Descriptor er niet is, dan

  2. bar 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 een bar bestaat, wordt deze gebruikt. Zo niet, dan wel

  3. kijk in de klasse Foo voor bar . Als het een descriptor is , wordt het descriptorprotocol gebruikt. Dit is hoe functies (in deze context ongebonden methoden), classmethod en staticmethod worden geïmplementeerd. Anders retourneert het eenvoudig het object daar, of is er een AttributeError

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:

  1. De parameters first_name en last_name worden nu misleidend, omdat u een volledige naam kunt invoeren first_name . Als er meer gevallen en / of meer parameters zijn met dit soort flexibiliteit, kan de if / elif / else-vertakking snel vervelend worden.

  2. Niet zo belangrijk, maar het is toch de moeite waard erop te wijzen: wat als last_name None , maar first_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:

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.

  1. De klasse bestaat uit attributen (gegevens) en methoden (functies).
  2. Attributen en methoden worden eenvoudig gedefinieerd als normale variabelen en functies.
  3. 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.
  4. Attributen die van toepassing zijn op de hele klasse worden eerst gedefinieerd en worden class-attributen genoemd .
  5. 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).
  6. Elke methode, opgenomen in de klassedefinitie, geeft het betreffende object door als zijn eerste parameter. Het woord self wordt gebruikt voor deze parameter (gebruik van self is eigenlijk volgens afspraak, omdat het woord self geen inherente betekenis heeft in Python, maar dit is een van de meest gerespecteerde conventies van Python, en u moet het altijd volgen).
  7. 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 het public sleutelwoord C ++ / Java. Voor meer informatie, zie het voorbeeld van "Private Class Members" op deze pagina.
  8. 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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow