1

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
R.P.
  • 19
  • 2
  • What do you mean by "external edits to properties"; what kind of constraints to do you want to enforce? Your example is unclear of what you'd like to do. In general, though, Python doesn't restrict the programmer, and things work by convention. Thus, a variable or method prefixed with a single or double underscore be regarded as private and not be assigned to. –  Aug 16 '16 at 04:45
  • 2
    One of the principles in Python is that we are all consenting adults. If someone really wants to poke around with the internal workings of a class, they can, and you can't stop them. You can, however, make it clear that you didn't intend for those properties to be edited directly (as you have illustrated). See here for a bit more of an explanation - http://stackoverflow.com/questions/1641219/does-python-have-private-variables-in-classes – Andrew Guy Aug 16 '16 at 04:46
  • 1
    Depending on what you want to achieve, you may want to use single underscores, btw. See [this question & answer](http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python) for an explanation on that. –  Aug 16 '16 at 04:46
  • Why not represent your cards as tuples (suit, rank). Then you don't have to worry about mutability or maintaining a class for cards. – Paul Rooney Aug 16 '16 at 06:03

3 Answers3

2

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)

nigel222
  • 7,582
  • 1
  • 14
  • 22
1

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.

Jace Browning
  • 11,699
  • 10
  • 66
  • 90
1

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
outoftime
  • 715
  • 7
  • 21