Python Language
Klasser
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
:
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.
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.
import inspect
inspect.isfunction(A.f)
# True
inspect.ismethod(A.f)
# False
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:
- Vad är skillnaden mellan @staticmethod och @classmethod i Python?
- Betydelse av @classmethod och @staticmethod för nybörjare?
Ny stil kontra gamla stil klasser
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
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
- Djup först (t.ex.
FooBar
sedanFoo
) om inte - en delad förälder (
object
) blockeras av ett barn (Bar
) och - 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:
bar
tittas upp i klassen,Foo
. Om den är där och det är en dataskriptor , används dataskrivaren. Så här kanproperty
kontrollera åtkomst till data i en instans, och instanser kan inte åsidosätta detta. Om en Data Descriptor inte finns där, då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. Ombar
finns i förekomsten används det. Om inte, vi dåtitta i klass
Foo
förbar
. Om det är en deskriptor används descriptorprotokollet. Så implementeras funktioner (i detta sammanhang obundna metoder),classmethod
ochstaticmethod
. Annars returnerar det helt enkelt objektet där, eller så finns det enAttributeError
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:
Parametrarna
first_name
ochlast_name
är nu vilseledande, eftersom du kan ange ett fullt namn förfirst_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.Inte lika viktigt, men ändå värt att påpeka: vad
last_name
omlast_name
ärNone
, menfirst_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:
https://docs.python.org/2/library/functions.html#classmethod
https://docs.python.org/3.5/library/functions.html#classmethod
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.
- Klassen består av attribut (data) och metoder (funktioner).
- Attribut och metoder definieras helt enkelt som normala variabler och funktioner.
- 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. - Attribut som gäller hela klassen definieras först och kallas klassattribut .
- 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). - 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 avself
är faktiskt enligt konvention, eftersom ordetself
har ingen inneboende betydelse i Python, men detta är en av Pythons mest respekterade konventioner, och du bör alltid följa det). - 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 ++ / Javapublic
sökord. För mer information, se exemplet "Private Class Members" på denna sida. - 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