Showing posts with label getters. Show all posts
Showing posts with label getters. Show all posts

Sunday, 10 April 2016

Classes 4: Encapsulation

Encapsulation can be a bit of a sticky subject. Many people have strong views on the subject and others just don't care. But what is it? Basically it is two distinct but related concepts. We can make class members like attributes private or protected. This stops anything outwith the class from accessing those members directly. We can also bundle data with its associated method. Which we did in Classes 3: Properties.

When we decide to make a class member like an attribute private. We have basically made the decision that it must be protected from the outside world. And this I think is the biggest problem with the whole concept when we have direct access to the source code. Nothing is really protected because with direct access to the source code. We can change whatever we want.

For this reason it is better to think in terms of a compiled program or library that might be shared with many people outside your organisation. Normally an outsider would access your class using the documented API. An analogy might be a customer at a bank.

A bank is a class of business. An attribute of a bank is that it holds customer accounts. Each account has a value. The amount of cash associated with the account. Account attributes of bank classes are however private. The method we use to access the account's value is the clerk. The clerk is the getter. If we give the clerk the correct information, which is different for every customer account and thus variable, the clerk will return the value of the account. We get our money.

We can put this analogy into action with the following example. After we create accounts for Bob and Sue with the bank class, the only way to access those accounts is through clerk method. All of the attributes are private.

#!/usr/bin/env python3

__project__= "Python Classes: The Bank Example"
__author__ = "Kevin Lynch"
__version__ = "$Revision: 1 $"
__date__ = "$Date: 2016/04/11 15:23:00 $"
__copyright__ = "Copyright (c) 2016 Kevin Lynch"
__license__ = "GPLv3"

# Create the bank class.
class bank:
        # Initialise bank with custom attributes.
        def __init__(self,account,pin,cash):
                # This is a bank. So all attributes are private.
                self.__account = account
                self.__pin = pin
                self.__cash = cash
                
        # Create the getter method.
        def clerk(self,account,pin):
                if account == self.__account:
                        if pin == self.__pin:
                                return str(self.__cash) # We get our money.
                        else:
                                return "CALLED THE COPS!!!" # Wrong pin code.
                else:
                        return "no such account" # Wrong account number.

# Global variables
bobsAccount = bank(12345678,9990,500)
suesAccount = bank(87654321,2204,5)

# We can ask the clerk how much money Bob and Sue have.
print("Bob has " + bobsAccount.clerk(12345678,9990) + " in the bank.\n\n")
print("Sue has " + suesAccount.clerk(87654321,2204) + " in the bank.\n\n")

# Sue forgot her pin code and used Bob's instead.
print("Sue has " + suesAccount.clerk(87654321,9990) + " in the bank.\n\n")

# Bob can't remember his account number.
print("Bob has " + bobsAccount.clerk(12945070,9990) + " in the bank.\n\n")

# Uncomment the following code to prove the attributes are private.
# print("Bob has " + str(bobsAccount.__cash) + " in the bank.\n\n")

The Big Lie
Uncommenting the last line of code in the example seems to prove the attributes of bank are indeed private. But this is not actually the case. Everything in Python is public. We just need to know where to look.

Every Python object has a number of built-in attributes and methods that are added automatically. The method __init__() is one such example. This method runs automatically every time an instance of a class is created. When we define __init__() in our code, we are overriding the default. And this allows us to add a degree of flexibility to the attributes, properties and methods we have add to the class.

All of the attributes within a class are contained within a dictionary object called __dict__. As we've seen in the Electronic Point Of Sale project. Dictionary objects can be accessed in much the same way as lists. And this means a determined developer would have no real problem in finding and interrogating private attributes within a class.

To get around this problem, supposedly, we can override the built in getters and setters that all Python classes have. These are called __getattr__ and __setattr__ respectively. However I've never seen this working and don't seem to be able to make it work. So, so far as I'm concerned for the time being. It doesn't work (at least not without crashing your program) and everything is public. Even when it's private.

Reading The Dictionary

#!/usr/bin/env python3

__project__= "Python Classes: The Bank Example"
__author__ = "Kevin Lynch"
__version__ = "$Revision: 2 $"
__date__ = "$Date: 2016/04/11 20:27:00 $"
__copyright__ = "Copyright (c) 2016 Kevin Lynch"
__license__ = "GPLv3"

