2

Given an arbitrary class X as input, is it possible to find out if instances of X will have a __dict__?


I tried hasattr(X, '__dict__'), but that doesn't work because it checks whether the class object has a __dict__:

>>> hasattr(int, '__dict__')
True
>>> vars(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: vars() argument must have __dict__ attribute

The absence of __slots__ also isn't a guarantee that there's a __dict__:

>>> hasattr(int, '__slots__')
False
>>> vars(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: vars() argument must have __dict__ attribute

I also considered creating an instance of X with object.__new__(X) (to bypass X.__new__ and X.__init__, which could potentially have undesired side effects), but this fails for built-in types:

>>> hasattr(object.__new__(int), '__dict__')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object.__new__(int) is not safe, use int.__new__()

Is it possible to do this without calling any unknown/untrusted code (like the constructor of X)?

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
  • Just to be perfectly clear. You want to be able to predict if instance of some class `X` will contain `__dict__`? – Laszlowaty Feb 14 '18 at 14:26
  • 1
    In theory, wouldn't it be possible to create a C-level extension type that does not have `__dict__` as a pre-named attribute, but which always mutates the instance inside of `__init__` to assign something to the attribute name `__dict__` during the execution of `__init__`? If so, this would imply it's not possible to know ahead of time without inspecting source code, because the instance strictly receives `__dict__` as a side-effect of `__init__`. It could even be conditional, e.g. if `datetime.datetime.now()` is greater than 3 pm, add `__dict__`. – ely Feb 14 '18 at 14:26
  • 1
    It would also be possible for the class's `__new__()` to choose between two subclasses based on the supplied parameters, one of which uses `__slots__` and the other one having a `__dict__`, thus making the answer indeterminate. – jasonharper Feb 14 '18 at 14:29
  • @Laszlowaty That's correct, yes. – Aran-Fey Feb 14 '18 at 14:30
  • @jasonharper Subclasses of `X` are irrelevant, even if they're returned from `X`'s `__new__` method. The question is whether _direct_ instances of `X` have a `__dict__`. If it's not possible to instantiate `X`, I don't care if the output is `True` or `False`. – Aran-Fey Feb 14 '18 at 14:32
  • @ely I'm not sure if that's possible. I think the `__dict__` attribute has to exist on the class, not on the instance. Either way, it's too much of an edge case for me to care :) – Aran-Fey Feb 14 '18 at 14:45
  • 2
    @Aran-Fey Well then it could dynamically modify the class object to have `__dict__` during instance creation. I know these are corner cases, much like [here](https://stackoverflow.com/questions/46575174/if-an-object-doesnt-have-dict-does-its-class-must-have-a-slots-att). But the point is that they illustrate you cannot have a *general* way to determine the answer. Best you could do is make assumptions about certain built-in types vs. user-defined types. – ely Feb 14 '18 at 14:58

2 Answers2

1

dir() will list __dict__ in Python3, example:

>>> class MyInt(int):
...     pass
...
>>> '__dict__' in dir(int)
False
>>> '__dict__' in dir(MyInt)
True
>>>
Edwin van Mierlo
  • 2,398
  • 1
  • 10
  • 19
  • 1
    This is good enough for me, but I'm still going to be nitpicky (because I explicitly asked for a solution that doesn't execute unknown code): `dir(X)` will call `X`'s metaclass's `__dir__` method if it exists. That said, I don't mind executing a `__dir__` method - I wanted to avoid calling `X`'s constructor because constructors sometimes have side effects, but I don't think anyone would be crazy enough to have side effects in a `__dir__` method. – Aran-Fey Feb 14 '18 at 14:38
  • Isn't it calling constructor of `X`? :) – Laszlowaty Feb 14 '18 at 14:38
  • @Laszlowaty Yes, but only for demonstration purposes. Calling `'__dict__' in dir(X)` gives me my expected output, with no constructors being called. But it's true that this answer could be made a lot clearer without all the `dir` calls on instances - after all, I don't even have an `X` instance to call `dir` on, so what's the point of showing the output of `dir(instance)`? This answer would benefit a lot from a little bit of polishing. – Aran-Fey Feb 14 '18 at 14:50
  • Well I'm preparing my answer to your question, but I need some more research to do. Short answer: it's not possible. The only reason you see `__dict__` here is because of the inheritance. If you define your class in python2 as `class A: ....` you will receive `['__doc__', '__init__', '__module__']` as output of the `dir(A)`. In python3, of course, you will receive `__dict__` in the output, but still you have no idea wheter it is the built-in `dict` method, or something else. – Laszlowaty Feb 14 '18 at 15:01
  • Now that's better! – Aran-Fey Feb 14 '18 at 15:09
1

You can use inspect module to get all attributes of an instance which are not methods

>>> import inspect
>>> from operator import itemgetter

>>> b = 5
>>> inspect.getmembers(b, lambda a:not(inspect.isroutine(a)))

Will produce a long lists of all attributes of b and their small description.

I have performed some tests to see how it works, Here are my findings

>>> def get_attrs(x):
       return list(map(itemgetter(0), inspect.getmembers(x, lambda a:not(inspect.isroutine(a)))))

>>> "__dict__" in get_attrs(type(b))
>>> False

>>> l = [1,2,3]
>>> "__dict__" in get_attr(type(l))
>>> False

>>> class A:
       pass

>>> a = A()
>>> "__dict__" in get_attr(type(a))
>>> True
Sohaib Farooqi
  • 5,457
  • 4
  • 31
  • 43
  • This seems to work, but it's not nearly as elegant and fast and readable as the `dir` solution. Also, since the question states that you get a class as input, I don't see why you're calling `get_attrs` on _instances_ like `5` or `[1,2,3]` or `a`. You should be calling it on `int` and `list` and `A` instead. – Aran-Fey Feb 14 '18 at 15:51
  • @Aran-Fey yes I do agree with you. I was considering input will be an instance of class. In that case if a class overrides `__dir__` then you might not get all attributes of class, However `vars` will still work fine as far as `__dict__` is defined. But in case of input is class it doesn't matter. – Sohaib Farooqi Feb 14 '18 at 16:29