Spring Semester 2007


Lecture Eight: Private instance members. Accessors, mutators, properties.
Let's implement additional behavior for fractions. Here's how a fraction can add (with another fraction).
class fraction(object):

    def gcd(a, b):
        m = max(abs(a), abs(b))
        n = min(abs(a), abs(b))
        if n == 0:
            return 0
        else:
            d = n
            while not (m % d == 0 and n % d == 0):
                d -= 1
            return d

    gcd = staticmethod(gcd)

    def __init__(self, num, den):
        self.num = num / fraction.gcd(num, den)
        self.den = den / fraction.gcd(num, den)

    def __str__(self):
        return str(self.num) + "/" + str(self.den)

    def add(self, other):
        return fraction(self.den * other.num + self.num * other.den, \
                        self.den * other.den)

Here's an example, too. One third plus two thirds should be equal to one.
>>> a = fraction(2,3)
>>> b = fraction(1,3)
>>> a.add(b)
<__main__.fraction object at 0x00D55150>
>>> print a.add(b)
1/1
>>> 

The code changes now to accomodate private instance variables. Note that we end up making more changes than we would first expect.
class fraction(object):

    def gcd(a, b):
        m = max(abs(a), abs(b))
        n = min(abs(a), abs(b))
        if n == 0:
            return 0
        else:
            d = n
            while not (m % d == 0 and n % d == 0):
                d -= 1
            return d

    gcd = staticmethod(gcd)

    def __init__(self, num, den):
        self.__num = num / fraction.gcd(num, den)
        self.__den = den / fraction.gcd(num, den)

    def __str__(self):
        return str(self.__num) + "/" + str(self.__den)

    def add(self, other):
        return fraction(self.__den * other.getNum() + self.__num * other.getDen(), \
                        self.__den * other.getDen())

    def getNum(self):
        return self.__num

    def getDen(self):
        return self.__den

Why is that necessary? In this case it isn't, but how acess to the other fraction's numerator and denominator should be read only, no?
>>>
>>> a = fraction(2,3)
>>> a.__num

Traceback (most recent call last):
  File "", line 1, in 
    a.__num
AttributeError: 'fraction' object has no attribute '__num'
>>> b = fraction(1,3)
>>> a.add(b)
<__main__.fraction object at 0x00D55EF0>
>>> print a.add(b)
1/1
>>> print fraction(2,6).add(fraction(5,3))
2/1
>>> print fraction(2,6)
1/3
>>> print fraction(5,3)
5/3
>>> 

I see: some sort of hygienic perspective. Profilaxy, if you want.

So access to private variables is completely closed outside the class? No, not really; Python conventions only mean to discourage this type of access.
>>> a._fraction__num
2
>>> a = fraction(2,3)
>>> a._fraction__num
2
>>> a._fraction__den
3
>>> 

Same goes for methods. So we're now ready for a review and the new program.

Here's the critter program from Dawson's book. What's in this program that we didn't discuss?

Properties. How can you quickly explain those?

It's only syntax, nothing deep. Show me.

Here's come code that uses (defines) properties. How does this work?
class critter(object):
    def __init__(self):
        self.__a = "first secret"
        self.__b = "second secret"
    def get_a(self):
        return self.__a
    def get_b(self):
        return self.__b
    def set_b(self, value):
        self.__b = value
    a = property(get_a)
    b = property(get_b, set_b)

Here's how it works. I see; just as you said, syntactic sugar, nothing more.
>>> x = critter()
>>> x.__a

Traceback (most recent call last):
  File "<pyshell#127>", line 1, in 
    x.__a
AttributeError: 'critter' object has no attribute '__a'
>>> x._critter__a
'first secret'
>>> x._critter__a = "changed once"
>>> x._critter__a
'changed once'
>>> x.a
'changed once'
>>> x.a = 'changed again'

Traceback (most recent call last):
  File "<pyshell#132>", line 1, in 
    x.a = 'changed again'
AttributeError: can't set attribute
>>> x.b
'second secret'
>>> x.b = 'changed once'
>>> x.b
'changed once'
>>> x.__b

Traceback (most recent call last):
  File "<pyshell#136>", line 1, in 
    x.__b
AttributeError: 'critter' object has no attribute '__b'
>>> x._critter__b
'changed once'
>>> x._critter__b = 'changed again'
>>> x.b
'changed again'
>>> 

Here's one more example, illustrating static variables. Good. I think that's enough for today.
class critter(object):
    total = 0
    def __init__(self):
        critter.total += 1

def test():
    c = []
    for i in range(10):
        c.append(critter())
        print critter.total

Same here. See you in lab.

Files for the next chapter (lab this week): We'll cover them in this order.
alien_blaster.py

playing_cards.py
playing_cards2.py
playing_cards3.py

games.py
simple_game.py

cards.py

blackjack.py

Updated by Adrian German for A202/A598