Encapsulation of three object-oriented features of Python

1, Introduce

Object oriented programming has three characteristics: encapsulation, inheritance and polymorphism; One of the most important features is encapsulation.

Encapsulation refers to the integration of data and functions. Does it sound familiar? Yes, the word "integration" we mentioned earlier is actually a popular term for encapsulation. In addition, for the attributes encapsulated in objects or classes, we can strictly control their access in two steps: hidden and open interfaces.

2, Hide properties

Python's hiding mechanism starts with a double underscore to hide attributes (set to private), but in fact, this is only a deformation operation. All attributes starting with a double sliding line in a class will automatically become the form of "class name generic name" at the class definition stage and when detecting syntax:

class Foo:
    __N = 0  # Deform to _foo_n

    def __init__(self):  # When defining a function, the function syntax will be detected, so the attributes beginning with _ will be deformed
        self.__x = 10  # Deform to self. _foo_x

    def __f1(self):  # Deform to _foo_f1
        print('__f1 run')

    def f2(self):  # When defining a function, the function syntax will be detected, so the method starting with _ will also be deformed
        self.__f1()  # Deform to self. _foo_f1 ()


print(Foo.__N)  # Error: AttributeError: class Foo has no attribute _N

obj = Foo()
print(obj.__x)  # Error: AttributeError: object obj has no attribute _x

The problems needing attention in this deformation are:

1. You can't directly access the attribute starting with double underscore outside the class, but you can spell the name after knowing the class name and attribute name: class name _ attribute, and then you can access it, such as Foo._Foo__N. therefore, this operation does not strictly restrict external access, but is just a deformation in the grammatical sense.

>>> Foo.__dict__
{..., '_Foo__N': 0, ...}
 
>>> obj.__dict__
{'_Foo__x': 10}
 
>>> Foo._Foo__N
0
>>> obj._Foo__x
10
>>> obj._Foo__N
0

2. Inside the class, you can directly access the attributes at the beginning of the double slide line (external not internal), such as self. _f1(), because the attributes at the beginning of the double slide line inside the class are uniformly deformed during the class definition stage:

>>> obj.f2()
__f1 run

3. The deformation operation only occurs once in the class definition stage. The assignment operation after the class definition will not deform:

>>> Foo.__M = 100
>>> Foo.__dict__
{..., '__M': 100, ...}
>>> Foo.__M
100
 
>>> obj.__y = 20
>>> obj.__dict__
{'__y': 20, '_Foo__x': 10}
>>> obj.__y
20

3, Open interface

Attributes are defined for use, so hiding is not the purpose.

3.1 hide data attributes

Hiding data limits the direct operation of data outside the class. Then, corresponding interfaces should be provided within the class to allow external indirect operation of data. Additional logic can be added to the interface to strictly control the operation of data:

class Teacher:
    def __init__(self, name, age):  # Hide your name and age
        self.__name = name
        self.__age = age

    def tell_info(self):  # Provide an interface for accessing teacher information
        print('full name:%s,Age:%s' % (self.__name, self.__age))

    def set_info(self, name, age):  # Provide an interface for setting teacher information, and attach the logic of type check
        if not isinstance(name, str):
            raise TypeError('Name must be a string type')
        if not isinstance(age, int):
            raise TypeError('Age must be integer')
        self.__name = name
        self.__age = age

t = Teacher('lili', 18)
t.set_info('LiLi', '19')

You can see that in the parameter passing of the code, the age is not integer, and the following exception will be thrown:

Traceback (most recent call last):
  File "C:/Program Files/PycharmProjects/Python object-oriented/Object oriented correlation/Packaging of three characteristics.py", line 19, in <module>
    t.set_info('LiLi', '19')
  File "C:/Program Files/PycharmProjects/Python object-oriented/Object oriented correlation/Packaging of three characteristics.py", line 13, in set_info
    raise TypeError('Age must be integer')
TypeError: Age must be integer

You must comply with the rule that the name is string type and the age is integer before you can set it normally:

