Chapter 8: Object-Oriented Programming
8.5: Encapsulation
Encapsulation is one of the most fundamental principles of object-oriented programming. It is the process of combining data (attributes) and methods that operate on that data within a single unit, usually a class, to create a cohesive and well-organized system.
By using encapsulation, we can limit access to certain parts of the object, which can help prevent unwanted interference or modification of the object's internal state. This can be especially useful in large and complex systems, where keeping track of the state of different objects can become difficult. Additionally, encapsulation can make it easier to modify and update the code, as changes to one part of the code will have less of an impact on the rest of the system. Overall, encapsulation is a powerful technique that can help create more robust and maintainable code.
Encapsulation is achieved by using private and protected access specifiers for attributes and methods. In Python, there is no strict concept of private or protected members, but we follow certain conventions to indicate the intended access level:
8.5.1: Public members:
By default, all members of a class are public, meaning they can be accessed from anywhere inside and outside the class. However, it is important to note that making all members public can lead to potential security issues, as sensitive information may be accessed or modified by unauthorized users.
To mitigate this risk, it is recommended to use access modifiers such as private or protected for sensitive members, and only provide public access to necessary members. Additionally, using encapsulation techniques such as getters and setters can help ensure that data is accessed and modified in a controlled and secure manner.
8.5.2: Protected members:
If a member is intended to be accessed only from within the class and its subclasses, its name should be prefixed with a single underscore (_). This is known as a convention and it is widely used in Python. However, it is important to note that this convention does not actually prevent access to the member from outside the class or its subclasses.
In such cases, it is recommended to use name mangling, a technique that adds a prefix to the name of the member to make it harder to access from outside the class. Name mangling is achieved by prefixing the member name with two underscores (__) and a suffix of one or more underscores. For example, a member named "my_var" would become "_MyClass__my_var" in the class called "MyClass". Note that this technique should be used with caution, as it can make the code harder to read and maintain.
8.5.3: Private members:
If a member is intended to be accessed only within the class (not even by subclasses), its name should be prefixed with double underscores (__). Python does provide a limited form of privacy by name mangling, which makes it difficult but not impossible to access the member from outside the class.
It is important to understand that name mangling is not a form of security. It is simply a convention used to discourage accidental access to private members. In fact, name mangling can be easily circumvented by accessing the member using its mangled name.
In addition, Python also allows for protected members, which can be accessed by subclasses but not from outside the class. These members are prefixed with a single underscore (_).
It is worth noting that the use of private and protected members is not necessary in all cases. In many cases, it is perfectly acceptable to make all members public. However, in larger projects or projects with multiple developers, the use of private and protected members can help to prevent unintended modifications to critical parts of the code.
Example:
class BankAccount:
def __init__(self, account_number, balance):
self._account_number = account_number
self.__balance = balance
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount <= self.__balance:
self.__balance -= amount
else:
print("Insufficient funds")
def get_balance(self):
return self.__balance
account = BankAccount("12345", 1000)
account.deposit(500)
account.withdraw(300)
print(account.get_balance()) # Output: 1200
In this example, the BankAccount
class has an attribute __balance
, which is intended to be private. We have provided methods like deposit()
, withdraw()
, and get_balance()
to manipulate the balance, thus preventing direct access to the attribute. Note that _account_number
is a protected member, which is not enforced by Python but signifies that it should be treated as protected by convention.
Exercise 8.5.1: Create a Simple Employee Class
In this exercise, you'll create a simple Employee class that uses encapsulation to protect its data members.
Instructions:
- Create a class called
Employee
. - Define the following private attributes:
__first_name
,__last_name
, and__salary
. - Create a constructor that takes
first_name
,last_name
, andsalary
as parameters and initializes the private attributes. - Create public methods
get_first_name()
,get_last_name()
, andget_salary()
that return the respective attributes. - Create a method
get_full_name()
that returns the employee's full name, which is the combination of the first and last name.
Solution:
class Employee:
def __init__(self, first_name, last_name, salary):
self.__first_name = first_name
self.__last_name = last_name
self.__salary = salary
def get_first_name(self):
return self.__first_name
def get_last_name(self):
return self.__last_name
def get_salary(self):
return self.__salary
def get_full_name(self):
return self.__first_name + " " + self.__last_name
employee = Employee("John", "Doe", 50000)
print(employee.get_full_name()) # Output: John Doe
Exercise 8.5.2: Implementing a Circle Class
In this exercise, you'll create a Circle
class that uses encapsulation to protect its data members and provide methods to manipulate them.
Instructions:
- Create a class called
Circle
. - Define the following private attributes:
__radius
and__pi
(use the value 3.14159 for pi). - Create a constructor that takes
radius
as a parameter and initializes the private attribute__radius
. - Create public methods
get_radius()
andget_pi()
that return the respective attributes. - Create methods
calculate_area()
andcalculate_circumference()
that return the area and circumference of the circle, respectively.
Solution:
class Circle:
def __init__(self, radius):
self.__radius = radius
self.__pi = 3.14159
def get_radius(self):
return self.__radius
def get_pi(self):
return self.__pi
def calculate_area(self):
return self.__pi * self.__radius ** 2
def calculate_circumference(self):
return 2 * self.__pi * self.__radius
circle = Circle(5)
print(circle.calculate_area()) # Output: 78.53975
print(circle.calculate_circumference()) # Output: 31.4159
Exercise 8.5.3: Creating a Password Protected Account
In this exercise, you'll create an Account
class that uses encapsulation to protect its data members and requires a password to access certain methods.
Instructions:
- Create a class called
Account
. - Define the following private attributes:
__account_number
,__balance
, and__password
. - Create a constructor that takes
account_number
,balance
, andpassword
as parameters and initializes the private attributes. - Create public methods
get_account_number()
andget_balance()
that return the respective attributes. - Create a method
validate_password(self, password)
that returnsTrue
if the given password matches the account's password, andFalse
otherwise. - Create a method
withdraw(self, amount, password)
that checks if the password is correct usingvalidate_password()
, and if so, subtracts the given amount from the balance.
Solution:
class Account:
def __init__(self, account_number, balance, password):
self.__account_number = account_number
self.__balance = balance
self.__password = password
def get_account_number(self):
return self.__account_number
def get_balance(self):
return self.__balance
def validate_password(self, password):
return self.__password == password
def withdraw(self, amount, password):
if self.validate_password(password):
self.__balance -= amount
return True
return False
account = Account("123456", 1000, "secret123")
print(account.get_account_number()) # Output: 123456
print(account.get_balance()) # Output: 1000
if account.withdraw(200, "secret123"):
print("Withdrawal successful!")
print("New balance:", account.get_balance()) # Output: 800
else:
print("Incorrect password or insufficient funds.")
In this exercise, you've created an Account
class that demonstrates the concept of encapsulation by protecting its data members and requiring a password for certain operations.
As we conclude Chapter 8, it's important to recognize the significance of Object-Oriented Programming (OOP) in Python. Through OOP, we can create more organized, maintainable, and scalable code. In this chapter, we have explored the key OOP concepts:
- Classes and Objects: The fundamentals of creating custom data types and instances in Python.
- Attributes and Methods: How to store data and define behaviors within classes.
- Inheritance: A way to create new classes from existing ones, promoting code reusability.
- Polymorphism: Leveraging the power of inheritance and method overriding to create flexible code that can handle different types of objects.
- Encapsulation: Protecting the internal state and implementation of a class, providing a well-defined interface for interacting with it.
By applying these principles in your Python programs, you can create code that is easier to understand, debug, and extend. Don't forget to practice implementing these concepts through exercises and real-world projects to gain a deeper understanding of OOP in Python.
As you move forward, remember that Python is a versatile language that supports multiple programming paradigms. Combining OOP with other approaches, such as functional programming, can help you further refine and tailor your code to suit various situations and requirements. See you in the next chapter. Happy coding!
8.5: Encapsulation
Encapsulation is one of the most fundamental principles of object-oriented programming. It is the process of combining data (attributes) and methods that operate on that data within a single unit, usually a class, to create a cohesive and well-organized system.
By using encapsulation, we can limit access to certain parts of the object, which can help prevent unwanted interference or modification of the object's internal state. This can be especially useful in large and complex systems, where keeping track of the state of different objects can become difficult. Additionally, encapsulation can make it easier to modify and update the code, as changes to one part of the code will have less of an impact on the rest of the system. Overall, encapsulation is a powerful technique that can help create more robust and maintainable code.
Encapsulation is achieved by using private and protected access specifiers for attributes and methods. In Python, there is no strict concept of private or protected members, but we follow certain conventions to indicate the intended access level:
8.5.1: Public members:
By default, all members of a class are public, meaning they can be accessed from anywhere inside and outside the class. However, it is important to note that making all members public can lead to potential security issues, as sensitive information may be accessed or modified by unauthorized users.
To mitigate this risk, it is recommended to use access modifiers such as private or protected for sensitive members, and only provide public access to necessary members. Additionally, using encapsulation techniques such as getters and setters can help ensure that data is accessed and modified in a controlled and secure manner.
8.5.2: Protected members:
If a member is intended to be accessed only from within the class and its subclasses, its name should be prefixed with a single underscore (_). This is known as a convention and it is widely used in Python. However, it is important to note that this convention does not actually prevent access to the member from outside the class or its subclasses.
In such cases, it is recommended to use name mangling, a technique that adds a prefix to the name of the member to make it harder to access from outside the class. Name mangling is achieved by prefixing the member name with two underscores (__) and a suffix of one or more underscores. For example, a member named "my_var" would become "_MyClass__my_var" in the class called "MyClass". Note that this technique should be used with caution, as it can make the code harder to read and maintain.
8.5.3: Private members:
If a member is intended to be accessed only within the class (not even by subclasses), its name should be prefixed with double underscores (__). Python does provide a limited form of privacy by name mangling, which makes it difficult but not impossible to access the member from outside the class.
It is important to understand that name mangling is not a form of security. It is simply a convention used to discourage accidental access to private members. In fact, name mangling can be easily circumvented by accessing the member using its mangled name.
In addition, Python also allows for protected members, which can be accessed by subclasses but not from outside the class. These members are prefixed with a single underscore (_).
It is worth noting that the use of private and protected members is not necessary in all cases. In many cases, it is perfectly acceptable to make all members public. However, in larger projects or projects with multiple developers, the use of private and protected members can help to prevent unintended modifications to critical parts of the code.
Example:
class BankAccount:
def __init__(self, account_number, balance):
self._account_number = account_number
self.__balance = balance
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount <= self.__balance:
self.__balance -= amount
else:
print("Insufficient funds")
def get_balance(self):
return self.__balance
account = BankAccount("12345", 1000)
account.deposit(500)
account.withdraw(300)
print(account.get_balance()) # Output: 1200
In this example, the BankAccount
class has an attribute __balance
, which is intended to be private. We have provided methods like deposit()
, withdraw()
, and get_balance()
to manipulate the balance, thus preventing direct access to the attribute. Note that _account_number
is a protected member, which is not enforced by Python but signifies that it should be treated as protected by convention.
Exercise 8.5.1: Create a Simple Employee Class
In this exercise, you'll create a simple Employee class that uses encapsulation to protect its data members.
Instructions:
- Create a class called
Employee
. - Define the following private attributes:
__first_name
,__last_name
, and__salary
. - Create a constructor that takes
first_name
,last_name
, andsalary
as parameters and initializes the private attributes. - Create public methods
get_first_name()
,get_last_name()
, andget_salary()
that return the respective attributes. - Create a method
get_full_name()
that returns the employee's full name, which is the combination of the first and last name.
Solution:
class Employee:
def __init__(self, first_name, last_name, salary):
self.__first_name = first_name
self.__last_name = last_name
self.__salary = salary
def get_first_name(self):
return self.__first_name
def get_last_name(self):
return self.__last_name
def get_salary(self):
return self.__salary
def get_full_name(self):
return self.__first_name + " " + self.__last_name
employee = Employee("John", "Doe", 50000)
print(employee.get_full_name()) # Output: John Doe
Exercise 8.5.2: Implementing a Circle Class
In this exercise, you'll create a Circle
class that uses encapsulation to protect its data members and provide methods to manipulate them.
Instructions:
- Create a class called
Circle
. - Define the following private attributes:
__radius
and__pi
(use the value 3.14159 for pi). - Create a constructor that takes
radius
as a parameter and initializes the private attribute__radius
. - Create public methods
get_radius()
andget_pi()
that return the respective attributes. - Create methods
calculate_area()
andcalculate_circumference()
that return the area and circumference of the circle, respectively.
Solution:
class Circle:
def __init__(self, radius):
self.__radius = radius
self.__pi = 3.14159
def get_radius(self):
return self.__radius
def get_pi(self):
return self.__pi
def calculate_area(self):
return self.__pi * self.__radius ** 2
def calculate_circumference(self):
return 2 * self.__pi * self.__radius
circle = Circle(5)
print(circle.calculate_area()) # Output: 78.53975
print(circle.calculate_circumference()) # Output: 31.4159
Exercise 8.5.3: Creating a Password Protected Account
In this exercise, you'll create an Account
class that uses encapsulation to protect its data members and requires a password to access certain methods.
Instructions:
- Create a class called
Account
. - Define the following private attributes:
__account_number
,__balance
, and__password
. - Create a constructor that takes
account_number
,balance
, andpassword
as parameters and initializes the private attributes. - Create public methods
get_account_number()
andget_balance()
that return the respective attributes. - Create a method
validate_password(self, password)
that returnsTrue
if the given password matches the account's password, andFalse
otherwise. - Create a method
withdraw(self, amount, password)
that checks if the password is correct usingvalidate_password()
, and if so, subtracts the given amount from the balance.
Solution:
class Account:
def __init__(self, account_number, balance, password):
self.__account_number = account_number
self.__balance = balance
self.__password = password
def get_account_number(self):
return self.__account_number
def get_balance(self):
return self.__balance
def validate_password(self, password):
return self.__password == password
def withdraw(self, amount, password):
if self.validate_password(password):
self.__balance -= amount
return True
return False
account = Account("123456", 1000, "secret123")
print(account.get_account_number()) # Output: 123456
print(account.get_balance()) # Output: 1000
if account.withdraw(200, "secret123"):
print("Withdrawal successful!")
print("New balance:", account.get_balance()) # Output: 800
else:
print("Incorrect password or insufficient funds.")
In this exercise, you've created an Account
class that demonstrates the concept of encapsulation by protecting its data members and requiring a password for certain operations.
As we conclude Chapter 8, it's important to recognize the significance of Object-Oriented Programming (OOP) in Python. Through OOP, we can create more organized, maintainable, and scalable code. In this chapter, we have explored the key OOP concepts:
- Classes and Objects: The fundamentals of creating custom data types and instances in Python.
- Attributes and Methods: How to store data and define behaviors within classes.
- Inheritance: A way to create new classes from existing ones, promoting code reusability.
- Polymorphism: Leveraging the power of inheritance and method overriding to create flexible code that can handle different types of objects.
- Encapsulation: Protecting the internal state and implementation of a class, providing a well-defined interface for interacting with it.
By applying these principles in your Python programs, you can create code that is easier to understand, debug, and extend. Don't forget to practice implementing these concepts through exercises and real-world projects to gain a deeper understanding of OOP in Python.
As you move forward, remember that Python is a versatile language that supports multiple programming paradigms. Combining OOP with other approaches, such as functional programming, can help you further refine and tailor your code to suit various situations and requirements. See you in the next chapter. Happy coding!
8.5: Encapsulation
Encapsulation is one of the most fundamental principles of object-oriented programming. It is the process of combining data (attributes) and methods that operate on that data within a single unit, usually a class, to create a cohesive and well-organized system.
By using encapsulation, we can limit access to certain parts of the object, which can help prevent unwanted interference or modification of the object's internal state. This can be especially useful in large and complex systems, where keeping track of the state of different objects can become difficult. Additionally, encapsulation can make it easier to modify and update the code, as changes to one part of the code will have less of an impact on the rest of the system. Overall, encapsulation is a powerful technique that can help create more robust and maintainable code.
Encapsulation is achieved by using private and protected access specifiers for attributes and methods. In Python, there is no strict concept of private or protected members, but we follow certain conventions to indicate the intended access level:
8.5.1: Public members:
By default, all members of a class are public, meaning they can be accessed from anywhere inside and outside the class. However, it is important to note that making all members public can lead to potential security issues, as sensitive information may be accessed or modified by unauthorized users.
To mitigate this risk, it is recommended to use access modifiers such as private or protected for sensitive members, and only provide public access to necessary members. Additionally, using encapsulation techniques such as getters and setters can help ensure that data is accessed and modified in a controlled and secure manner.
8.5.2: Protected members:
If a member is intended to be accessed only from within the class and its subclasses, its name should be prefixed with a single underscore (_). This is known as a convention and it is widely used in Python. However, it is important to note that this convention does not actually prevent access to the member from outside the class or its subclasses.
In such cases, it is recommended to use name mangling, a technique that adds a prefix to the name of the member to make it harder to access from outside the class. Name mangling is achieved by prefixing the member name with two underscores (__) and a suffix of one or more underscores. For example, a member named "my_var" would become "_MyClass__my_var" in the class called "MyClass". Note that this technique should be used with caution, as it can make the code harder to read and maintain.
8.5.3: Private members:
If a member is intended to be accessed only within the class (not even by subclasses), its name should be prefixed with double underscores (__). Python does provide a limited form of privacy by name mangling, which makes it difficult but not impossible to access the member from outside the class.
It is important to understand that name mangling is not a form of security. It is simply a convention used to discourage accidental access to private members. In fact, name mangling can be easily circumvented by accessing the member using its mangled name.
In addition, Python also allows for protected members, which can be accessed by subclasses but not from outside the class. These members are prefixed with a single underscore (_).
It is worth noting that the use of private and protected members is not necessary in all cases. In many cases, it is perfectly acceptable to make all members public. However, in larger projects or projects with multiple developers, the use of private and protected members can help to prevent unintended modifications to critical parts of the code.
Example:
class BankAccount:
def __init__(self, account_number, balance):
self._account_number = account_number
self.__balance = balance
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount <= self.__balance:
self.__balance -= amount
else:
print("Insufficient funds")
def get_balance(self):
return self.__balance
account = BankAccount("12345", 1000)
account.deposit(500)
account.withdraw(300)
print(account.get_balance()) # Output: 1200
In this example, the BankAccount
class has an attribute __balance
, which is intended to be private. We have provided methods like deposit()
, withdraw()
, and get_balance()
to manipulate the balance, thus preventing direct access to the attribute. Note that _account_number
is a protected member, which is not enforced by Python but signifies that it should be treated as protected by convention.
Exercise 8.5.1: Create a Simple Employee Class
In this exercise, you'll create a simple Employee class that uses encapsulation to protect its data members.
Instructions:
- Create a class called
Employee
. - Define the following private attributes:
__first_name
,__last_name
, and__salary
. - Create a constructor that takes
first_name
,last_name
, andsalary
as parameters and initializes the private attributes. - Create public methods
get_first_name()
,get_last_name()
, andget_salary()
that return the respective attributes. - Create a method
get_full_name()
that returns the employee's full name, which is the combination of the first and last name.
Solution:
class Employee:
def __init__(self, first_name, last_name, salary):
self.__first_name = first_name
self.__last_name = last_name
self.__salary = salary
def get_first_name(self):
return self.__first_name
def get_last_name(self):
return self.__last_name
def get_salary(self):
return self.__salary
def get_full_name(self):
return self.__first_name + " " + self.__last_name
employee = Employee("John", "Doe", 50000)
print(employee.get_full_name()) # Output: John Doe
Exercise 8.5.2: Implementing a Circle Class
In this exercise, you'll create a Circle
class that uses encapsulation to protect its data members and provide methods to manipulate them.
Instructions:
- Create a class called
Circle
. - Define the following private attributes:
__radius
and__pi
(use the value 3.14159 for pi). - Create a constructor that takes
radius
as a parameter and initializes the private attribute__radius
. - Create public methods
get_radius()
andget_pi()
that return the respective attributes. - Create methods
calculate_area()
andcalculate_circumference()
that return the area and circumference of the circle, respectively.
Solution:
class Circle:
def __init__(self, radius):
self.__radius = radius
self.__pi = 3.14159
def get_radius(self):
return self.__radius
def get_pi(self):
return self.__pi
def calculate_area(self):
return self.__pi * self.__radius ** 2
def calculate_circumference(self):
return 2 * self.__pi * self.__radius
circle = Circle(5)
print(circle.calculate_area()) # Output: 78.53975
print(circle.calculate_circumference()) # Output: 31.4159
Exercise 8.5.3: Creating a Password Protected Account
In this exercise, you'll create an Account
class that uses encapsulation to protect its data members and requires a password to access certain methods.
Instructions:
- Create a class called
Account
. - Define the following private attributes:
__account_number
,__balance
, and__password
. - Create a constructor that takes
account_number
,balance
, andpassword
as parameters and initializes the private attributes. - Create public methods
get_account_number()
andget_balance()
that return the respective attributes. - Create a method
validate_password(self, password)
that returnsTrue
if the given password matches the account's password, andFalse
otherwise. - Create a method
withdraw(self, amount, password)
that checks if the password is correct usingvalidate_password()
, and if so, subtracts the given amount from the balance.
Solution:
class Account:
def __init__(self, account_number, balance, password):
self.__account_number = account_number
self.__balance = balance
self.__password = password
def get_account_number(self):
return self.__account_number
def get_balance(self):
return self.__balance
def validate_password(self, password):
return self.__password == password
def withdraw(self, amount, password):
if self.validate_password(password):
self.__balance -= amount
return True
return False
account = Account("123456", 1000, "secret123")
print(account.get_account_number()) # Output: 123456
print(account.get_balance()) # Output: 1000
if account.withdraw(200, "secret123"):
print("Withdrawal successful!")
print("New balance:", account.get_balance()) # Output: 800
else:
print("Incorrect password or insufficient funds.")
In this exercise, you've created an Account
class that demonstrates the concept of encapsulation by protecting its data members and requiring a password for certain operations.
As we conclude Chapter 8, it's important to recognize the significance of Object-Oriented Programming (OOP) in Python. Through OOP, we can create more organized, maintainable, and scalable code. In this chapter, we have explored the key OOP concepts:
- Classes and Objects: The fundamentals of creating custom data types and instances in Python.
- Attributes and Methods: How to store data and define behaviors within classes.
- Inheritance: A way to create new classes from existing ones, promoting code reusability.
- Polymorphism: Leveraging the power of inheritance and method overriding to create flexible code that can handle different types of objects.
- Encapsulation: Protecting the internal state and implementation of a class, providing a well-defined interface for interacting with it.
By applying these principles in your Python programs, you can create code that is easier to understand, debug, and extend. Don't forget to practice implementing these concepts through exercises and real-world projects to gain a deeper understanding of OOP in Python.
As you move forward, remember that Python is a versatile language that supports multiple programming paradigms. Combining OOP with other approaches, such as functional programming, can help you further refine and tailor your code to suit various situations and requirements. See you in the next chapter. Happy coding!
8.5: Encapsulation
Encapsulation is one of the most fundamental principles of object-oriented programming. It is the process of combining data (attributes) and methods that operate on that data within a single unit, usually a class, to create a cohesive and well-organized system.
By using encapsulation, we can limit access to certain parts of the object, which can help prevent unwanted interference or modification of the object's internal state. This can be especially useful in large and complex systems, where keeping track of the state of different objects can become difficult. Additionally, encapsulation can make it easier to modify and update the code, as changes to one part of the code will have less of an impact on the rest of the system. Overall, encapsulation is a powerful technique that can help create more robust and maintainable code.
Encapsulation is achieved by using private and protected access specifiers for attributes and methods. In Python, there is no strict concept of private or protected members, but we follow certain conventions to indicate the intended access level:
8.5.1: Public members:
By default, all members of a class are public, meaning they can be accessed from anywhere inside and outside the class. However, it is important to note that making all members public can lead to potential security issues, as sensitive information may be accessed or modified by unauthorized users.
To mitigate this risk, it is recommended to use access modifiers such as private or protected for sensitive members, and only provide public access to necessary members. Additionally, using encapsulation techniques such as getters and setters can help ensure that data is accessed and modified in a controlled and secure manner.
8.5.2: Protected members:
If a member is intended to be accessed only from within the class and its subclasses, its name should be prefixed with a single underscore (_). This is known as a convention and it is widely used in Python. However, it is important to note that this convention does not actually prevent access to the member from outside the class or its subclasses.
In such cases, it is recommended to use name mangling, a technique that adds a prefix to the name of the member to make it harder to access from outside the class. Name mangling is achieved by prefixing the member name with two underscores (__) and a suffix of one or more underscores. For example, a member named "my_var" would become "_MyClass__my_var" in the class called "MyClass". Note that this technique should be used with caution, as it can make the code harder to read and maintain.
8.5.3: Private members:
If a member is intended to be accessed only within the class (not even by subclasses), its name should be prefixed with double underscores (__). Python does provide a limited form of privacy by name mangling, which makes it difficult but not impossible to access the member from outside the class.
It is important to understand that name mangling is not a form of security. It is simply a convention used to discourage accidental access to private members. In fact, name mangling can be easily circumvented by accessing the member using its mangled name.
In addition, Python also allows for protected members, which can be accessed by subclasses but not from outside the class. These members are prefixed with a single underscore (_).
It is worth noting that the use of private and protected members is not necessary in all cases. In many cases, it is perfectly acceptable to make all members public. However, in larger projects or projects with multiple developers, the use of private and protected members can help to prevent unintended modifications to critical parts of the code.
Example:
class BankAccount:
def __init__(self, account_number, balance):
self._account_number = account_number
self.__balance = balance
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount <= self.__balance:
self.__balance -= amount
else:
print("Insufficient funds")
def get_balance(self):
return self.__balance
account = BankAccount("12345", 1000)
account.deposit(500)
account.withdraw(300)
print(account.get_balance()) # Output: 1200
In this example, the BankAccount
class has an attribute __balance
, which is intended to be private. We have provided methods like deposit()
, withdraw()
, and get_balance()
to manipulate the balance, thus preventing direct access to the attribute. Note that _account_number
is a protected member, which is not enforced by Python but signifies that it should be treated as protected by convention.
Exercise 8.5.1: Create a Simple Employee Class
In this exercise, you'll create a simple Employee class that uses encapsulation to protect its data members.
Instructions:
- Create a class called
Employee
. - Define the following private attributes:
__first_name
,__last_name
, and__salary
. - Create a constructor that takes
first_name
,last_name
, andsalary
as parameters and initializes the private attributes. - Create public methods
get_first_name()
,get_last_name()
, andget_salary()
that return the respective attributes. - Create a method
get_full_name()
that returns the employee's full name, which is the combination of the first and last name.
Solution:
class Employee:
def __init__(self, first_name, last_name, salary):
self.__first_name = first_name
self.__last_name = last_name
self.__salary = salary
def get_first_name(self):
return self.__first_name
def get_last_name(self):
return self.__last_name
def get_salary(self):
return self.__salary
def get_full_name(self):
return self.__first_name + " " + self.__last_name
employee = Employee("John", "Doe", 50000)
print(employee.get_full_name()) # Output: John Doe
Exercise 8.5.2: Implementing a Circle Class
In this exercise, you'll create a Circle
class that uses encapsulation to protect its data members and provide methods to manipulate them.
Instructions:
- Create a class called
Circle
. - Define the following private attributes:
__radius
and__pi
(use the value 3.14159 for pi). - Create a constructor that takes
radius
as a parameter and initializes the private attribute__radius
. - Create public methods
get_radius()
andget_pi()
that return the respective attributes. - Create methods
calculate_area()
andcalculate_circumference()
that return the area and circumference of the circle, respectively.
Solution:
class Circle:
def __init__(self, radius):
self.__radius = radius
self.__pi = 3.14159
def get_radius(self):
return self.__radius
def get_pi(self):
return self.__pi
def calculate_area(self):
return self.__pi * self.__radius ** 2
def calculate_circumference(self):
return 2 * self.__pi * self.__radius
circle = Circle(5)
print(circle.calculate_area()) # Output: 78.53975
print(circle.calculate_circumference()) # Output: 31.4159
Exercise 8.5.3: Creating a Password Protected Account
In this exercise, you'll create an Account
class that uses encapsulation to protect its data members and requires a password to access certain methods.
Instructions:
- Create a class called
Account
. - Define the following private attributes:
__account_number
,__balance
, and__password
. - Create a constructor that takes
account_number
,balance
, andpassword
as parameters and initializes the private attributes. - Create public methods
get_account_number()
andget_balance()
that return the respective attributes. - Create a method
validate_password(self, password)
that returnsTrue
if the given password matches the account's password, andFalse
otherwise. - Create a method
withdraw(self, amount, password)
that checks if the password is correct usingvalidate_password()
, and if so, subtracts the given amount from the balance.
Solution:
class Account:
def __init__(self, account_number, balance, password):
self.__account_number = account_number
self.__balance = balance
self.__password = password
def get_account_number(self):
return self.__account_number
def get_balance(self):
return self.__balance
def validate_password(self, password):
return self.__password == password
def withdraw(self, amount, password):
if self.validate_password(password):
self.__balance -= amount
return True
return False
account = Account("123456", 1000, "secret123")
print(account.get_account_number()) # Output: 123456
print(account.get_balance()) # Output: 1000
if account.withdraw(200, "secret123"):
print("Withdrawal successful!")
print("New balance:", account.get_balance()) # Output: 800
else:
print("Incorrect password or insufficient funds.")
In this exercise, you've created an Account
class that demonstrates the concept of encapsulation by protecting its data members and requiring a password for certain operations.
As we conclude Chapter 8, it's important to recognize the significance of Object-Oriented Programming (OOP) in Python. Through OOP, we can create more organized, maintainable, and scalable code. In this chapter, we have explored the key OOP concepts:
- Classes and Objects: The fundamentals of creating custom data types and instances in Python.
- Attributes and Methods: How to store data and define behaviors within classes.
- Inheritance: A way to create new classes from existing ones, promoting code reusability.
- Polymorphism: Leveraging the power of inheritance and method overriding to create flexible code that can handle different types of objects.
- Encapsulation: Protecting the internal state and implementation of a class, providing a well-defined interface for interacting with it.
By applying these principles in your Python programs, you can create code that is easier to understand, debug, and extend. Don't forget to practice implementing these concepts through exercises and real-world projects to gain a deeper understanding of OOP in Python.
As you move forward, remember that Python is a versatile language that supports multiple programming paradigms. Combining OOP with other approaches, such as functional programming, can help you further refine and tailor your code to suit various situations and requirements. See you in the next chapter. Happy coding!