Sök…


Introduktion

Python erbjuder sig inte bara som ett populärt skriptspråk utan stöder också det objektorienterade programmeringsparadigmet. Klasser beskriver data och tillhandahåller metoder för att manipulera dessa data, alla omfattade under ett enda objekt. Dessutom tillåter klasser abstraktion genom att separera konkreta implementeringsdetaljer från abstrakta representationer av data.

Koder med klasser är i allmänhet lättare att läsa, förstå och underhålla.

Grundläggande arv

Arv i Python baseras på liknande idéer som används i andra objektorienterade språk som Java, C ++ etc. En ny klass kan härledas från en befintlig klass enligt följande.

class BaseClass(object):
    pass

class DerivedClass(BaseClass):
    pass

BaseClass är den redan befintliga ( överordnade ) klassen, och DerivedClass är den nya ( barn ) klassen som ärver (eller underklasser ) attribut från BaseClass . Obs : Från Python 2.2 ärver alla klasser implicit från object , som är basklassen för alla inbyggda typer.

Vi definierar en förälder Rectangle klass i exemplet nedan, vilket implicit ärver från object :

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)

Den Rectangle klassen kan användas som en basklass för att definiera en Square klass, såsom en kvadrat är en specialfall av rektangeln.

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

Square klassen ärver automatiskt alla attribut i Rectangle såväl som objektklassen. super() används för att kalla __init__() -metoden i Rectangle , väsentligen kallar alla åsidosatta metoder i basklassen. Obs : i Python 3 kräver inte super() argument.

Deriverade klassobjekt kan komma åt och ändra attributen för dess basklasser:

r.area()
# Output: 12
r.perimeter()  
# Output: 14

s.area()
# Output: 4
s.perimeter()
# Output: 8

Inbyggda funktioner som fungerar med arv

issubclass(DerivedClass, BaseClass) : returnerar True om DerivedClass är en underklass för BaseClass

isinstance(s, Class) : returnerar True om s är ett exempel på Class eller någon av de härledda klasserna i 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

Klass- och instansvariabler

Instansvariabler är unika för varje instans, medan klassvariabler delas av alla instanser.

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

Klassvariabler kan nås i instanser av denna klass, men tilldelning till klassattributet kommer att skapa en instansvariabel som skuggar klassvariabeln

c2.x = 4
c2.x
# 4
C.x
# 2

Observera att mutation av klassvariabler från instanser kan leda till några oväntade konsekvenser.

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]

Bundna, obundna och statiska metoder

Idén om bundna och obundna metoder togs bort i Python 3 . I Python 3 när du förklarar en metod i en klass använder du ett def nyckelord och skapar därmed ett funktionsobjekt. Detta är en vanlig funktion, och den omgivande klassen fungerar som sitt namnområde. I följande exempel förklarar vi metod f inom klass A , och det blir en funktion 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)

I Python 2 var beteendet annorlunda: funktionsobjekt inom klassen ersattes implicit med objekt av typen instancemethod , som kallades obundna metoder eftersom de inte var bundna till någon speciell klassinstans. Det var möjligt att komma åt den underliggande funktionen med .__func__ egenskapen.

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 ...>

Det senare beteendet bekräftas genom inspektion - metoder erkänns som funktioner i Python 3, medan skillnaden upprätthålls i 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

I båda versionerna av Python-funktion / -metod kan Af kallas direkt, förutsatt att du passerar en instans av klass A som det första argumentet.

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

Anta nu a är en instans av klassen A , vad är af då? Tja, intuitivt skulle detta vara samma metod f i klass A , bara det på något sätt skulle "veta" att det applicerades på objektet a - i Python kallas detta metod bundet till a .