t.set_info('LiLi', 19)  # The name is a string type, the age is an integer, and can be set normally
t.tell_info()  # Name: Li Li, age: 19

3.2 hide function properties

The purpose of hiding is to isolate the complexity, such as the withdrawal function of ATM program. This function is composed of many other functions, such as card insertion, identity authentication, entering amount, printing small ticket, withdrawing money, etc. for users, we only need to develop the withdrawal function interface, and we can hide the other functions:

class ATM:
    def __card(self):  # Card insertion
        print('Card insertion')

    def __auth(self):  # identity authentication 
        print('User authentication')

    def __input(self):  # Enter amount
        print('Enter withdrawal amount')

    def __print_bill(self):  # Print small ticket
        print('Print bill')

    def __take_money(self):  # Withdraw money
        print('withdraw money')

    def withdraw(self):  # Withdrawal function
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()


obj = ATM()
obj.withdraw()

Summary:

The essence of hidden attributes and open interfaces is to clearly distinguish between the inside and outside. The things in the package can be modified inside the class without affecting the code of the external caller; while the outside of the class only needs to get an interface. As long as the interface name and parameters remain unchanged, no matter how the designer changes the internal implementation code, the user does not need to change the code. This provides a good basis for cooperation, As long as the basic contract of interface remains unchanged, code modification is not a concern.

4, Use of property

BMI index is an index used to measure the impact of a person's weight and height on health. The calculation formula is:

Body mass index( BMI)= Weight( kg)÷height^2(m)
EX: 70kg ÷(1.75×1.75)= 22.86

Height or weight are constantly changing, so you need to calculate the BMI value every time you want to view it, but obviously BMI sounds more like a feature than a function. Therefore, Python provides a decorator property specifically to "disguise" the functions in the class The data attribute of the object. When the object accesses this special attribute, it will trigger the execution of the function, and then take the return value as the result of this access, for example:

class People:
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height

    @property
    def bmi(self):
        return self.weight / (self.height ** 2)


obj = People('lili', 75, 1.85)
print(obj.bmi)  # Print result: 21.913805697589478
"""Trigger method bmi The implementation of will obj Automatic transmission to self,The returned value after execution is the result of this reference"""

Using property effectively ensures the consistency of property access. In addition, property also provides the functions of setting and deleting properties, such as:

class Foo:
    def __init__(self, val):
        self.__NAME = val  # Hide attributes

    @property
    def name(self):
        return self.__NAME

    @name.setter
    def name(self, value):
        if not isinstance(value, str):  # Type check before setting value
            raise TypeError('%s must be str' % value)
        self.__NAME = value  # After passing the type check, store the value value in the real location self. _name

    @name.deleter
    def name(self):
        raise PermissionError('Can not delete')

        
f = Foo('lili')
print(f.name)  # Print result: lili

f.name = 'LiLi'  # Trigger the function name(f, 'LiLi') corresponding to the name.setter decorator
f.name = 123  # Trigger the function name(f, 123) corresponding to name.setter and throw an exception TypeError: 123 must be str
del f.name  # Trigger the function name(f) corresponding to name.delete and throw an exception PermissionError: Can not delete

The Foo class in the above code can also be written in another form, that is, the syntax format without decorator syntax sugar, as follows:

class Foo:
    def __init__(self, val):
        self.__NAME = val  # Hide attributes

    def name(self):
        return self.__NAME

    def set_name(self, value):
        if not isinstance(value, str):  # Type check before setting value
            raise TypeError('%s must be str' % value)
        self.__NAME = value  # After passing the type check, store the value value in the real location self. _name

    def del_name(self):
        raise PermissionError('Can not delete')

    name = property(name, set_name, del_name)  # The name of the corresponding function in parentheses shall be placed in the order of reading, modifying and deleting

The subsequent use method is the same as before without any difference. This method is only for understanding. The first method is recommended here.

Posted on Mon, 06 Dec 2021 14:00:01 -0500 by KI114