Object-Oriented Programming in Python for Java Developers: Advanced Concepts
For developers familiar with Java, transitioning to Python's object-oriented features involves understanding subtle but important differences. This guide covers encapsulation, runtime introspection (often called "reflection" in Java), and the singleton pattern—all adapted to Pythonic conventions.
Encapsulation in Python
Unlike Java, Python does not enforce access control through keywords like private or protected. Instead, it relies on naming conventions to indicate intended visibility. Attributes prefixed with a double underscore (__) undergo name mangling, making them harder—but not impossible—to access from outside the class.
class User:
def __init__(self, username):
self.__username = username # "private" by convention
def get_username(self):
return self.__username
def set_username(self, username):
if isinstance(username, str) and username.strip():
self.__username = username
else:
raise ValueError("Username must be a non-empty string")
user = User("alice")
print(user.get_username()) # Output: alice
While user._User__username could technically access the mangled attribute, doing so breaks encapsulation and is discouraged. Python encourages trust over enforcement—developers are expected to respect interface boundaries.
Runtime Introspection (Python's "Reflection")
Python provides built-in functions like hasattr(), getattr(), and setattr() for dynamic attribute access. These eliminate the need for complex reflection APIs seen in Java.
class CommandHandler:
def __init__(self, identity):
self.identity = identity
def greet(self):
print(f"Hello, {self.identity}!")
def perform_action(self):
action = input("Enter command: ")
if hasattr(self, action):
method = getattr(self, action)
method()
else:
print(f"Unknown command: {action}")
handler = CommandHandler("bob")
handler.perform_action() # User can type 'greet' at prompt
This approach enables flexible, dynamic behavior without boilerplate code. Note that setattr(obj, 'attr', value) can also dynamically assign attributes at runtime.
Singleton Pattern Implementation
The singleton pattern ensures only one instance of a class exists. In Python, this can be cleanly implemented using a class method that controls instantiation.
class DatabaseConnection:
_instance = None
@classmethod
def instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
if self._instance is not None:
raise RuntimeError("Use instance() to access the singleton")
# Initialize connection here
db1 = DatabaseConnection.instance()
db2 = DatabaseConnection.instance()
print(db1 is db2) # Output: True
This implemantation prevents direct instantiation via DatabaseConnection() after the first call, enforcing the singleton contract more strictly than the basic version.
List and Dictionary Comprehensions
While not directly related to OOP, Python’s comprehension syntax offers concise alternatives to iterative operations—similar in spirit to Java Streams but with inverted syntax.
profile = {"name": "charlie", "age": 25, "role": "developer"}
# Print only the 'name' field
[print(f"{k}: {v}") for k, v in profile.items() if k == "name"]
# Output: name: charlie
Though list comprehensions are powerful, they should be used judiciously—especially when side effects (like printing) are involved, as this deviates from functional style.