Hone logo
Hone
Problems

Implementing a Basic Protocol System in Python

In software development, defining clear contracts for how different components interact is crucial for maintainability and extensibility. Python's typing.Protocol allows us to define these contracts abstractly, enabling "duck typing" in a more structured and type-checked manner. This challenge will have you implement a simple protocol system to ensure classes adhere to a specific interface.

Problem Description

Your task is to create a Python protocol class that defines a contract for objects that can be "serialized" into a string representation. You then need to implement a function that accepts any object conforming to this protocol and serializes it.

Requirements:

  1. Define a Serializable Protocol:

    • Create a protocol class named Serializable using typing.Protocol.
    • This protocol should require any conforming class to implement a method called serialize.
    • The serialize method should take no arguments (other than self) and return a string.
  2. Implement Conforming Classes:

    • Create at least two distinct classes that implement the Serializable protocol. These classes should have different data structures but must provide the serialize method as defined by the protocol.
    • For example, one class could represent a User with name and id, and another could represent a Configuration with key-value pairs.
  3. Implement a Serialization Function:

    • Create a function named serialize_object that accepts a single argument.
    • This function should be type-hinted to accept any object that conforms to the Serializable protocol.
    • Inside the function, call the serialize method of the passed object and return the resulting string.

Expected Behavior:

  • The Serializable protocol should be correctly defined.
  • The implemented classes should pass type checking when annotated to implement Serializable (if using a type checker like MyPy).
  • The serialize_object function should correctly serialize instances of any class that adheres to the Serializable protocol.

Edge Cases to Consider:

  • What happens if a class claims to implement the protocol but doesn't have the serialize method? (A type checker should flag this).
  • What if the serialize method returns something other than a string? (A type checker should flag this).

Examples

Example 1:

from typing import Protocol

class Serializable(Protocol):
    def serialize(self) -> str:
        ...

class User:
    def __init__(self, user_id: int, username: str):
        self.user_id = user_id
        self.username = username

    def serialize(self) -> str:
        return f"User(id={self.user_id}, name='{self.username}')"

def serialize_object(obj: Serializable) -> str:
    return obj.serialize()

# Usage:
user_instance = User(101, "Alice")
print(serialize_object(user_instance))
Output:
User(id=101, name='Alice')

Explanation: The User class implements the serialize method. The serialize_object function accepts user_instance (which conforms to Serializable) and calls its serialize method, returning the formatted string.

Example 2:

from typing import Protocol, Dict

class Serializable(Protocol):
    def serialize(self) -> str:
        ...

class Configuration:
    def __init__(self, settings: Dict[str, str]):
        self.settings = settings

    def serialize(self) -> str:
        settings_str = ", ".join([f"'{k}': '{v}'" for k, v in self.settings.items()])
        return f"Configuration({{{settings_str}}})"

def serialize_object(obj: Serializable) -> str:
    return obj.serialize()

# Usage:
config_instance = Configuration({"theme": "dark", "fontSize": "14px"})
print(serialize_object(config_instance))
Output:
Configuration({'theme': 'dark', 'fontSize': '14px'})

Explanation: The Configuration class also implements the serialize method. When passed to serialize_object, its custom serialization logic is executed.

Example 3: (Illustrating non-conforming class with type checker)

from typing import Protocol

class Serializable(Protocol):
    def serialize(self) -> str:
        ...

class IncompleteClass:
    def __init__(self, data: int):
        self.data = data

# If you were to try and pass an instance of IncompleteClass to serialize_object,
# a type checker like MyPy would raise an error because IncompleteClass is missing
# the serialize method.
#
# def serialize_object(obj: Serializable) -> str:
#     return obj.serialize()
#
# incomplete_instance = IncompleteClass(42)
# print(serialize_object(incomplete_instance)) # This would error with MyPy

Explanation: IncompleteClass does not define a serialize method. While it might run without Python-level errors if you just called serialize directly (leading to an AttributeError), using typing.Protocol with a type checker would catch this structural incompatibility before runtime.

Constraints

  • You must use typing.Protocol for defining the contract.
  • The serialize method must accept no arguments other than self.
  • The serialize method must return a str.
  • The serialize_object function must be type-hinted to accept Serializable.
  • No external libraries beyond the standard typing module are allowed for the protocol definition.

Notes

  • typing.Protocol provides structural subtyping. This means that a class doesn't need to explicitly inherit from the protocol to conform to it. If it has the required methods and attributes with the correct signatures, it conforms.
  • Consider how you would represent different data types within your serialize methods to make the output meaningful.
  • Think about the advantages of using protocols over abstract base classes (ABCs) for this kind of scenario, especially in terms of flexibility and not forcing inheritance.
Loading editor...
python