0

If I have this tree of classes:

@dataclass
class Parent():
  p: int = 42

@dataclass
class Child(Parent):
  c: int = 0

obj = Child()

From the object obj, is there an easy way to know that p was defined in Parent, and c in Child?

I am looping via __mro__ from the top, getting the list of attributes there, and going lower in the hierarchy I get the new attributes not yet defined. It works, but I wonder if there is a more pythonic way.

Edit: Context

It is about autogenerated classes, describing real world electricity networks.

Image having these 3 classes, defining a tree:

  • Equipment class defining name
  • ConductingEquipment defining resistance
  • Cable defining material.

There will of course be more equipment types and more conducting equipment types. There is one standard exchange format for this, which requires giving the fully qualified attribute name, so Equipment.name even for a Cable.

So in this admittedly weird context, this is something I do need to do.

Guillaume
  • 2,325
  • 2
  • 22
  • 40
  • 3
    What's the context - what do you do with that information? – jonrsharpe Jun 27 '23 at 07:35
  • Will it be useful... https://stackoverflow.com/questions/192109/is-there-a-built-in-function-to-print-all-the-current-properties-and-values-of-a (or) https://stackoverflow.com/questions/6886493/get-all-object-attributes-in-python – KJG Jun 27 '23 at 07:50
  • 2
    I don't think there's a "pythonic" way because this isn't something you should need to do. – Barmar Jun 27 '23 at 08:22
  • @Barmar Sadly I do need to do it (context added) but I am afraid that you are right, and there is no easier way than looping manually. – Guillaume Jun 27 '23 at 11:16

2 Answers2

0

Something like this ?

for attribute in vars(obj).keys():
    if attribute in vars(obj.__class__):
        print(f"Attribute '{attribute}' defined in {obj.__class__.__name__}")
    else:
        print(
            f"Attribute '{attribute}' inherited from {obj.__class__.__base__.__name__}"
        )

Output

Attribute 'p' inherited from Parent
Attribute 'c' defined in Child
rochard4u
  • 629
  • 3
  • 17
  • Thanks, but this does not fit my use case, which actually has multiple levels of inheritance. Then looping (probably with `__mro__ `) looks like the way to go. – Guillaume Jun 27 '23 at 11:14
0

Accessing The Annotations Dict Of An Object In Python 3.10 And Newer

Python 3.10 adds a new function to the standard library: inspect.get_annotations(). In Python versions 3.10 and newer, calling this function is the best practice for accessing the annotations dict of any object that supports annotations. This function can also “un-stringize” stringized annotations for you.

If for some reason inspect.get_annotations() isn’t viable for your use case, you may access the __annotations__ data member manually.

Best practice for this changed in Python 3.10 as well: as of Python 3.10, o.annotations is guaranteed to always work on Python functions, classes, and modules. If you’re certain the object you’re examining is one of these three specific objects, you may simply use o.annotations to get at the object’s annotations dict.

However, other types of callables–for example, callables created by functools.partial()–may not have an annotations attribute defined. When accessing the annotations of a possibly unknown object, best practice in Python versions 3.10 and newer is to call getattr() with three arguments, for example getattr(o, 'annotations', None).

Before Python 3.10, accessing annotations on a class that defines no annotations but that has a parent class with annotations would return the parent’s annotations. In Python 3.10 and newer, the child class’s annotations will be an empty dict instead.

In Python 3.9 and older, accessing the annotations dict of an object is much more complicated than in newer versions. The problem is a design flaw in these older versions of Python, specifically to do with class annotations.

Best practice for accessing the annotations dict of other objects–functions, other callables, and modules–is the same as best practice for 3.10, assuming you aren’t calling inspect.get_annotations(): you should use three-argument getattr() to access the object’s annotations attribute.

Unfortunately, this isn’t best practice for classes. The problem is that, since annotations is optional on classes, and because classes can inherit attributes from their base classes, accessing the annotations attribute of a class may inadvertently return the annotations dict of a base class.

Python

@dataclass
class Parent():
    p: int = 42

@dataclass
class Child(Parent):
    c: int = 0

class Foo(Child):
    pass

parent = Parent()
child = Child()
foo = Foo()

output 3.7


print(f"Parent object annotations: {parent.__annotations__}")
print(f"Child object annotations: {child.__annotations__}")
print(f"Foo object annotations: {foo.__annotations__}")

Parent object annotations: {'p': <class 'int'>}
Child object annotations: {'c': <class 'int'>}
Foo object annotations: {'c': <class 'int'>} # <-- Wrong :(

python 3.10

print(f"Parent object annotations: {inspect.get_annotations(type(child))}")
print(f"Child object annotations: {inspect.get_annotations(type(parent))}")
print(f"Foo object annotations: {inspect.get_annotations(type(foo))}")

Parent object annotations: {'c': <class 'int'>}
Child object annotations: {'p': <class 'int'>}
Foo object annotations: {}

Source: https://docs.python.org/3/howto/annotations.html

Kostas Nitaf
  • 428
  • 1
  • 2
  • 12
  • Thanks, but the tree needs to be known (or found out by looping through `__mro__`), which was my initial solution. – Guillaume Jun 27 '23 at 11:15
  • I found the question interesting! There must be a way :P. I will take another look and come back to you. – Kostas Nitaf Jun 27 '23 at 12:40
  • Oh, there is a way (simili-one-liner): `all_attrs={}; for parent in reversed(self.__class__.__mro__[:-1]): for f in fields(parent): if f.name not in all_attrs: do_something()` but more pythonic? If you find something, I would be very curious to hear about it. – Guillaume Jun 27 '23 at 13:22