30

In Python 3.5, say I have:

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

I want to get the list ["bar", "barbar"] from the class.

I know I can do:

foo = Foo(1, 2)
foo.__dict__.keys()

Is there a way to get ["bar", "barbar"] without instantiating an object?

vwrobel
  • 1,706
  • 15
  • 25
  • 1
    Possible duplicate of [Is there any way to get class instance attributes without creating class instance?](https://stackoverflow.com/questions/30128924/is-there-any-way-to-get-class-instance-attributes-without-creating-class-instanc) – idjaw Jun 12 '17 at 13:40
  • 1
    `Foo.__init__.__code__.co_varnames` – Avihoo Mamka Jun 12 '17 at 13:41
  • 3
    @AvihooMamka Won't that only give you the arguments passed to the methods? What if you created a dynamic attribute in the `__init__` that did not depend on an argument passed to the `__init__`? – idjaw Jun 12 '17 at 13:52
  • @idjaw correct, it won't work in your case. – Avihoo Mamka Jun 12 '17 at 13:55
  • 3
    The attributes of class instances are dynamic and can change through time—so **no**, you can't. – martineau Jun 12 '17 at 13:59
  • Your edits have completely changed your question. Suggest you ask a new one with just last part that in it. – martineau Jun 12 '17 at 14:02
  • @martineau you're right. I deleted the edit for the sake of clarity. – vwrobel Jun 12 '17 at 14:04
  • @vwrobel I expanded on the suggestion of using *inspect* to get what you want. Please be warned, as indicated in my answer, that I don't suggest this being done for real-world production code. Use it exactly as intended, for investigative purposes. – idjaw Jun 12 '17 at 14:16
  • Suggestion: instead of accessing private methods like `__dict__`, use the `dir` function. – James Jun 12 '17 at 18:39

4 Answers4

28

No because the attributes are dynamic (so called instance attributes). Consider the following,

class Foo:
    def __init__( self ):
        self.bar = 1

    def twice( self ):
        self.barbar = 2

f = Foo()
print( list(f.__dict__.keys() ) )
f.twice()
print( list(f.__dict__.keys() ) )

In the first print, only f.bar was set, so that's the only attributes that's shown when printing the attribute keys. But after calling f.twice(), you create a new attribute to f and now printing it show both bar and barbar.

Lærne
  • 3,010
  • 1
  • 22
  • 33
13

Warning - The following isn't foolproof in always providing 100% correct data. If you end up having something like self.y = int(1) in your __init__, you will end up including the int in your collection of attributes, which is not a wanted result for your goals. Furthermore, if you happen to add a dynamic attribute somewhere in your code like Foo.some_attr = 'pork', then you will never see that either. Be aware of what it is that you are inspecting at what point of your code, and understand why you have and don't have those inclusions in your result. There are probably other "breakages" that will not give you the full 100% expectation of what are all the attributes associated with this class, but nonetheless, the following should give you something that you might be looking for.

However, I strongly suggest you take the advice of the other answers here and the duplicate that was flagged that explains why you can't/should not do this.

The following is a form of solution you can try to mess around with:

I will expand on the inspect answer.

However, I do question (and probably would advice against) the validity of doing something like this in production-ready code. For investigative purposes, sure, knock yourself out.

By using the inspect module as indicated already in one of the other answers, you can use the getmembers method which you can then iterate through the attributes and inspect the appropriate data you wish to investigate.

For example, you are questioning the dynamic attributes in the __init__

Therefore, we can take this example to illustrate:

from inspect import getmembers


class Foo:
    def __init__(self, x):
        self.x = x
        self.y = 1
        self.z = 'chicken'


members = getmembers(Foo)

for member in members:
    if '__init__' in member:
        print(member[1].__code__.co_names)

Your output will be a tuple:

('x', 'y', 'z')

Ultimately, as you inspect the class Foo to get its members, there are attributes you can further investigate as you iterate each member. Each member has attributes to further inspect, and so on. For this particular example, we focus on __init__ and inspect the __code__ (per documentation: The __code__ object representing the compiled function body) attribute which has an attribute called co_names which provides a tuple of members as indicated above with the output of running the code.

idjaw
  • 25,487
  • 7
  • 64
  • 83
0

Try classname.annotations.keys()

vinodhraj
  • 177
  • 1
  • 7
-3

As Lærne mentioned, attributes declared inside of functions (like __init__), are dynamic. They effectively don't exist until the __init__ function is called.

However, there is a way to do what you want.

You can create class attributes, like so:

class Foo:
    bar = None
    barbar = None

    def __init__(self, bar, barbar):
        self.bar = bar
        self.barbar = barbar

And you can access those attributes like this:

[var for var in vars(Foo).keys() if not var.startswith('__')]

Which gives this result:

['bar', 'barbar']
Remolten
  • 2,614
  • 2
  • 25
  • 29
  • 1
    Your code is checking for **class** attributes, not **instance** attributes. – martineau Jun 12 '17 at 14:13
  • @martineau That is mentioned in the answer (and the question). This would be a way for the OP to accomplish what they want to do. – Remolten Jun 12 '17 at 14:14
  • 2
    Although the title says "attributes of a class", the OP's code only shows two instance attributes being created in `__init__()` and then asks if there's a way to determine them without instantiating an object—so I believe you've misunderstood the question. – martineau Jun 12 '17 at 14:20
  • @martineau And I clearly state that cannot be done in the first paragraph of my answer. Then, I propose an alternative. There is no misunderstanding in play. – Remolten Jun 12 '17 at 14:30
  • 2
    OK, but your code ***isn't*** doing what they want, it's listing class attributes with names that just happen to coincide with those given to instance attributes created in one of the methods. Sounds like a fragile solution, at best, since the source of the information is wrong. – martineau Jun 12 '17 at 14:48
  • @martineau Assigning to `self.bar` in `__init__` will override the class attribute `bar` and make it an instance attribute. There is not name collision like you mention. After the override, the class attribute `bar` is accessible through `Foo.bar`, but not in the instance. Thus, I would argue it is not a particularly *fragile* solution. – Remolten Jun 12 '17 at 15:09
  • I understand how class and instance attribute names work wrt to each other and never said anything about collisions. I meant [fragile](http://www.dictionary.com/browse/fragile) in the usual sense since there's nothing enforcing or maintaining the correspondence your solution requires. – martineau Jun 12 '17 at 17:49
  • @martineau Fair enough. – Remolten Jun 12 '17 at 19:39