3

Goal (in Python 2.7):

Inspecting an arbitrary object, find all of the instance variables. But exclude class variables.

Ultimate goal:

Print useful details of an object, from a third-party class library that doesn't provide a useful "str" implementation. (Maya's Python API, version 1, which is a simple SWIG wrapper. not using version 2, because I'm learning from some version 1 examples.)

Example class:

# ---------- class Vector ----------
class Vector(object):
    def __init__(self, x=0.0, y=0.0, z=0.0):
        self.x, self.y, self.z = x, y, z
    # Provide useful info for 'repr(self)', 'str(self)', and 'print self'.
    def __repr__(self):
        return 'Vector({0}, {1}, {2})'.format(self.x, self.y, self.z)
    # math operators
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
    # a simple method
    def ApproximateLength(self):
        return self.x + self.y + self.z
    # list/sequence/iterator support.
    def tolist(self):
        return [self.x, self.y, self.z]
    def __len__(self):
        return 3
        # No need for "next(self)", because we create a list, use its iterator.
    def __iter__(self):
        return iter(self.tolist())
# class variable
Vector.Zero = Vector()

Solution so far:

import inspect
import types
def printElements(ob):
    for x in ob: print x
# Excludes 'internal' names (start with '__').
def Public(name):
    return not name.startswith('__')
def Attributes(ob):
    # Exclude methods.
    attributes = inspect.getmembers(ob, lambda member: not inspect.ismethod(member))
    # Exclude 'internal' names.
    publicAttributes = filter(lambda desc: Public(desc[0]), attributes)
    return publicAttributes

Example usage:

vec = Vector(1.0, 2.0, 3.0)
printElements(Attributes(vec))   

Output:

('Zero', Vector(0.0, 0.0, 0.0))
('x', 1.0)
('y', 2.0)
('z', 3.0)

This class does print itself well:

print vec

=>

Vector(1.0, 2.0, 3.0)

The goal is to extract similar information, for classes that I don't have source to (or don't want to modify the source of). Those classes have many class variables, which bury the information I seek.

Question:

How detect that 'Zero' is a "class variable", inherited from Vector, to eliminate it from the output?

Clumsy approach I will use if no better way:

printElements(Attributes(type(vec)))

lists the attributes on the object's type. Could test each attribute of "vec" against the attributes of "type(vec)", excluding any that match. I don't care about the subtle possibility that the same named attribute exists on both class and instance. So this would satisfy my requirements.

However, that seems clumsy. Is there a more direct way to determine whether the attribute is inherited from the class?


EDIT: Incorporating Joran's answer:

def IsClassVar(self, attrName):
    return hasattr(self.__class__, attrName)
def Attributes(ob):
    ....
    publicAttributes = filter(lambda desc: Public(desc[0]), attributes)
    # Exclude 'class' variables.
    # NOTE: This does not attempt to detect whether the instance variable is different than the class variable.
    publicAttributes = filter(lambda desc: not isClassVar(ob, desc[0]), publicAttributes)
    return publicAttributes

This gives the desired result:

printElements(Attributes(vec))   

=>

('x', 1.0)
('y', 2.0)
('z', 3.0)

Alternative, To detect instance variable overriding class variable:

def IsClassVar(self, attrName):
    return hasattr(self.__class__, attrName)
# REQUIRE attrName already known to be supported by self.
# But just in case, return False if exception, so will be skipped.
def IsNotSameAsClassVar(self, attrName):
    try:
        if not IsClassVar(self, attrName):
            return True
        # If it has different value than class' attribute, it is on the instance.
        return getattr(self, attrName) is not getattr(self.__class__, attrName)
    except:
        return False
def Attributes(ob):
    ....
    publicAttributes = filter(lambda desc: Public(desc[0]), attributes)
    # Exclude 'class' variables.
    # More complete solution.
    publicAttributes = filter(lambda desc: IsNotSameAsClassVar(ob, desc[0]), publicAttributes)
    return publicAttributes

Now if we override 'Zero' on vec, it gets included:

# Probably a bad idea, but showing the principle.
vec.Zero = "Surprise!"

Then:

print vec.Zero
print Vector.Zero

=>

Surprise!
Vector(0.0, 0.0, 0.0)

And:

printElements(Attributes(vec))   

=>

('Zero', 'Surprise!')
('x', 1.0)
('y', 2.0)
('z', 3.0)
6
  • What about properties (and other custom descriptors), or things that derive dynamically from custom __getattr__/__setattr__/etc? Commented Nov 25, 2013 at 19:04
  • For my current usage, I'm not concerned about those dynamic possibilities: it is okay for a solution to either include or exclude those. Commented Nov 25, 2013 at 19:07
  • What about attributes that are both? Commented Nov 25, 2013 at 19:18
  • @Bakuriu: I am happy with any solution, regardless of how it handles subtle/advanced situations. On the other hand, it would be great to see alternative solutions, and an explanation of when those alternatives are useful. Commented Nov 25, 2013 at 19:55
  • @ToolmakerSteve ... you probably want return getattr(self, attrName) != getattr(self.__class__, attrName) ... using is checks for identity which is not what you actually want to check I dont think whereas != checks for value , however if you actually want to check identity you can use return getattr(self, attrName) is not getattr(self.__class__, attrName) which reads slightly better Commented Nov 25, 2013 at 20:24

1 Answer 1

6

something like this may work

def isClassVar(self,varname):
        return hasattr(self.__class__,varname)
...
vec.isClassVar("Zero")

note that this does not necessarily mean it is an instance variable ... just that is is not a class variable

Sign up to request clarification or add additional context in comments.

1 Comment

Yes, that is what I am looking for. Will accept this answer in a few minutes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.