
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.