Heim  >  Artikel  >  Backend-Entwicklung  >  Verwendung von Python-Attributen und -Deskriptoren

Verwendung von Python-Attributen und -Deskriptoren

高洛峰
高洛峰Original
2017-02-25 10:32:131016Durchsuche

Über den @property decorator

In Python verwenden wir den @property decorator, um Funktionsaufrufe als Zugriff auf Eigenschaften zu tarnen.

Warum machst du das? Denn @property ermöglicht es uns, benutzerdefinierten Code mit Variablenzugriff/-einstellung zu verknüpfen und gleichzeitig eine einfache Schnittstelle für den Zugriff auf Eigenschaften Ihrer Klasse beizubehalten.

Nehmen wir zum Beispiel an, wir haben eine Klasse, die einen Film darstellen muss:

class Movie(object):
 def __init__(self, title, description, score, ticket):
 self.title = title
 self.description = description
 self.score = scroe
 self.ticket = ticket

Sie verwenden dies an anderer Stelle im Projektkategorie, aber dann wird einem klar: Was ist, wenn man dem Film versehentlich eine negative Bewertung gibt? Sie halten dies für ein falsches Verhalten und hoffen, dass die Movie-Klasse diesen Fehler verhindern kann. Ihr erster Gedanke ist, die Movie-Klasse so zu ändern, dass sie wie folgt aussieht:

class Movie(object):
 def __init__(self, title, description, score, ticket):
 self.title = title
 self.description = description
     self.ticket = ticket
 if score < 0:
  raise ValueError("Negative value not allowed:{}".format(score))
 self.score = scroe

Aber das wird nicht funktionieren. Weil andere Teile des Codes direkt über Movie.score zugewiesen werden. Diese neu geänderte Klasse erfasst nur fehlerhafte Daten in der __init__-Methode, kann jedoch nichts gegen vorhandene Klasseninstanzen unternehmen. Wenn jemand versucht, m.scrore= -100 auszuführen, können Sie nichts dagegen tun. Was zu tun?

Pythons Eigenschaft löst dieses Problem.

Wir können dies tun

class Movie(object):
 def __init__(self, title, description, score):
 self.title = title
 self.description = description
 self.score = score
     self.ticket = ticket
 
 @property
 def score(self):
 return self.__score
 
 
 @score.setter
 def score(self, score):
 if score < 0:
  raise ValueError("Negative value not allowed:{}".format(score))
 self.__score = score
 
 @score.deleter
 def score(self):
 raise AttributeError("Can not delete score")

Auf diese Weise wird durch Ändern der Punktzahl an einer beliebigen Stelle erkannt, ob sie kleiner als 0 ist.

Nachteile von Immobilien

Der größte Nachteil von Immobilien besteht darin, dass sie nicht wiederverwendet werden können. Angenommen, Sie möchten dem Ticketfeld auch einen nicht negativen Scheck hinzufügen.

Das Folgende ist die geänderte neue Klasse:

class Movie(object):
 def __init__(self, title, description, score, ticket):
 self.title = title
 self.description = description
 self.score = score
 self.ticket = ticket
 
 @property
 def score(self):
 return self.__score
 
 
 @score.setter
 def score(self, score):
 if score < 0:
  raise ValueError("Negative value not allowed:{}".format(score))
 self.__score = score
 
 @score.deleter
 def score(self):
 raise AttributeError("Can not delete score")
 
 
 @property
 def ticket(self):
 return self.__ticket
 
 @ticket.setter
 def ticket(self, ticket):
 if ticket < 0:
  raise ValueError("Negative value not allowed:{}".format(ticket))
 self.__ticket = ticket
 
 
 @ticket.deleter
 def ticket(self):
 raise AttributeError("Can not delete ticket")

Sie können sehen, dass der Code stark zugenommen hat, aber auch die Logik wiederholt wurde erscheint oft. Obwohl Eigenschaften dafür sorgen können, dass die Schnittstelle einer Klasse von außen ordentlich und schön aussieht, kann sie von innen nicht so ordentlich und schön aussehen.

Deskriptoren erscheinen

Was sind Deskriptoren?

Im Allgemeinen ist ein Deskriptor eine Objekteigenschaft mit Bindungsverhalten, und der Zugriff auf seine Eigenschaften wird durch Deskriptorprotokollmethoden überschrieben. Diese Methoden sind __get__(), __set__() und __delete__(). Solange ein Objekt mindestens eine dieser drei Methoden enthält, wird es als Deskriptor bezeichnet. Was macht der

-Deskriptor?

Das Standardverhalten für den Attributzugriff besteht darin, das Attribut aus dem Wörterbuch eines Objekts abzurufen, festzulegen oder zu löschen. Beispielsweise hat a.x eine Suchkette, die mit a.__dict__['x'] beginnt, dann geben Sie(a ) .__dict__['x'] und weiter durch die Basisklassen von Typ(a) mit Ausnahme von Metaklassen. Wenn der gesuchte Wert ein Objekt ist, das eine der Deskriptormethoden definiert, überschreibt Python möglicherweise das Standardverhalten und ruft den Deskriptor auf Wo dies in der Präzedenzkette auftritt, hängt davon ab, welche Deskriptormethoden definiert wurden. – Auszug aus der offiziellen Dokumentation

Einfach ausgedrückt: Deskriptoren ändern die grundlegende Art und Weise, ein Attribut abzurufen, festzulegen und zu löschen.

Sehen wir uns zunächst an, wie Deskriptoren verwendet werden können, um das oben genannte Problem der wiederholten Eigenschaftslogik zu lösen.

class Integer(object):
 def __init__(self, name):
 self.name = name
 
 def __get__(self, instance, owner):
 return instance.__dict__[self.name]
 
 def __set__(self, instance, value):
 if value < 0:
  raise ValueError("Negative value not allowed")
 instance.__dict__[self.name] = value
 
