1. Managing data access: private attributes#

  • All class data in Python is technically public. Any attribute or method of any class can be access by anyone.

  • However, they are a few ways to manage access to data.

1.1. Naming Convention: internal attributes#

  • The most important convention is using a single leading _ –> underscore to indicate an attribute or method that isn’t a part of the public access interface, and can change without notice.

    Attention

    Nothing is technically preventing you from using these attributes, but a single leading underscore is the developer’s way of saying that you shouldn’t.

1.1.1. Examples - 1#

  • See some examples below:

    obj._att_name
    obj._method_name()
    

    Note

    See that the attribute starts with a single _ –> “internal”. That means: ” don’t touch this “

1.2. Naming Convention: pseudoprivate attributes#

  • Another naming convention is using a leading __ –> double underscore. Attributes and methods whose names start with __ are the closest thing Python has to “private” fields and methods of other programming languages.

  • The main use of these pseudo-private attributes is to prevent name clashes in child classes. It is a way to protect important attributes and methods that should not be overridden.

1.2.1. Examples - 2#

  • See some examples below:

    obj.__att_name            # ---> This is interpreted as `obj.Myclass__attr_name`
    obj.__method_name()
    

    Note

    See that the attribute starts with __ –> “private”. That means: important attrivutes and methods that should not be overridden.

    Warning

    Leading and trailing __ are only used for built-in Python methods __init__() __repr__()

1.2.2. Example - 3#

class BetterDate:

    _MAX_DAYS = 30
    _MAX_MONTHS = 12
    
    def __init__(self, year, month, day):
        self.year, self.month, self.day = year, month, day
        
    @classmethod
    def from_str(cls, datestr):
        year, month, day = map(int, datestr.split("-"))
        return cls(year, month, day)
        
    # Add _is_valid() checking day and month values
    def _is_valid(self):
        return (self.day <= BetterDate._MAX_DAYS) and \
            (self.month <= BetterDate._MAX_MONTHS)
        
bd1 = BetterDate(2020, 4, 30)
print(bd1._is_valid())

bd2 = BetterDate(2020, 6, 45)
print(bd2._is_valid())

1.3. Use @property to customize access#

  • How can we control attribute access? or check the value? or make attributes read-only?

1.3.1. Example - 1 property#

class Employee:

    def __init__(self, name, new_salary):
        self._salary = new_salary        #--> use "protected" attribute with leading `_` 
    
    @property                            #--> use `@property` on a method whose name is   
    def salary(self):                    # exactly the name of the restricted attribute;
        return self._salary       

    @salary.setter                       #--> use `@attr.setter on a method `attr() that
    def salary(self, new_salary):        # will be called on `obj.attr = value`
        if new_salary < 0 :
            raise ValueError("Invalid salary. Cool right? this value is now protected =) ")
        self._salary = new_salary
        
emp = Employee("Fernando",100000)
print(emp.salary)

emp.salary = -35000  # --> @salary.setter

1.3.2. Example - 2 property#

class Customer:
    def __init__(self, name, new_bal):
        self.name = name
        if new_bal < 0:
           raise ValueError("Invalid balance!")
        self._balance = new_bal  

    # Add a decorated balance() method returning _balance        
    @property
    def balance(self):
        return self._balance

    # Add a setter balance() method
    @balance.setter
    def balance(self, new_bal):
        # Validate the parameter value
        if new_bal < 0:
           raise ValueError("Invalid balance!")
        self._balance = new_bal
        print("Setter method called")

# Create a Customer        
cust = Customer("Belinda Lutz", 2000)

# Assign 3000 to the balance property
cust.balance = 3000

# Print the balance property
print(cust.balance)