Mastering Objectoriented Python
上QQ阅读APP看书,第一时间看更新

Creating properties

A property is a method function that appears (syntactically) to be a simple attribute. We can get, set, and delete property values similarly to how we can get, set, and delete attribute values. There's an important distinction here. A property is actually a method function and can process, rather than simply preserve, a reference to another object.

Besides the level of sophistication, one other difference between properties and attributes is that we can't attach new properties to an existing object easily; however, we can add attributes to an object easily, by default. A property is not identical to simple attributes in this one respect.

There are two ways to create properties. We can use the @property decorator or we can use the property() function. The differences are purely syntactic. We'll focus on the decorator.

We'll take a look at two basic design patterns for properties:

  • Eager calculation: In this design pattern, when we set a value via a property, other attributes are also computed
  • Lazy calculation: In this design pattern, calculations are deferred until requested via a property

In order to compare the preceding two approaches to properties, we'll split some common features of the Hand object into an abstract superclass, as follows:

class Hand:
    def __str__( self ):
        return ", ".join( map(str, self.card) )
    def __repr__( self ):
        return "{__class__.__name__}({dealer_card!r}, {_cards_str})".format(
        __class__=self.__class__,
        _cards_str=", ".join( map(repr, self.card) ),
        **self.__dict__ )

In the preceding code, we defined just some string representation methods and nothing else.

The following is a subclass of Hand, where total is a lazy property that is computed only when needed:

class Hand_Lazy(Hand):
    def __init__( self, dealer_card, *cards ):
        self.dealer_card= dealer_card
        self._cards= list(cards)
    @property
    def total( self ):
        delta_soft = max(c.soft-c.hard for c in self._cards)
        hard_total = sum(c.hard for c in self._cards)
        if hard_total+delta_soft <= 21: return hard_total+delta_soft
        return hard_total
    @property
    def card( self ):
        return self._cards
    @card.setter
    def card( self, aCard ):
        self._cards.append( aCard )
    @card.deleter
    def card( self ):
        self._cards.pop(-1)

The Hand_Lazy class initializes a Hand object with a list of the Cards object. The total property is a method that computes the total only when requested. Additionally, we defined some other properties to update the collection of cards in the hand. The card property can get, set, or delete cards in the hand. We'll take a look at these properties in setter and deleter properties section.

We can create a Hand object, total appears to be a simple attribute:

>>> d= Deck()
>>> h= Hand_Lazy( d.pop(), d.pop(), d.pop() )
>>> h.total
19
>>> h.card= d.pop()
>>> h.total
29

The total is computed lazily by rescanning the cards in the hand each time the total is requested. This can be an expensive overhead.