
# One of the primary roles of a class is to encapsulate data and 
# internal implementation details of an object. 
# However, a class also defines a public interface that the outside world 
# is supposed to use to manipulate the object. 
# This distinction between implementation details and the 
# public interface is important.

# In Python, almost everything about classes and objects is open.
# 
#     You can easily inspect object internals.
#     You can change things at will.
#     There is no strong notion of access-control (i.e., private class members)
# 
# That is an issue when you are trying to isolate details of 
# the internal implementation.

# Python relies on programming conventions to indicate the intended use 
# of something. 
# These conventions are based on naming. 
# There is a general attitude that it is up to the programmer 
# to observe the rules as opposed to having the language enforce them.

# #################################################
# Private Attributes
# 
#    Any attribute name with leading _ is considered to be private.

# If you find yourself using such names directly, 
# you’re probably doing something wrong. 
# ====> Look for higher level functionality.
#
# It's NOT OK to access a variable OR a function that starts with _ !!!

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

s = Stock('IBM', 50, 91.1)	## Access __init__( )

# Trouble:
#
#  shares should be an INTEGER
s.shares = 100			# Correct
print(s.__dict__)

# But we can assign ANY type of data to "shares" (or any variable):
s.shares = [1, 0, 0]
print(s.__dict__)

# How do we LIMIT this flexibility ????




