machin
machin

Reputation: 470

How to get values of all properties (except inherited ones) that belong to specific class in Python 3

How (in Python 3) to get values of all properties that belong to specific class. I need ONLY those values (properties) that defined in specific class without inherited ones.

Here is some example:

class A(object):
    def __init__(self, color):
        self._color = color

    @property
    def color(self):
        return self._color

class B(A):
    def __init__(self, color, height, width):
        super().__init__(color)
        self._height = height
        self._width = width

    @property
    def height(self):
        return self._height

    @property
    def width(self):
        return self._width

and here is a code for fetching all values (including inherited):

b_inst = B('red', 10, 20)
val = [{p: b_inst.__getattribute__(p)} for p in dir(B)
       if isinstance(getattr(B, p), property)]

print(val)

>> [{'color': 'red'}, {'height': 10}, {'width': 20}]

Now, I just want to retrieve values of properties defined ONLY in class B, i.e. height and width.

Upvotes: 0

Views: 429

Answers (2)

Santi Peñate-Vera
Santi Peñate-Vera

Reputation: 1186

Since, I'm gravitating towards this question only to find a useless answer, I worked one out:

from collections import defaultdict, deque
from typing import List, Dict


class A:
    def __init__(self):
        self.value_a1 = 1
        self.value_a2 = 2


class B(A):
    def __init__(self):
        super().__init__()
        self.value_b1 = 3
        self.value_b2 = 4


class C(A):
    def __init__(self):
        super().__init__()
        self.value_c1 = 5
        self.value_c2 = 6


class D(B, C):
    def __init__(self):
        B.__init__(self)
        C.__init__(self)
        self.value_d1 = 7
        self.value_d2 = 8


class E(D):
    def __init__(self):
        super().__init__()
        self.value_e1 = 9
        self.value_e2 = 10


def build_inheritance_graph(classes) -> Dict[object, List[object]]:
    """

    :param classes:
    :return:
    """
    inheritance_graph = {}

    # Recursive function to populate the graph for a class and its ancestors
    def add_class_and_ancestors(cls):
        if cls not in inheritance_graph:
            inheritance_graph[cls] = list(cls.__bases__)
            for parent in cls.__bases__:
                add_class_and_ancestors(parent)

    for cls in classes:
        add_class_and_ancestors(cls)

    return inheritance_graph

def topological_sort(inheritance_graph) -> List[object]:
    """

    :param inheritance_graph:
    :return:
    """
    # Calculate in-degrees for each class
    in_degree = defaultdict(int)
    for cls, parents in inheritance_graph.items():
        in_degree[cls] += 0  # Ensure each class is initialized in in_degree
        for parent in parents:
            in_degree[parent] += 0
            in_degree[cls] += 1

    # Collect classes with no incoming edges (in-degree of 0)
    queue = deque([cls for cls, degree in in_degree.items() if degree == 0])
    sorted_classes = []

    while queue:
        cls = queue.popleft()
        sorted_classes.append(cls)

        for child, parents in inheritance_graph.items():
            if cls in parents:  # Check if cls is a parent of child
                in_degree[child] -= 1
                if in_degree[child] == 0:
                    queue.append(child)

    return sorted_classes


def get_declared_attributes(cls):
    """Return instance attributes defined directly in the given class's __init__."""
    # Create a temporary instance
    instance = cls()

    # Capture all instance attributes
    all_attrs = set(vars(instance).keys()) if hasattr(instance, '__dict__') else set()

    # Check parent classes to remove inherited attributes
    for base in cls.__bases__:
        parent_instance = base()
        parent_attrs = set(vars(parent_instance).keys()) if hasattr(parent_instance, '__dict__') else set()
        all_attrs -= parent_attrs

    return all_attrs

def get_mapping(classes):
    """

    :param classes: list of class types
    :return:
    """
    graph = build_inheritance_graph(classes)

    # Get classes sorted from least to most dependency
    sorted_classes_ = topological_sort(graph)

    props_by_class = list()

    # Display the sorted classes
    print("Classes sorted by dependency (from least to most):")
    for cls_ in sorted_classes_:
        props_list = get_declared_attributes(cls_)
        parents_list = [p.__name__ for p in graph[cls_]]
        # print(cls_.__name__, "->", parents_list, ": ", props_list)

        props_by_class.append({
            "class": cls_,
            "parents": parents_list,
            "properties": list(props_list)
        })

    return props_by_class

# Build the inheritance graph
mapping = get_mapping(classes=[B, C, D, E])

for item in mapping:
    print("class:", item['class'].__name__, "\n\tparents:", item['parents'], "\n\tproperties:", item['properties'])

This prints:

class: object 
    parents: [] 
    properties: []
class: A 
    parents: ['object'] 
    properties: ['value_a2', 'value_a1']
class: B 
    parents: ['A'] 
    properties: ['value_b1', 'value_b2']
class: C 
    parents: ['A'] 
    properties: ['value_c1', 'value_c2']
class: D 
    parents: ['B', 'C'] 
    properties: ['value_d2', 'value_d1']
class: E 
    parents: ['D'] 
    properties: ['value_e1', 'value_e2']

Upvotes: 0

bruno desthuilliers
bruno desthuilliers

Reputation: 77942

Note that in Python "property" has a very specific meaning (the builtin property type). If you're only concerned about this then you just have to lookup your child class's __dict__:

val = [p.__get__(c) for k, p in type(c).__dict__.items() if isinstance(p, property)]

If you want something that works on any arbitrary attribute then what you ask for is just not possible, since Python objects (with a few exceptions) are dict-based (vs struct-based like in C++ or Java) and dynamic (any piece of code can add / remove arbitrary attributes on a per-instance basis) so there's no fix schema nor class-level definition of what attributes a given object may or not possess.

Upvotes: 1

Related Questions