Fundamentals 8 min read

Using Single and Double Underscores in Python for Internal Methods, Attributes, and Special Dunder Methods

The article explains how to use single and double underscores in Python to define internal methods, attributes, and special dunder methods such as __init__, __str__, __bool__, __new__, __format__, __eq__, __del__, __copy__, and __deepcopy__, providing usage scenarios and code examples for each.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Using Single and Double Underscores in Python for Internal Methods, Attributes, and Special Dunder Methods

This guide demonstrates best practices for employing single and double underscores in Python class design, covering internal methods, internal attributes, and a range of special (dunder) methods.

Single underscore for internal methods : An internal method _handle_response processes HTTP responses within a client class, ensuring it is only called from inside the class.

import requests
class HttpClient:
    def __init__(self, base_url):
        self.base_url = base_url
    def _handle_response(self, response):
        if response.status_code != 200:
            raise Exception(f"Request failed with status {response.status_code}")
        return response.json()
    def get(self, endpoint):
        url = f"{self.base_url}/{endpoint}"
        response = requests.get(url)
        return self._handle_response(response)
client = HttpClient("https://api.example.com")
data = client.get("/resources")
print(data)

Double underscore __init__ for initialization : The __init__ method sets up essential configuration such as base URLs and authentication tokens.

import requests
class TestAPI:
    def __init__(self, base_url, auth_token):
        self.base_url = base_url
        self.auth_token = auth_token
    def test_get_resources(self):
        headers = {'Authorization': f'Bearer {self.auth_token}'}
        response = requests.get(f"{self.base_url}/resources", headers=headers)
        assert response.status_code == 200

test_api = TestAPI("https://api.example.com", "my-token")
test_api.test_get_resources()

Double underscore __str__ for custom string representation : Provides a readable output for test result objects.

class TestResult:
    def __init__(self, passed, message):
        self.passed = passed
        self.message = message
    def __str__(self):
        status = "Passed" if self.passed else "Failed"
        return f"{status}: {self.message}"
result = TestResult(True, "All tests passed")
print(result)

Double underscore __bool__ for boolean conversion : Allows instances to be used directly in boolean contexts.

class TestResult:
    def __init__(self, passed, message):
        self.passed = passed
        self.message = message
    def __bool__(self):
        return self.passed
result = TestResult(True, "All tests passed")
assert bool(result), "Test should pass."

Double underscore __new__ for controlling instantiation (singleton pattern) :

class SingletonTestFramework:
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(SingletonTestFramework, cls).__new__(cls)
        return cls._instance
framework = SingletonTestFramework()
another_framework = SingletonTestFramework()
assert framework is another_framework

Double underscore __format__ for custom formatting :

class TestReport:
    def __init__(self, name, result):
        self.name = name
        self.result = result
    def __format__(self, format_spec):
        if format_spec == "short":
            return f"{self.name}: {self.result}"
        return f"{self.name}: {self.result} (full report)"
report = TestReport("Test Name", "Passed")
print(f"{report:short}")

Double underscore __eq__ for equality comparison :

class TestResult:
    def __init__(self, passed, message):
        self.passed = passed
        self.message = message
    def __eq__(self, other):
        if isinstance(other, TestResult):
            return self.passed == other.passed and self.message == other.message
        return False
result1 = TestResult(True, "All tests passed")
result2 = TestResult(True, "All tests passed")
assert result1 == result2

Double underscore __del__ for resource cleanup :

import requests
class TestFramework:
    def __init__(self, session):
        self.session = session
    def __del__(self):
        self.session.close()
session = requests.Session()
framework = TestFramework(session)
del framework

Double underscore __copy__ and __deepcopy__ for custom copy behavior :

import copy
class TestConfig:
    def __init__(self, name, settings):
        self.name = name
        self.settings = settings
    def __copy__(self):
        return TestConfig(self.name, self.settings.copy())
    def __deepcopy__(self, memo):
        return TestConfig(self.name, copy.deepcopy(self.settings, memo))
config = TestConfig("Test Config", {"key": "value"})
shallow_copy = copy.copy(config)
deep_copy = copy.deepcopy(config)
config.settings["key"] = "changed"
assert config.settings["key"] == "changed"
assert shallow_copy.settings["key"] == "changed"
assert deep_copy.settings["key"] == "value"

Each section includes usage notes emphasizing that internal methods and attributes should not be accessed from outside the class, that special methods must return appropriate types, and that edge cases such as authentication failures or resource‑release errors should be handled carefully.

PythonBackend Developmentfundamentalsunderscoreclass-designDunder Methods
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.