class Movie(object):
 score = Integer(&#39;score&#39;)
 ticket = Integer(&#39;ticket&#39;)

Weil der Deskriptor eine hohe Priorität hat und das standardmäßige Get- und Set-Verhalten ändert, also wenn wir auf Movie() zugreifen oder es festlegen begrenzt durch den Deskriptor Integer.

Allerdings können wir Instanzen nicht immer auf die folgende Weise erstellen.

a = Movie()
a.score = 1
a.ticket = 2
a.title = ‘test&#39;
a.descript = ‘…&#39;

Das ist zu stumpf, daher fehlt uns noch ein Konstruktor.

class Integer(object):
 def __init__(self, name):
 self.name = name
 
 def __get__(self, instance, owner):
 if instance is None:
  return self
 return instance.__dict__[self.name]
 
 def __set__(self, instance, value):
 if value < 0:
  raise ValueError(&#39;Negative value not allowed&#39;)
 instance.__dict__[self.name] = value
 
 
class Movie(object):
 score = Integer(&#39;score&#39;)
 ticket = Integer(&#39;ticket&#39;)
 
 def __init__(self, title, description, score, ticket):
 self.title = title
 self.description = description
 self.score = score
 self.ticket = ticket

Auf diese Weise werden __get__ und __set__ von Integer eingegeben, wenn Punkte und Tickets abgerufen, festgelegt und gelöscht werden, wodurch wiederholte Logik reduziert wird.

Da das Problem nun gelöst ist, fragen Sie sich vielleicht, wie dieser Deskriptor tatsächlich funktioniert. Konkret wird in der Funktion __init__ auf den eigenen self.score und self.ticket zugegriffen. Wie hängen sie mit den Klassenattributen „score“ und „ticket“ zusammen?

Wie Deskriptoren funktionieren

Siehe die offizielle Beschreibung

Wenn ein Objekt sowohl __get__() als auch __set__() definiert, wird es als Datendeskriptor betrachtet, der nur __get__ definiert. () werden Nicht-Daten-Deskriptoren genannt (sie werden normalerweise für Methoden verwendet, aber auch andere Verwendungen sind möglich).

Daten- und Nicht-Daten-Deskriptoren unterscheiden sich darin, wie Überschreibungen in Bezug auf Einträge im Wörterbuch If einer Instanz berechnet werden Das Wörterbuch einer Instanz hat einen Eintrag mit demselben Namen wie ein Datendeskriptor, der Datendeskriptor hat Vorrang. Wenn das Wörterbuch einer Instanz einen Eintrag mit demselben Namen wie ein Nicht-Datendeskriptor hat, hat der Wörterbucheintrag Vorrang.

Die wichtigen Punkte, die Sie beachten sollten, sind:

Deskriptoren werden von der Methode __getattribute__() aufgerufen
Das Überschreiben von __getattribute__() verhindert automatische Deskriptoraufrufe
object.__getattribute__() und type.__getattribute__() machen anders Aufrufe von __get__().
Datendeskriptoren überschreiben immer Instanzwörterbücher.
Nicht-Datendeskriptoren können von Instanzwörterbüchern überschrieben werden. Das sieht so aus:

def __getattribute__(self, key):
 "Emulate type_getattro() in Objects/typeobject.c"
 v = object.__getattribute__(self, key)
 if hasattr(v, &#39;__get__&#39;):
 return v.__get__(None, self)
 return v
Das Folgende ist ein Auszug aus einem ausländischen Blog.

Gegeben eine Klasse „C“ und eine Instanz „c“, wobei „c = C(…)“ ist, bedeutet der Aufruf von „c.name“, ein Attribut „name“ für die Instanz „c“ wie folgt nachzuschlagen :

Get the Class from Instance
Call the Class's special method getattribute__. All objects have a default __getattribute
Inside getattribute

Get the Class's mro as ClassParents
For each ClassParent in ClassParents
If the Attribute is in the ClassParent's dict
If is a data descriptor
Return the result from calling the data descriptor's special method __get__()
Break the for each (do not continue searching the same Attribute any further)
If the Attribute is in Instance's dict
Return the value as it is (even if the value is a data descriptor)
For each ClassParent in ClassParents
If the Attribute is in the ClassParent's dict
If is a non-data descriptor
Return the result from calling the non-data descriptor's special method __get__()
If it is NOT a descriptor
Return the value
If Class has the special method getattr
Return the result from calling the Class's special method__getattr__.

我对上面的理解是,访问一个实例的属性的时候是先遍历它和它的父类,寻找它们的__dict__里是否有同名的data descriptor如果有,就用这个data descriptor代理该属性,如果没有再寻找该实例自身的__dict__ ,如果有就返回。任然没有再查找它和它父类里的non-data descriptor,最后查找是否有__getattr__

描述符的应用场景

python的property、classmethod修饰器本身也是一个描述符,甚至普通的函数也是描述符(non-data discriptor)

django model和SQLAlchemy里也有描述符的应用

class User(db.Model):
 id = db.Column(db.Integer, primary_key=True)
 username = db.Column(db.String(80), unique=True)
 email = db.Column(db.String(120), unique=True)
 
 def __init__(self, username, email):
 self.username = username
 self.email = email
 
 def __repr__(self):
 return &#39;<User %r>' % self.username

总结

只有当确实需要在访问属性的时候完成一些额外的处理任务时,才应该使用property。不然代码反而会变得更加啰嗦,而且这样会让程序变慢很多。以上就是本文的全部内容,由于个人能力有限,文中如有笔误、逻辑错误甚至概念性错误,还请提出并指正。

更多Python属性和描述符的使用相关文章请关注PHP中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn