You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
inspect.getsource() on sourceless dataclass raises undocumented exception #98239
fromdataclassesimportdataclassfrominspectimportgetsourcedefs= {}
exec(
"""@dataclassclass C: "The source for this class cannot be located."""",
{"dataclass": dataclass},
defs,
)
try:
getsource(defs["C"])
exceptOSError:
print("Got the documented exception.")
The output is:
$ python sourceless_dataclass.py
Traceback (most recent call last):
File "<path>/sourceless_dataclass.py", line 16, in <module>
getsource(defs["C"])
File "/usr/lib/python3.10/inspect.py", line 1147, in getsource
lines, lnum = getsourcelines(object)
File "/usr/lib/python3.10/inspect.py", line 1129, in getsourcelines
lines, lnum = findsource(object)
File "/usr/lib/python3.10/inspect.py", line 940, in findsource
file = getsourcefile(object)
File "/usr/lib/python3.10/inspect.py", line 817, in getsourcefile
filename = getfile(object)
File "/usr/lib/python3.10/inspect.py", line 786, in getfile
raise TypeError('{!r} is a built-in class'.format(object))
TypeError: <class 'C'> is a built-in class
The documentation states that OSError can be raised but does not mention TypeError.
The implementation of inspect.getsource() assumes that if a class has no __module__ attribute, it must be a built-in class, but a sourceless dataclass doesn't have a __module__ attribute either. I don't know whether this is a bug in getsource() or whether the generation of the dataclass should set __module__ to '__main__', but in any case the behavior is not as documented.
Your environment
CPython versions tested on: Python 3.10.6
Operating system and architecture: Ubuntu Linux 18.04
OSError is the documented exception if the source cannot be found, so I think that for namedtuple it is working as intended. For the namedtuple class, __module__ is set to '__main__', so for consistency it might be good for the dataclass creation to do the same.
dataclass is a decorator that works on an existing class
Classes are created according to the regular Python rules. When you exec a source code without __name__ provided, it defaults to 'builtins'. And inspect does not know how to get sources of builtins.
This happens without @dataclass as well:
frominspectimportgetsourcedefs= {}
exec(
"""class C: "The source for this class cannot be located."""",
defs,
)
print(defs["C"].__module__) # 'builtins'try:
getsource(defs["C"])
exceptOSError:
print("Got the documented exception.")
# TypeError: <class 'C'> is a built-in class
This is why you get an exception.
Fix:
exec(
"""@dataclassclass C: "The source for this class cannot be located."""",
{"dataclass": dataclass, "__name__": "__main__"}, # pass `__name__`!defs,
)
So:
I propose to leave dataclasses alone
I think we must document TypeError in inspect calls. It is explicit, it is here for quite a long time: there are no reasons to keep it in secret :)
It appears to me that this is a kind of introspection limitation for sourceless dataclasses.
There are other ways to generate a sourceless dataclass, such as the dataclasses.make_dataclass() API. However, in that case __module__ is 'types', as can be seen using the documented example:
and this time the error makes better sense, because the actually codegen is buried somewhere else, and it's not possible (to my knowledge, at least) to infer the lines & line number.
Ideally, in the case of sourceless dataclasses, one would likely want to overwrite __module__ to the call site (of make_dataclasses(), for example). Then somehow inspect should be able to make use of this knowledge and determine the appropriate source file & line number. But I am not sure if there's a robust solution to handle all corner cases, hence I think of it as a limitation.
Thank you, @sobolevn, it's nice to see this new module argument added.
But as I said above, even after __module__ is added/overwritten, inspect.getsource() would still raise (in the OP's case __module__ is builtins, my case it's a user-supplied module location, so the error type differs). It'd be cool for getsource() to point to the line where the dataclass is created:
C=make_dataclass(...)
but I don't have good suggestion for how to make it 100% robust.
I see we made the documentation change and sobolevn's #102104 helps here too. I'm not seeing a concrete additional suggestion (and maybe any such suggestion should be its own issue), so closing. Thanks all! :-)
Activity
mthuurne commentedon Oct 13, 2022
Note that
inspect.getmodule()returns thebuiltinsmodule when passed a sourceless dataclass instead of returningNone.ericvsmith commentedon Oct 15, 2022
Note there's a slightly different error with namedtuples. Note this is with 3.12.0a0:
Maybe that will help point someone in the right direction.
mthuurne commentedon Oct 16, 2022
OSErroris the documented exception if the source cannot be found, so I think that fornamedtupleit is working as intended. For thenamedtupleclass,__module__is set to'__main__', so for consistency it might be good for the dataclass creation to do the same.sobolevn commentedon Feb 8, 2023
I doubt that these two cases are the same:
namedtupleis a function that creates a new classdataclassis a decorator that works on an existing classClasses are created according to the regular Python rules. When you
execa source code without__name__provided, it defaults to'builtins'. Andinspectdoes not know how to get sources ofbuiltins.This happens without
@dataclassas well:This is why you get an exception.
Fix:
So:
dataclassesaloneTypeErrorininspectcalls. It is explicit, it is here for quite a long time: there are no reasons to keep it in secret :)I will send a PR for this.
pythongh-98239: Document that `inspect.getsource()` can raise `TypeEr…
pythongh-98239: Document that `inspect.getsource()` can raise `TypeEr…
leofang commentedon Mar 20, 2023
It appears to me that this is a kind of introspection limitation for sourceless dataclasses.
There are other ways to generate a sourceless dataclass, such as the
dataclasses.make_dataclass()API. However, in that case__module__is'types', as can be seen using the documented example:Then in this case
getsource()would raise the right error type (OSError):and this time the error makes better sense, because the actually codegen is buried somewhere else, and it's not possible (to my knowledge, at least) to infer the lines & line number.
Ideally, in the case of sourceless dataclasses, one would likely want to overwrite
__module__to the call site (ofmake_dataclasses(), for example). Then somehowinspectshould be able to make use of this knowledge and determine the appropriate source file & line number. But I am not sure if there's a robust solution to handle all corner cases, hence I think of it as a limitation.sobolevn commentedon Mar 20, 2023
make_dataclassnow supportsmoduleargument, please see #102104leofang commentedon Mar 20, 2023
Thank you, @sobolevn, it's nice to see this new
moduleargument added.But as I said above, even after
__module__is added/overwritten,inspect.getsource()would still raise (in the OP's case__module__isbuiltins, my case it's a user-supplied module location, so the error type differs). It'd be cool forgetsource()to point to the line where the dataclass is created:but I don't have good suggestion for how to make it 100% robust.
gh-98239: Document that `inspect.getsource()` can raise `TypeError` (#…
pythongh-98239: Document that `inspect.getsource()` can raise `TypeEr…
pythongh-98239: Document that `inspect.getsource()` can raise `TypeEr…
gh-98239: Document that `inspect.getsource()` can raise `TypeError` (G…
gh-98239: Document that `inspect.getsource()` can raise `TypeError` (G…
hauntsaninja commentedon Mar 25, 2023
I see we made the documentation change and sobolevn's #102104 helps here too. I'm not seeing a concrete additional suggestion (and maybe any such suggestion should be its own issue), so closing. Thanks all! :-)
pythongh-98239: Document that `inspect.getsource()` can raise `TypeEr…
pythongh-98239: Document that `inspect.getsource()` can raise `TypeEr…