# Create the bank class.
class bank:
        # Initialise bank with custom attributes.
        def __init__(self,account,pin,cash):
                # This is a bank. So all attributes are private.
                self.__account = account
                self.__pin = pin
                self.__cash = cash
             
# Global variable. Bob creates his account with the bank.
bobsAccount = bank(12345678,9990,500)

# Lets look at the dictionary.
print("\n\nReading the whole dictionary")
print(bobsAccount.__dict__)

# Now lets search for the cash.
d = bobsAccount.__dict__ # This will make the dictionary easier to work with.
for key in d.keys():
        # Search for the keyword cash and do something.
        if "cash" in key:
                print("\n\nCash found in " + key)
                print(key + " = " + str(d[key]))
             
                # We can even edit the value of the attribute.
                print("\n\nLets give Bob more cash!!!")
                d[key] = 1000
                print(key + " = " + str(d[key]))              
                break
        else:
                print("\nCash not found in " + key)
             
# Now lets prove we actually altered Bob's account.
# Since we know the name of the attribute. We can access it directly.
print("\n\nCash in Bob's account = " + str(bobsAccount._bank__cash))

Why Bother Then?
There are a few reasons. The straw man is that it's what developers from the likes of Java or C++ backgrounds have been taught to do. So it's considered "good practice". However it would also be good practice if people just read the API reference, used the class properly and saved other programmers from having to write countless lines of extra code.

A much better reason is that it ensures there is one and only one obvious means of accessing and altering class members. Which in turn helps to bolster the integrity of those class members and the stability of the program overall. By using encapsulation with getters and setters, we can validate data being passed around the program. Everything in theory happens in an orderly and predictable way.

Clearly this isn't a massive problem with very small and simple examples. But when a program runs into hundreds, thousands or even millions of lines of code, passes through the hands of dozens of programmers. It becomes a problem.

Saturday, 9 April 2016

Classes 3: Properties

Properties are the Pythonic way of implementing class members with associated getters and setters. In other object oriented languages like Java this is done from the beginning. In Python, the convention is to use getters and setters only when they are actually needed. For example when exception handling is required or when it becomes desirable to make a class member private. Otherwise the convention is to use the simplest implementation.

We can see the use of properties, getters and setters in action by modifying our bicycle example as follows.
#!/usr/bin/env python3

__project__= "Python Classes: The Bicycle Example"
__author__ = "Kevin Lynch"
__version__ = "$Revision: 3a $"
__date__ = "$Date: 2016/04/10 01:48:00 $"
__copyright__ = "Copyright (c) 2016 Kevin Lynch"
__license__ = "GPLv3"

class bicycle: # Defines the bicycle class.
        # Initialise bicycle with custom attributes.
        def __init__(self,frame,wheels):
                self.frameSet = frame
                self.wheelSet = wheels
                
        @property # Property decorator.
        def frameSet(self):
                return self.__frameSet

        @frameSet.setter # Setter method for the property frameSet.
        def frameSet(self,frame):
                if frame == "carbon":
                        self.__frameSet = frame
                elif frame == "steel":
                        self.__frameSet = frame
                else:
                        self.__frameSet = "wood"

        @property # Property decorator.
        def wheelSet(self):
                return self.__wheelSet

        @wheelSet.setter # Setter method for the property wheelSet.
        def wheelSet(self,wheels):
                if wheels == "carbon":
                        self.__wheelSet = wheels
                elif wheels == "alloy":
                        self.__wheelSet = wheels
                else:
                        self.__wheelSet = "wood"

# Global variables. Both reference the bicycle class.
bobsBike = bicycle("carbon","carbon")
suesBike = bicycle("steel","alloy")

print("Bob's Bike:\nFrame Set: " + bobsBike.frameSet + "\nWheel Set: " + bobsBike.wheelSet)
print("\n\nSue's Bike:\nFrame Set: " + suesBike.frameSet + "\nWheel Set: " + suesBike.wheelSet)

# We can still change things as before.
suesBike.frameSet = "plastic"

print("\n\nSue's Bike:\nFrame Set: " + suesBike.frameSet + "\nWheel Set: " + suesBike.wheelSet)

The only thing we have had to change in our bicycle example is the implementation of the bicycle class. In particular we have added our getters and setters. The getter method is decorated with the @property decorator and the setter method is decorated with the @<property name>.setter decorator. The rest of the program is unchanged.

This means that when we implement an attribute within a class in the simplest way possible. We can then change that implementation later without breaking the whole program. We use the same syntax to access properties as we do when accessing attributes or methods.