Search code examples
pythonclass

dir(object) vs object.__dir__()


Here is the simple Python code: What's the difference between Case 1 and Case 2 -- why am I getting result as False in first case and True in other? Why are the ids equal in the Case 2? Also does dir(object) call object._dir__() internally? If so the return object/results of two calls should it be the same.

class Hello:
    def __init__(self):
        self.a1 = "a1"


hello = Hello()
print(hello)

# Case 1
var1 = dir(hello)
var2 = hello.__dir__()
print(id(var1), id(var2), id(var1) == id(var2))

# Case 2
print(id(dir(hello)), id(hello.__dir__()), id(dir(hello)) == id(hello.__dir__()))
print(dir(hello) == hello.__dir__())

Output

<__main__.Hello object at 0x7f320828c320>
139852862206472 139852862013960 False
139852862014024 139852862014024 True
False

Solution

  • It's just a coincidence that you're ever getting True. (Well, not a coincidence, since the implementation of CPython makes it very likely… but it's not something the language requires.)

    In case 1, you have two different dicts in var1 and var2. They're both alive at the same time, so they can't have the same id.

    In case 2, you again have two different dicts—but this time, you aren't storing them anywhere; as soon as you call id on one, you release it, which means it can get garbage collected* before you get the other one,** which means it can end up reusing the same id.***

    Notice that the docs for id say:

    This is an integer which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.

    If you actually want to test whether two expressions refer to the same object, use is, don't compare their ids.


    Your edited question also asks:

    Also does dir(object) calls object._dir__() internally?

    According to dir:

    If the object has a method named __dir__(), this method will be called and must return the list of attributes.

    And the data model section on __dir__ says:

    Called when dir() is called on the object. A sequence must be returned. dir() converts the returned sequence to a list and sorts it.

    Then you say:

    If so the return object of two calls should be the same.

    Well, it depends on what you mean by "the same". It should return an equal value (since nothing has changed), but it's not going to be the identical value, which is what you're trying to test for. (If it isn't obvious why dir gives you a new list each time, it should still be clear that it must do so from the fact that "dir() converts the returned sequence to a list and sorts it"…)


    * Because CPython uses reference counting as its primary garbage collection mechanism, "can be collected" generally means "will be collected immediately". This isn't true for most other Python implementations.

    ** If the order in which parts of your expression get evaluated isn't clear to you from reading the docs, you can try dis.dis('id(dir(hello)) == id(hello.__dir__())') to see the actual bytecodes in order.

    *** In CPython, the id is just the address of the PyObject struct that represents the object; if one PyObject gets freed and another one of the same type gets allocated immediately after, it will usually get the same address.