Πώς λειτουργεί ο διακοσμητής @property;

Θα ήθελα να καταλάβω πώς λειτουργεί η ενσωματωμένη συνάρτηση property. Αυτό που με μπερδεύει είναι ότι η property μπορεί επίσης να χρησιμοποιηθεί ως διακοσμητής, αλλά δέχεται ορίσματα μόνο όταν χρησιμοποιείται ως ενσωματωμένη συνάρτηση και όχι όταν χρησιμοποιείται ως διακοσμητής.

Αυτό το παράδειγμα προέρχεται από την τεκμηρίωση:

class C(object):
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

Τα ορίσματα του property'είναι τα getx, setx, delx και μια συμβολοσειρά doc.

Στον παρακάτω κώδικα το property χρησιμοποιείται ως διακοσμητής. Το αντικείμενό του είναι η συνάρτηση x, αλλά στον παραπάνω κώδικα δεν υπάρχει θέση για συνάρτηση αντικειμένου στα ορίσματα.

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Και, πώς δημιουργούνται οι διακοσμητές x.setter και x.deleter; Έχω μπερδευτεί.

Λύση

Η συνάρτηση property() επιστρέφει ένα ειδικό αντικείμενο περιγραφής:

>>> property()

Είναι αυτό το αντικείμενο που έχει επιπλέον μεθόδους:

>>> property().getter

>>> property().setter

>>> property().deleter

Αυτές λειτουργούν ως διακοσμητές επίσης. Επιστρέφουν ένα νέο αντικείμενο ιδιοκτησίας:

>>> property().getter(None)

που είναι αντίγραφο του παλιού αντικειμένου, αλλά με μία από τις συναρτήσεις να έχει αντικατασταθεί.

Θυμηθείτε, ότι η σύνταξη @decorator είναι απλά συντακτική ζάχαρη- η σύνταξη:

@property
def foo(self): return self._foo

στην πραγματικότητα σημαίνει το ίδιο πράγμα με το

def foo(self): return self._foo
foo = property(foo)

οπότε η συνάρτηση foo αντικαθίσταται από την property(foo), η οποία όπως είδαμε παραπάνω είναι ένα ειδικό αντικείμενο. Στη συνέχεια, όταν χρησιμοποιείτε την @foo.setter(), αυτό που κάνετε είναι να καλείτε τη μέθοδο property().setter που σας έδειξα παραπάνω, η οποία επιστρέφει ένα νέο αντίγραφο της ιδιότητας, αλλά αυτή τη φορά με τη συνάρτηση setter να έχει αντικατασταθεί με τη διακοσμημένη μέθοδο.

Η ακόλουθη ακολουθία δημιουργεί επίσης μια πλήρη ιδιότητα, χρησιμοποιώντας αυτές τις μεθόδους διακοσμητή.

Πρώτα δημιουργούμε μερικές συναρτήσεις και ένα αντικείμενο property με μόνο έναν getter:

>>> def getter(self): print('Get!')
... 
>>> def setter(self, value): print('Set to {!r}!'.format(value))
... 
>>> def deleter(self): print('Delete!')
... 
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True

Στη συνέχεια χρησιμοποιούμε τη μέθοδο .setter() για να προσθέσουμε έναν setter:

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

Τέλος, προσθέτουμε έναν διαγραφέα με τη μέθοδο .deleter():

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

Τέλος, το αντικείμενο property ενεργεί ως αντικείμενο περιγραφής, οπότε διαθέτει τις μεθόδους .__get__(), .__set__() και .__delete__() για να συνδεθεί με τη λήψη, τον ορισμό και τη διαγραφή χαρακτηριστικών της περίπτωσης:

>>> class Foo: pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

Το Descriptor Howto περιλαμβάνει μια καθαρή Python υλοποίηση δείγματος του τύπου property():

class Property: "Emulate PyProperty_Type() in Objects/descrobject.c&quot, &gt, def init(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel εάν το doc είναι None και το fget δεν είναι None: doc = fget.doc self.doc = doc &gt, def get(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(obj) &gt, def set(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(obj, value) &gt, def delete(self, obj): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(obj) &gt, def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.doc) &gt, def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.doc) &gt, def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.doc)

Σχόλια (23)

Η τεκμηρίωση λέει ότι είναι απλά μια συντόμευση για τη δημιουργία ιδιοτήτων μόνο για ανάγνωση. Έτσι

@property
def x(self):
    return self._x

είναι ισοδύναμο με

def getx(self):
    return self._x
x = property(getx)
Σχόλια (1)

Το πρώτο μέρος είναι απλό:

@property
def x(self): ...

είναι το ίδιο με

def x(self): ...
x = property(x)
  • το οποίο, με τη σειρά του, είναι η απλοποιημένη σύνταξη για τη δημιουργία μιας property με έναν απλό getter.

Το επόμενο βήμα θα ήταν να επεκτείνουμε αυτή την ιδιότητα με έναν setter και έναν deleter. Και αυτό συμβαίνει με τις κατάλληλες μεθόδους:

@x.setter
def x(self, value): ...

επιστρέφει μια νέα ιδιότητα η οποία κληρονομεί τα πάντα από την παλιά x συν τον δεδομένο setter.

Ο x.deleter λειτουργεί με τον ίδιο τρόπο.

Σχόλια (0)