De snygga-grymma detaljerna är följande: skrivning af åberopar den magiska __getattribute__ metoden för a , som först kontrollerar om a har ett attribut som heter f (det gör det inte), sedan kontrollerar klassen A om den innehåller en metod med ett sådant namn (det gör), och skapar ett nytt objekt m av method som har referensen till det ursprungliga Af i m.__func__ , och en referens till objektet a i m.__self__ . När detta objekt kallas som en funktion gör det helt enkelt följande: m(...) => m.__func__(m.__self__, ...) . Således kallas detta objekt en bunden metod för när den åberopas vet den att tillhandahålla objektet det var bunden till som det första argumentet. (Dessa saker fungerar på samma sätt i Python 2 och 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

Slutligen har Python klassmetoder och statiska metoder - speciella typer av metoder. Klassmetoder fungerar på samma sätt som vanliga metoder, förutom att när de anropas till ett objekt binder de sig till objektets klass istället för till objektet. Således m.__self__ = type(a) . När du kallar en sådan bunden metod, passerar den klassen a som det första argumentet. Statiska metoder är ännu enklare: de binder ingenting alls och returnerar helt enkelt den underliggande funktionen utan några omvandlingar.

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

Observera att klassmetoderna är bundna till klassen även när de öppnas på instansen:

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

Det är värt att notera att på den lägsta nivån är funktioner, metoder, statiska metoder osv. Beskrivningar som åberopar __get__ , __set __ och eventuellt __del__ specialmetoder. För mer information om klassmetoder och statiska metoder:

Ny stil kontra gamla stil klasser

Python 2.x 2.2.0

Klasser i ny stil introducerades i Python 2.2 för att förena klasser och typer . De ärver från object toppnivå. En klass i ny stil är en användardefinierad typ och liknar mycket inbyggda typer.

# 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

Gammaldags klasser ärver inte från object . Gamla instanser implementeras alltid med en inbyggd 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

I Python 3 togs gamla klasser bort.

Klasser i ny stil i Python 3 arv implicit från object , så det finns inget behov att specificera MyClass(object) längre.

class MyClass:
    pass

my_inst = MyClass()

type(my_inst)
# <class '__main__.MyClass'>
my_inst.__class__
# <class '__main__.MyClass'>
issubclass(MyClass, object)
# True

Standardvärden för exempelvis variabler

Om variabeln innehåller ett värde av en immutable typ (t.ex. en sträng) är det okej att tilldela ett standardvärde som det här

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

Man måste vara försiktig när man initialiserar muterbara objekt som listor i konstruktören. Tänk på följande exempel:

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

Detta beteende orsakas av det faktum att i Python är standardparametrar bundna vid funktionsutförande och inte vid funktionsdeklaration. För att få en standardinstansvariabel som inte delas mellan instanser bör man använda en konstruktion som denna:

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 

Se även Mutable Default Arguments och "Minsta förvånande" och det Mutable Default Argument .

Multipel ärft

Python använder linjäriseringsalgoritmen C3 för att bestämma i vilken ordning klassen ska kunna lösas, inklusive metoder. Detta kallas MRO (Method Resolution Order).

Här är ett enkelt exempel:

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'

Om vi nu ger FooBar, om vi letar upp foo-attributet, ser vi att Foos attribut hittas först

fb = FooBar()

och

>>> fb.foo
'attr foo of Foo'

Här är FooBars MRO:

>>> FooBar.mro()
[<class '__main__.FooBar'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <type 'object'>]

Det kan enkelt sägas att Pythons MRO-algoritm är det

  1. Djup först (t.ex. FooBar sedan Foo ) om inte
  2. en delad förälder ( object ) blockeras av ett barn ( Bar ) och
  3. inga cirkulära förhållanden tillåtna.

Det är till exempel att Bar inte kan ärva från FooBar medan FooBar ärver från Bar.

För ett omfattande exempel i Python, se wikipedia-posten .

En annan kraftfull egenskap i arv är super . super kan hämta funktioner för överordnade klasser.

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()

Multipel arv med klassens initmetod, när varje klass har en egen initmetod så försöker vi med flera ineritanser då bara initmetoden får kallas klass som ärvs först.

till exempel bara Foo klass init metod att kallas Bar klass init inte blir uppringd

    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()

Produktion:

    foobar init
    foo init

Men det betyder inte att Bar- klass inte ärver. Förekomsten av den sista FooBar- klassen är också instansen för Bar- klass och Foo- klassen.

print isinstance(a,FooBar)
print isinstance(a,Foo)
print isinstance(a,Bar) 

Produktion:

True
True
True

Beskrivare och prickade uppslag

Beskrivare är objekt som är (vanligtvis) attribut för klasser och som har någon av __get__ , __set__ eller __delete__ specialmetoder.

Data Descriptors har någon av __set__ eller __delete__

Dessa kan kontrollera den prickade uppslagningen på en instans och används för att implementera funktioner, staticmethod , classmethod och property . En prickad lookup (t.ex. exempel foo av klass Foo tittar upp bar - dvs foo.bar ) använder följande algoritm:

  1. bar tittas upp i klassen, Foo . Om den är där och det är en dataskriptor , används dataskrivaren. Så här kan property kontrollera åtkomst till data i en instans, och instanser kan inte åsidosätta detta. Om en Data Descriptor inte finns där, då

  2. bar letas upp i förekomsten __dict__ . Det är därför vi kan åsidosätta eller blockera metoder som kallas från en instans med en prickad uppslagning. Om bar finns i förekomsten används det. Om inte, vi då

  3. titta i klass Foo för bar . Om det är en deskriptor används descriptorprotokollet. Så implementeras funktioner (i detta sammanhang obundna metoder), classmethod och staticmethod . Annars returnerar det helt enkelt objektet där, eller så finns det en AttributeError

Klassmetoder: alternativa initialiseringar

Klassmetoder presenterar alternativa sätt att bygga instanser av klasser. För att illustrera, låt oss titta på ett exempel.

Låt oss anta att vi har en relativt enkel 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 + ".")

Det kan vara praktiskt att ha ett sätt att bygga instanser av den här klassen som anger ett fullt namn istället för för- och efternamn separat. Ett sätt att göra detta skulle vara att last_name vara en valfri parameter, och förutsatt att om det inte anges, gick vi över hela namnet i:

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 + ".")

Det finns dock två huvudproblem med denna kodkod:

  1. Parametrarna first_name och last_name är nu vilseledande, eftersom du kan ange ett fullt namn för first_name . Om det finns fler fall och / eller fler parametrar som har den här typen av flexibilitet kan förgreningen if / elif / annars bli irriterande snabbt.

  2. Inte lika viktigt, men ändå värt att påpeka: vad last_name om last_name är None , men first_name delas inte upp i två eller fler saker via mellanslag? Vi har ännu ett lager av inmatningsvalidering och / eller undantagshantering ...

Ange klassmetoder. I stället för att ha en enda initialisator skapar vi en separat initialisator, kallad from_full_name , och dekorerar den med (inbyggd) classmethod .

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 + ".")

Observera att cls istället för self det första argumentet från from_full_name . Klassmetoder tillämpas på den övergripande klassen, inte ett exempel på en viss klass (vilket self vanligtvis anger). Så, om cls är vår Person , är det returnerade värdet från from_full_name klass Person(first_name, last_name, age) , som använder Person __init__ att skapa en instans av klassen Person . I synnerhet, om vi skulle göra en underklass Employee till Person , skulle from_full_name fungera i Employee .

För att visa att detta fungerar som förväntat, låt oss skapa instanser av Person på mer än ett sätt utan förgreningen i __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.

Andra referenser:

Klassens sammansättning

Klassens sammansättning tillåter uttryckliga förhållanden mellan objekt. I det här exemplet bor människor i städer som tillhör länder. Sammansättning ger människor tillgång till antalet alla som bor i sitt land:

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

I detta fall betyder "apa-lappning" att lägga till en ny variabel eller metod i en klass efter att den har definierats. Säg till exempel att vi definierade klass A som

class A(object):
    def __init__(self, num):
        self.num = num

    def __add__(self, other):
        return A(self.num + other.num)

Men nu vill vi lägga till en annan funktion senare i koden. Anta att denna funktion är som följer.

def get_num(self):
    return self.num

Men hur lägger vi till detta som en metod i A ? Det är enkelt att vi bara placerar den funktionen i A med ett uppdrag.

A.get_num = get_num

Varför fungerar det här? Eftersom funktioner är objekt precis som alla andra objekt, och metoder är funktioner som tillhör klassen.

Funktionen get_num ska vara tillgänglig för alla befintliga (redan skapade) liksom för de nya instanserna av A

Dessa tillägg är automatiskt tillgängliga i alla instanser av den klassen (eller dess underklasser). Till exempel:

foo = A(42)

A.get_num = get_num

bar = A(6);

foo.get_num() # 42

bar.get_num() # 6

Observera att till skillnad från vissa andra språk fungerar den här tekniken inte för vissa inbyggda typer, och den anses inte vara bra stil.

Lista alla klassmedlemmar

Funktionen dir() kan användas för att få en lista över medlemmarna i en klass:

dir(Class)

Till exempel:

>>> 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']

Det är vanligt att bara leta efter "icke-magiska" medlemmar. Detta kan göras med en enkel förståelse som visar medlemmar med namn som inte börjar med __ :

>>> [m for m in dir(list) if not m.startswith('__')]
['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

varningar:

Klasser kan definiera en __dir__() -metod. Om den metoden existerar kommer samtal dir() att ringa __dir__() , annars försöker Python skapa en lista över medlemmar i klassen. Detta betyder att dir-funktionen kan ha oväntade resultat. Två citat av betydelse från den officiella pytondokumentationen :

Om objektet inte tillhandahåller dir (), försöker funktionen sitt bästa för att samla information från objektets diktattribut , om det definieras, och från dess typobjekt. Den resulterande listan är inte nödvändigtvis fullständig och kan vara felaktig när objektet har en anpassad getattr ().

Obs: Eftersom dir () huvudsakligen tillhandahålls som en bekvämlighet för användning vid en interaktiv prompt, försöker den tillhandahålla en intressant uppsättning namn mer än den försöker tillhandahålla en strikt eller konsekvent definierad uppsättning namn, och dess detaljerade beteende kan ändras över utsläpp. Till exempel är metaklassattribut inte i resultatlistan när argumentet är en klass.

Introduktion till klasser

En klass fungerar som en mall som definierar de grundläggande egenskaperna hos ett visst objekt. Här är ett exempel:

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))

Det finns några saker att notera när man tittar på exemplet ovan.

  1. Klassen består av attribut (data) och metoder (funktioner).
  2. Attribut och metoder definieras helt enkelt som normala variabler och funktioner.
  3. Som anges i motsvarande dokstring __init__() metoden __init__() initialiseraren . Det motsvarar konstruktören i andra objektorienterade språk och är metoden som först körs när du skapar ett nytt objekt eller en ny instans av klassen.
  4. Attribut som gäller hela klassen definieras först och kallas klassattribut .
  5. Attribut som gäller för en specifik instans av en klass (ett objekt) kallas instansattribut . De definieras generellt inuti __init__() ; detta är inte nödvändigt, men det rekommenderas (eftersom attribut definierade utanför __init__() riskerar att komma åt innan de definieras).
  6. Varje metod som ingår i klassdefinitionen skickar objektet i fråga som sin första parameter. Ordet self används för denna parameter (användningen av self är faktiskt enligt konvention, eftersom ordet self har ingen inneboende betydelse i Python, men detta är en av Pythons mest respekterade konventioner, och du bör alltid följa det).
  7. De som används för objektorienterad programmering på andra språk kan bli förvånade av några saker. En är att Python har ingen riktig begreppet private delar, så allt som standard imiterar beteendet hos C ++ / Java public sökord. För mer information, se exemplet "Private Class Members" på denna sida.
  8. Några av klassens metoder har följande form: __functionname__(self, other_stuff) . Alla sådana metoder kallas "magiska metoder" och är en viktig del av klasserna i Python. Exempelvis implementeras överbelastning av operatörer i Python med magiska metoder. Mer information finns i relevant dokumentation .

Låt oss nu göra några fall av vår Person !

>>> # Instances
>>> kelly = Person("Kelly")
>>> joseph = Person("Joseph")
>>> john_doe = Person("John Doe")

Vi har för närvarande tre Person objekt, kelly , joseph och john_doe .

Vi kan komma åt klassens attribut från varje instans med punktoperatören . Notera igen skillnaden mellan klass- och instansattribut:

>>> # Attributes
>>> kelly.species
'Homo Sapiens'
>>> john_doe.species
'Homo Sapiens'
>>> joseph.species
'Homo Sapiens'
>>> kelly.name
'Kelly'
>>> joseph.name
'Joseph'

Vi kan utföra klassens metoder med samma punktoperatör . :

>>> # Methods
>>> john_doe.__str__()
'John Doe'
>>>  print(john_doe)
'John Doe'
>>>  john_doe.rename("John")
'Now my name is John'

Egenskaper

Python-klasser stöder egenskaper som ser ut som vanliga objektvariabler, men med möjlighet att bifoga anpassat beteende och dokumentation.

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

Objektet av klass MyClass verkar ha en egenskap .string , men dess beteende är nu tätt kontrollerad:

mc = MyClass()
mc.string = "String!"
print(mc.string)
del mc.string

Förutom den användbara syntaxen som ovan tillåter egenskapssyntaxen validering eller andra förstärkningar att läggas till dessa attribut. Detta kan vara särskilt användbart med offentliga API: er - där en hjälpnivå bör ges till användaren.

En annan vanlig användning av egenskaper är att göra det möjligt för klassen att presentera "virtuella attribut" - attribut som faktiskt inte lagras men beräknas endast på begäran.

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 klass

En singleton är ett mönster som begränsar inställningen av en klass till en instans / ett objekt. För mer info om python singleton designmönster, se här .

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

En annan metod är att dekorera din klass. Följ exemplet från det här svaret och skapa en Singleton-klass:

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)

För att använda kan du använda Instance

@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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow