pythonpython-3.xnested-attributes

Get a sum value from nested attributes, in Python


I need to code a SEARCH class, so that accessing its attributes by their nested representation(s).

Attributes and their relationship: the goal of the 'SEARCH' class is to obtain a serie of consecutive integer numbers 'n', selecting 1 or summing 2 of the predefined A, B, C, D constants, as follows:

A = 0
B = 1
C = 2
D = 4
with 'n' meeting these attributes relationship:
n = (A or B) + (None or C or D), thus getting: n = 0..5

Expected Usage: the main constraint is to apply for nested attributes for acceding to the 'n' value series.

# Expected output 'n' from the 'SEARCH' class nested attributes:
print(SEARCH.A) # Output: 0
print(SEARCH.B) # Output: 1
print(SEARCH.A.C) # Output: 2 (= A + C = 0 + 2)
print(SEARCH.A.D) # Output: 4
print(SEARCH.B.C) # Output: 3
print(SEARCH.B.D) # Output: 5
print(SEARCH.A.A) # Output: to drop an error and break
print(SEARCH.B.B) # Output: to drop an error and break

I need this 'SEARCH' class to behave somehow similarly to an enumeration (See n=0..5). This is why I tried with Enum and Flag classes, as in my previous question on this same topic. The Enum approach was wrong. I checked this nearest case question.

Code step 1: My first piece of code (after the Enum tries), with some workarounds with regards to a real nested attribute sequence (C and D underscored) ; nevertheless, this first investigation already reflects what I am aiming:

class SEARCH:
    # class attributes
    A = 0
    B = 1
    C = 2
    D = 4

    class _A:
        @staticmethod
        def _C():
            return SEARCH.A + SEARCH.C

        @staticmethod
        def _D():
            return SEARCH.A + SEARCH.D

    class _B:
        @staticmethod
        def _C():
            return SEARCH.B + SEARCH.C

        @staticmethod
        def _D():
            return SEARCH.B + SEARCH.D

    def __getattr__(self, attr):
        # Raise error for invalid nested attributes
        if attr in ['A', 'B']:
            raise AttributeError(f"Invalid nested attribute access: '{attr}'")

# Usage
try:
    # Valid cases
    print(SEARCH.A)           # Expected Output: 0
    print(SEARCH.B)           # Expected Output: 1
    print(SEARCH.C)           # Expected Output: 2
    print(SEARCH.D)           # Expected Output: 4
    print(SEARCH._A._C())       # Expected Output: 2 (0 + 2), but not using underscores
    print(SEARCH._A._D())       # Expected Output: 4 (0 + 4)
    print(SEARCH._B._C())       # Expected Output: 3 (1 + 2)
    print(SEARCH._B._D())       # Expected Output: 5 (1 + 4)

    # Invalid nested cases
    try:
        print(SEARCH.A.A())  # Should raise an error
    except AttributeError as e:
        print(f"Error: {e}")

    try:
        print(SEARCH.B.A())  # Should raise an error
    except AttributeError as e:
        print(f"Error: {e}")

except AttributeError as e:
    print(f"General Error: {e}")

Code Step 2: my current investigations consist in using the __new__ method, for creating a new instance of a class, to be called before __init__, where __new__ is used to run the initialize() class method automatically whenever the class is used. I am still struggling with a few errors, since the type object 'A' has no attribute 'value'. As follows:

class SEARCH:
    A_value = 0
    B_value = 1
    C_value = 2
    D_value = 4

    class A:
        pass

    class B:
        pass

    def __new__(cls, *args, **kwargs):
        cls.initialize()  # Automatically initialize when the class is loaded
        return super(SEARCH, cls).__new__(cls)

    @classmethod
    def initialize(cls):
        # Dynamically set the nested class values after SEARCH is fully defined
        cls.A.value = cls.A_value
        cls.A.C = type('C', (), {'value': cls.A_value + cls.C_value})
        cls.A.D = type('D', (), {'value': cls.A_value + cls.D_value})

        cls.B.value = cls.B_value
        cls.B.C = type('C', (), {'value': cls.B_value + cls.C_value})
        cls.B.D = type('D', (), {'value': cls.B_value + cls.D_value})

# Testing
try:
    # Valid cases
    print(SEARCH.A.value)        # Expected Output: 0
    print(SEARCH.B.value)        # Expected Output: 1
    print(SEARCH.A.C.value)      # Expected Output: 2 (0 + 2)
    print(SEARCH.A.D.value)      # Expected Output: 4 (0 + 4)
    print(SEARCH.B.C.value)      # Expected Output: 3 (1 + 2)
    print(SEARCH.B.D.value)      # Expected Output: 5 (1 + 4)

    # Invalid nested cases
    try:
        print(SEARCH.A.A)  # Should raise an error
    except AttributeError as e:
        print(f"Error: {e}")

    try:
        print(SEARCH.B.A)  # Should raise an error
    except AttributeError as e:
        print(f"Error: {e}")

except AttributeError as e:
    print(f"General Error: {e}")

I could not find yet what I'm doing wrong .

Any hints or direction about: how to get a sum value from nested attributes in Python-3.x ?


Solution

  • I found a better way applying for metaclass, also more readable as follows :

    class SEARCHMeta(type):
        def __init__(cls, name, bases, dct):
            super().__init__(name, bases, dct)
            # Dynamically set the nested class values after SEARCH is fully defined
            if name == "SEARCH":
                cls.A.value = cls.A_value
                cls.A.C = type('C', (), {'value': cls.A_value + cls.C_value})
                cls.A.D = type('D', (), {'value': cls.A_value + cls.D_value})
    
                cls.B.value = cls.B_value
                cls.B.C = type('C', (), {'value': cls.B_value + cls.C_value})
                cls.B.D = type('D', (), {'value': cls.B_value + cls.D_value})
    
    class SEARCH(metaclass=SEARCHMeta):
        A_value = 0
        B_value = 1
        C_value = 2
        D_value = 4
    
        class A:
            pass
    
        class B:
            pass
    
    # Usage
    try:
        # Valid cases
        print(SEARCH.A.value)        # Expected Output: 0
        print(SEARCH.B.value)        # Expected Output: 1
        print(SEARCH.A.C.value)      # Expected Output: 2 (0 + 2)
        print(SEARCH.A.D.value)      # Expected Output: 4 (0 + 4)
        print(SEARCH.B.C.value)      # Expected Output: 3 (1 + 2)
        print(SEARCH.B.D.value)      # Expected Output: 5 (1 + 4)
    
        # Invalid nested cases
        try:
            print(SEARCH.A.A)  # Should raise an error
        except AttributeError as e:
            print(f"Error: {e}")
    
        try:
            print(SEARCH.B.A)  # Should raise an error
        except AttributeError as e:
            print(f"Error: {e}")
    
    except AttributeError as e:
        print(f"General Error: {e}")