R.P.
R.P.

Reputation: 19

Writing safe, enforced Python classes

I am trying to implement a class with the best and safest conventions possible. Are there better ways to

a) prevent external edits to properties, and

b) enforce certain constraints, such as valid rank and suit, on these properties?

class Card:

    __ranks = ['Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King']
    __suits = ['Heart', 'Club', 'Diamond', 'Spade']

    def __init__(self, rank, suit):
        assert rank in self.__ranks
        assert suit in self.__suits
        self.__rank = rank
        self.__suit = suit

    def getRank(self):
        return self.__rank

    def getSuit(self):
        return self.__suit

Upvotes: 1

Views: 262

Answers (3)

Jace Browning
Jace Browning

Reputation: 12672

It's more common to control this type of behavior through the property decorator. You can essentially create a read-only attribute by not implementing a setter:

class Foo:

    def __init__(self, bar):
        self._bar = bar

    @property
    def bar(self):
       """Read-only access to bar."""
       return self._bar

I wouldn't bother with the double underscore name mangling. That still won't (and isn't intended to) make attributes inaccessible from the outside.

Upvotes: 1

nigel222
nigel222

Reputation: 8212

You could use a named tuple, so that the object is immutable

>>> from collections import namedtuple
>>> Card = namedtuple('Card','rank,suit')
>>> acard = Card('10','D')
>>> acard.suit
'D'
>>> acard.rank
'10'
>>> acard.rank='H'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> 

For more information see the namedtuple documentation (in the collections module, https://docs.python.org/2/library/collections.html)

Upvotes: 2

outoftime
outoftime

Reputation: 765

Prevent external edits to properties

Any attribute can be changed when you have a class instance. But you can follow convention that attributes started from single and double underscore is private and you should not access them directly unless you know what are you doing.

To provide public interface @property decorator really what you want.

Enforce certain constraints

Quote from docs:

In the current implementation, the built-in variable __debug__ is True under normal circumstances, False when optimization is requested (command line option -O). The current code generator emits no code for an assert statement when optimization is requested at compile time.

Assets is for development only. They can be used for test checking, etc. In case you need to check appropriate values are passed into __init__ method, you can raise ValueError or custom error derived from it.

class Card(object):

    class CardValueError(ValueError):
        pass

    __ranks = ['Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10',
               'Jack', 'Queen', 'King']
    __suits = ['Heart', 'Club', 'Diamond', 'Spade']

    def __init__(self, rank, suit):
        if rank not in self.__ranks or suit not in self.__suits:
            raise Card.CardValueError()
        self.__rank = rank
        self.__suit = suit

    @property
    def rank(self):
        return self.__rank

    @property
    def suit(self):
        return self.__suit

Upvotes: 1

Related Questions