Inspect python class attributes

I need a way to check the class, so I can safely identify which attributes are user-defined class attributes. The problem is that functions such as dir (), inspect.getmembers () and friends return all class attributes, including predefined ones: __class__ , __doc__ , __dict__ , __hash__ . This, of course, is understandable, and it can be argued that I could just list the list of named members to ignore, but unfortunately these predefined attributes can change with different versions of Python, which makes my project vulnerable to changes in the python project - and I do not like it.

Example:

 >>> class A: ... a=10 ... b=20 ... def __init__(self): ... self.c=30 >>> dir(A) ['__doc__', '__init__', '__module__', 'a', 'b'] >>> get_user_attributes(A) ['a','b'] 

In the above example, I want a safe way to get only user-defined class attributes ['a', 'b'] not 'c', since this is an instance attribute. So my question is: can someone help me with the aforementioned dummy function get_user_attributes(cls) ?

PS I spent some time trying to solve the problem by parsing a class at the AST level, which would be very easy. But I can not find a way to convert already processed objects to the AST node tree. I think all AST information is discarded once the class has been compiled into bytecode.

Best regards, Jacob

+27
python class attributes introspection
Nov 22 '10 at 0:00
source share
5 answers

Below is the hard way. Here is an easy way. I don’t know why this has not happened before.

 import inspect def get_user_attributes(cls): boring = dir(type('dummy', (object,), {})) return [item for item in inspect.getmembers(cls) if item[0] not in boring] 



Here begins

 def get_user_attributes(cls): boring = dir(type('dummy', (object,), {})) attrs = {} bases = reversed(inspect.getmro(cls)) for base in bases: if hasattr(base, '__dict__'): attrs.update(base.__dict__) elif hasattr(base, '__slots__'): if hasattr(base, base.__slots__[0]): # We're dealing with a non-string sequence or one char string for item in base.__slots__: attrs[item] = getattr(base, item) else: # We're dealing with a single identifier as a string attrs[base.__slots__] = getattr(base, base.__slots__) for key in boring: del attrs['key'] # we can be sure it will be present so no need to guard this return attrs 

It should be reliable enough. Essentially, it works if you ignore the attributes that are in the default subclass of object . Then it gets the mro of the class that went to it and traverses it in reverse so that the keys of the subclass can overwrite the keys of the superclass. It returns a dictionary of key-value pairs. If you need a list of keys, tuples of values, as in inspect.getmembers , then just return either attrs.items() or list(attrs.items()) in Python 3.

If you really don't want to navigate mro and just want the attributes defined directly in the subclass, then this is simpler:

 def get_user_attributes(cls): boring = dir(type('dummy', (object,), {})) if hasattr(cls, '__dict__'): attrs = cls.__dict__.copy() elif hasattr(cls, '__slots__'): if hasattr(base, base.__slots__[0]): # We're dealing with a non-string sequence or one char string for item in base.__slots__: attrs[item] = getattr(base, item) else: # We're dealing with a single identifier as a string attrs[base.__slots__] = getattr(base, base.__slots__) for key in boring: del attrs['key'] # we can be sure it will be present so no need to guard this return attrs 
+27
Nov 22 '10 at 0:13
source share

Double underscores at both ends of the "special attributes" were part of python prior to 2.0. It would be unlikely that they would change this at any time in the near future.

 class Foo(object): a = 1 b = 2 def get_attrs(klass): return [k for k in klass.__dict__.keys() if not k.startswith('__') and not k.endswith('__')] print get_attrs(Foo) 

['a', 'b']

+6
Nov 22 2018-10-10T00:
source share

Thanks aaronasterling, you gave me the expression I need :-) My last class attribute check function looks like this:

 def get_user_attributes(cls,exclude_methods=True): base_attrs = dir(type('dummy', (object,), {})) this_cls_attrs = dir(cls) res = [] for attr in this_cls_attrs: if base_attrs.count(attr) or (callable(getattr(cls,attr)) and exclude_methods): continue res += [attr] return res 

Return only attribute attributes of an attribute class attribute (exclude_methods = True) or also get methods. My initial tests of this function support both old and new python classes.

/ Jacob

+3
Nov 22 2018-10-22T00:
source share

If you are using new style classes, can you just subtract the attributes of the parent class?

 class A(object): a = 10 b = 20 #... def get_attrs(Foo): return [k for k in dir(Foo) if k not in dir(super(Foo))] 

Edit: Not really. __dict__ , __module__ and __weakref__ appear when inheriting from the object, but are not in the object itself. This may be a special case - I doubt that they will change very often.

+2
Nov 22 '10 at 0:15
source share

I apologize for the nekro-stumbling thread. I am surprised that there is still no simple function (or library) to handle such widespread use since 2019.

I would like to thank aaronasterling for this idea. In fact, set container provides an easier way to express this:

 class dummy: pass def abridged_set_of_user_attributes(obj): return set(dir(obj))-set(dir(dummy)) def abridged_list_of_user_attributes(obj): return list(abridged_set_of_user_attributes(obj)) 

The original solution, using list comprehension, is actually two-level loops, because there are two components to the keyword, even though only one for keyword made it look less time-consuming than it is.

+1
Apr 28 '19 at 6:40
source share



All Articles