<jupyter_start><jupyter_text>Object-oriented Programming> Everything in python is an object, a member of some class.Classes are the fundamental core of OOP and the blueprint for custom software objects.Classes make it easy to design, implement, test, and debug software applications. Objectives* Examine object-oriented paradigm and its features  - inhertiance, polymorphism, overloading* Build custom classes* Control attribute access* Simulate "Private" attributes* Super and subclasses* Namespace and scope---  IntroductionWe have already using class objects, for example integers are a class.  You can test the type with the `type` function.<jupyter_code>x = 5
type(x)<jupyter_output><empty_output><jupyter_text>Dictionaries are also classes.<jupyter_code>y = {1:1,2:4}
type(y)<jupyter_output><empty_output><jupyter_text>Of course, `float`, `str`, `list`, `tuple`, and `set` are also built-in classes which you should be familiar.Python has many existing classes packaged as class libraries and can be found on GitHub and installed using `pip` or `conda`.  --- Classes are new data types and many are **derived** from an existing class (**superclass**).  These *derived* classes are known as **subclasses** and **inherit** the *attributes* (variables) and *methods* (functions) from the superclass.   Custom ClassesA class is defined using the **`class`** keyword followed by the class name and a colon (:).  This is the **class header**.  Class names are typically capitalized, if following *Style Guide for Python*, but need not be.  A *docstring* usually follows the class header and can be accessed by typing the class name followed by the question mark.Here is an *Account* class.<jupyter_code># Account Class
class Account:
  """Example of a user-defined class"""

  def __init__(self, name, balance):
    """Constructor method"""
    
    # Error checking
    if balance < 0.0:
      raise ValueError('Initial deposit has to be positive.')
    self.name = name
    self.balance = balance


  def deposit(self, amount):
    """Deposit to account"""
    # If amount < 0, error
    if amount < 0.0:
      raise ValueError('Deposit must be positive.')
    self.balance += amount


  def withdraw(self, amount):
    """Withdraw money"""

    # If amount < 0, error
    if amount < 0.0:
      raise ValueError('Deposit must be positive.')
    elif amount > self.balance:
      raise ValueError('Insufficient funds.')    
    self.balance -= amount 


# Get help on the class
Account?
# Create an object (instance of the class)
x = Account('James', 100.00)
# Check the name on the account
x.name
# What is the balance
x.balance
# Go to ATM and withdraw $5.00
x.withdraw(5)
# Check balance
x.balance
# Got paid, deposit $50
x.deposit(50)
# Check balance again
x.balance
# Here is another account
y = Account('Joe',50)
# Withdraw $10, deposit $20, check balance of Joe's account
y.withdraw(10.0)
y.deposit(20.0)
y.balance
# What is the combined balance of James and Joe?
x.balance + y.balance<jupyter_output><empty_output><jupyter_text>Objects are created using the assignment operator:`x = Classname(parameter values)`When an object is created, the attributes are set using:`self.attribute = value`Methods are called using:`x.method(values)`  Default valuesDefault values can be assigned and passed in the parameter list.<jupyter_code># Account Class
class Account:
  """Example of a user-defined class"""

  def __init__(self, name, balance=0.0):
    """Constructor method"""
    
    # Error checking
    if balance < 0.0:
      raise ValueError('Initial deposit has to be positive.')
    self.name = name
    self.balance = balance


  def deposit(self, amount):
    """Deposit to account"""
    # If amount < 0, error
    if amount < 0.0:
      raise ValueError('Deposit must be positive.')
    self.balance += amount


  def withdraw(self, amount):
    """Withdraw money"""

    # If amount < 0, error
    if amount < 0.0:
      raise ValueError('Deposit must be positive.')
    elif amount > self.balance:
      raise ValueError('Insufficient funds.')    
    self.balance -= amount 

x=Account('Joe')
x.balance<jupyter_output><empty_output><jupyter_text>Encapsulation - Private dataA classes **client code** is code that uses objects of the class.  Most OOP allow hiding code (encapsulating = make private).<jupyter_code># It is undesirable to provide direct access to the *balance* attribute.
x.balance=-100.00
print(x.balance)<jupyter_output>-100.0<jupyter_text>Python, unlike other programming languages, does **not** have private data.  However, *naming conventions* are used to indicate private data.  In particular, any attribute beginning with the underscore are considered *private*, however, attributes are always accessible.<jupyter_code># Account Class
class Account2:
  """Example of a user-defined class"""

  def __init__(self, name, balance=0.0):
    """Constructor method"""
    
    # Error checking
    if balance < 0.0:
      raise ValueError('Initial deposit has to be positive.')
    self.name = name
    self._balance = balance


  def deposit(self, amount):
    """Deposit to account"""
    # If amount < 0, error
    if amount < 0.0:
      raise ValueError('Deposit must be positive.')
    self._balance += amount


  def withdraw(self, amount):
    """Withdraw money"""

    # If amount < 0, error
    if amount < 0.0:
      raise ValueError('Deposit must be positive.')
    elif amount > self._balance:
      raise ValueError('Insufficient funds.')    
    self._balance -= amount 
y=Account2('Ishbak')
y._balance = -100
y._balance
# Account Class
class Account3:
  """Example of a user-defined class"""

  def __init__(self, name, balance=0.0):
    """Constructor method"""
    
    # Error checking
    if balance < 0.0:
      raise ValueError('Initial deposit has to be positive.')
    self.name = name
    self.__balance = balance


  def deposit(self, amount):
    """Deposit to account"""
    # If amount < 0, error
    if amount < 0.0:
      raise ValueError('Deposit must be positive.')
    self.__balance += amount


  def withdraw(self, amount):
    """Withdraw money"""

    # If amount < 0, error
    if amount < 0.0:
      raise ValueError('Deposit must be positive.')
    elif amount > self.__balance:
      raise ValueError('Insufficient funds.')    
    self.__balance -= amount 
z=Account3('Yaakov',100.0)
z.balance<jupyter_output><empty_output><jupyter_text>Overload OperatorsA key feature of Classes is the ability to assign operators (e.g., +) to the objects of the new class.<jupyter_code>2+3
type(2+3)
2.0+3.0
type(2.0+3.0)
x=[1,2,3]
y=[4,5,6]
x+y
type(x+y)
'Hello' + 'World'
type('Hello'+'Work')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10861759425493717, 'fraction_numerical': 0.015858767205266307, 'mean_word_length': 3.851233671988389, 'pattern_counts': {'":': 0, '<': 48, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '97230', 'license': 'MIT', 'n_tokens_mistral': 2025, 'n_tokens_neox': 1924, 'n_words': 738, 'path': 'lectures/Chap10-OOP.ipynb', 'repo_name': 'jamesquinlan/python-DSC225', 'size': '6684'}