14

Python 3.7 introduces the dataclasses module that contains a @dataclass decorator. This decorator can generate class functions. How can I print these generated functions?

Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
OrangeTux
  • 11,142
  • 7
  • 48
  • 73
  • 2
    There's no easy way. You may be interested in [DataclassInspector](https://github.com/DamlaAltun/DataclassInspector), a project that tries to do exactly that. If you want to look at what the functions are actually doing, you can use [`dis.dis`](https://docs.python.org/3/library/dis.html#dis.dis) – Patrick Haugh Nov 19 '18 at 20:58
  • You can still inspect the generated signature: `str(inspect.signature(YourDataClass.__init__))` – x0s Nov 29 '18 at 17:16
  • I've submitted a feature request https://github.com/ericvsmith/dataclasses/issues/139 – pylang Nov 30 '18 at 20:51

2 Answers2

7

I asked the same question myself. Dataclasses part project should have the verbose option, but is not.

Found out this video, helpful. According to the video dataclasses.py is a code generator. So this should be our first idea how to get the code.


I tried this code:

from dataclasses import dataclass
import inspect
import os
from uncompyle6 import PYTHON_VERSION, deparse_code2str

@dataclass
class C:
    name: str
    value: int = 34

inspect.getmembers(C) #('__init__', <function __init__(self, name: str, value: int = 34) -> None>)

co= C.__init__.__code__ # let's find out code for the __init__ method from code object

code = deparse_code2str(
                code=co,
                version=PYTHON_VERSION,
                out=open(os.devnull, "w"))
print(code)

Will print

self.name = name
self.value = value

The code acutally uses the inspector to understand the class, and then to decompile the methods using Python decompiler.

Here are the methods discovered:

def __eq__(self, other):
    if other.__class__ is self.__class__:
        return (self.name, self.value) == (
            other.name,
            other.value,
        )
    else:
        return NotImplemented

def __init__(self, name: str, value: int = 34) -> None:
    self.name = name
    self.value = value

def __repr__(self):
    key = (id(self), _thread.get_ident())
    if key in repr_running:
        return "..."
    else:
        repr_running.add(key)
        try:
            result = user_function(self)
        finally:
            repr_running.discard(key)

        return result

There is actually a project that is doing @dataclass discovery. I installed it and it worked.

from dataclasses import dataclass
import inspect
import os
import dis
from DataclassInspector.inspector import Inspector

@dataclass
class C:
    name: str
    value: int = 34

inspected = Inspector(C)
print(inspected._generate_code())

Provided the output like this:

from dataclasses import Field, _MISSING_TYPE, _DataclassParams
class C:
    __dataclass_fields__ = {'name': "Field(name='name', type=str, default=_MISSING_TYPE, default_factory=_MISSING_TYPE, init=True, repr=True, hash=None, compare=True, metadata={}, _field_type=_FIELD)", 'value': "Field(name='value', type=int, default=34, default_factory=_MISSING_TYPE, init=True, repr=True, hash=None, compare=True, metadata={}, _field_type=_FIELD)"}
    __dataclass_params__ = _DataclassParams(init=True,repr=True,eq=True,order=False,unsafe_hash=False,frozen=False)
    name: str
    value: int = 34

    def __eq__(self, other):
        if other.__class__ is self.__class__:
            return (self.name, self.value) == (other.name, other.value)
        else:
            return NotImplemented


    __hash__ = None

    def __init__(self, name: str, value: int = 34) -> None:
        self.name = name
        self.value = value



    def __repr__(self):
        key = (
         id(self), _thread.get_ident())
        if key in repr_running:
            return '...'
        else:
            repr_running.add(key)
            try:
                result = user_function(self)
            finally:
                repr_running.discard(key)

            return result
prosti
  • 42,291
  • 14
  • 186
  • 151
4

After inspecting the dataclass implementation, the methods seems to be generated by a dataclasses._create_fn. To get the original generated code, I mocked the function as:

import dataclasses

_original_create_fn = dataclasses._create_fn

def _new_create_fn(name, args, body, **kwargs):
    args_str = ', '.join(args)
    body_str = '\n'.join('  ' + l for l in body)
    print(f'def {name}({args_str}):\n{body_str}\n')
    return _original_create_fn(name, args, body, **kwargs)

dataclasses._create_fn = _new_create_fn


# After the dataclasses as been mocked, creating new dataclass
# will display their source code
@dataclasses.dataclass
class A:
    x: int
    y: int

Which display something like:

def __init__(self, x:_type_x, y:_type_y):
  self.x=x
  self.y=y

def __repr__(self):
  return self.__class__.__qualname__ + f"(x={self.x!r}, y={self.y!r})"

def __eq__(self, other):
  if other.__class__ is self.__class__:
   return (self.x,self.y,)==(other.x,other.y,)
  return NotImplemented
Conchylicultor
  • 4,631
  • 2
  • 37
  • 40
  • This is nicely done. The bit missing from being a "complete" display is the mappings of types and defaults - your example doesn't have default values but you can see part of the type story in the initializer signature: `x:_type_x, `y:_type_y`, but without seeing what those translate to. If there were defaults, they look like `= _dflt_x`. Minor nit, I guess :) – Mats Wichmann Aug 10 '22 at 21:14