Question

Concretely, I have a user-defined class of type

class Foo(object):
  def __init__(self, bar):
    self.bar = bar

  def bind(self):
    val = self.bar
    do_something(val)

I need to:

1) be able to call on the class (not an instance of the class) to recover all the self.xxx attributes defined within the class.

For an instance of a class, this can be done by doing a f = Foo('') and then f.__dict__. Is there a way of doing it for a class, and not an instance? If yes, how? I would expect Foo.__dict__ to return {'bar': None} but it doesn't work this way.

2) be able to access all the self.xxx parameters called from a particular function of a class. For instance I would like to do Foo.bind.__selfparams__ and recieve in return ['bar']. Is there a way of doing this?

Was it helpful?

Solution

This is something that is quite hard to do in a dynamic language, assuming I understand correctly what you're trying to do. Essentially this means going over all the instances in existence for the class and then collecting all the set attributes on those instances. While not infeasible, I would question the practicality of such approach both from a design as well as performance points of view.

More specifically, you're talking of "all the self.xxx attributes defined within the class"—but these things are not defined at all, not at least in a single place—they more like "evolve" as more and more instances of the class are brought to life. Now, I'm not saying all your instances are setting different attributes, but they might, and in order to have a reliable generic solution, you'd literally have to keep track of anything the instances might have done to themselves. So unless you have a static analysis approach in mind, I don't see a clean and efficient way of achieving it (and actually even static analysis is of no help generally speaking in a dynamic language).

A trivial example to prove my point:

class Foo(object):
    def __init__(self):
        # statically analysable
        self.bla = 3

        # still, but more difficult
        if SOME_CONSTANT > 123:
            self.x = 123
        else:
            self.y = 321

    def do_something(self):
        import random
        setattr(self, "attr%s" % random.randint(1, 100), "hello, world of dynamic languages!")

    foo = Foo()
    foo2 = Foo()
    # only `bla`, `x`, and `y` attrs in existence so far
    foo2.do_something()
    # now there's an attribute with a random name out there
    # in order to detect it, we'd have to get all instances of Foo existence at the moment, and individually inspect every attribute on them.

And, even if you were to iterate all instances in existence, you'd only be getting a snapshot of what you're interested, not all possible attributes.

OTHER TIPS

  1. This is not possible. The class doesn't have those attributes, just functions that set them. Ergo, there is nothing to retrieve and this is impossible.

  2. This is only possible with deep AST inspection. Foo.bar.func_code would normally have the attributes you want under co_freevars but you're looking up the attributes on self, so they are not free variables. You would have to decompile the bytecode from func_code.co_code to AST and then walk said AST.

This is a bad idea. Whatever you're doing, find a different way of doing it.

To do this, you need some way to find all the instances of your class. One way to do this is just to have the class itself keep track of its instances. Unfortunately, keeping a reference to every instance in the class means that those instances can never be garbage-collected. Fortunately, Python has weakref, which will keep a reference to an object but does not count as a reference to Python's memory management, so the instances can be garbage-collected as per usual.

A good place to update the list of instances is in your __init__() method. You could also do it in __new__() if you find the separation of concerns a little cleaner.

import weakref

class Foo(object):

    _instances = []

    def __init__(self, value):
        self.value = value

        cls = type(self)
        type(self)._instances.append(weakref.ref(self, 
                                                 type(self)._instances.remove))

    @classmethod
    def iterinstances(cls):
        "Returns an iterator over all instances of the class."
        return (ref() for ref in cls._instances)

    @classmethod
    def iterattrs(cls, attr, default=None):
        "Returns an iterator over a named attribute of all instances of the class."
        return (getattr(ref(), attr, default) for ref in cls._instances)

Now you can do this:

f1, f2, f3 = Foo(1), Foo(2), Foo(3)

for v in Foo.iterattrs("value"):
    print v,   # prints 1 2 3

I am, for the record, with those who think this is generally a bad idea and/or not really what you want. In particular, instances may live longer than you expect depending on where you pass them and what that code does with them, so you may not always have the instances you think you have. (Some of this may even happen implicitly.) It is generally better to be explicit about this: rather than having the various instances of your class be stored in random variables all over your code (and libraries), have their primary repository be a list or other container, and access them from there. Then you can easily iterate over them and get whatever attributes you want. However, there may be use cases for something like this and it's possible to code it up, so I did.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top