Search code examples
pythonpython-3.xpython-dataclasses

When making a dataclass, how can I use a custom type for the field objects themselves?


I'm writing a library that works a lot like dataclass but with some additional functionality. Obviously I want to reuse dataclass as much as possible, but for my added functionality, I'd like to extend dataclass.Field. That is, I'd like to define:

class MyField(dataclasses.Field):
  ... # additional functionality

And then do something so that when the user does:

@mydataclass
class UserDefinedClass:
  foo: int
  y: Bar

(Where mydataclass is a decorator that eventually calls dataclass...)

The actual field objects inside UserDefinedClass will be of type MyField rather than dataclasses.Field.

I can't find anything in the dataclass docs that allows me to do this, but I think I might be missing something.

(I am aware of the metadata keyword argument to field, and I will probably use that if the answer to this question is "that's impossible". But it'll be more work.)


Solution

  • It certainly doesn't exactly look as though dataclasses.Field is supposed to be subclassed, and convincing the dataclass decorator to use a custom field is, well, a bit grotesque, but this works – even if I'd advise against this.

    It would certainly be nicer if you could tell dataclass() to use a custom field class, but alas...

    import dataclasses
    from unittest import mock
    
    
    def mydataclass(**kwargs):
        def deco(cls):
            with mock.patch("dataclasses.Field", MyField):  # NB: not thread-safe
                return dataclasses.dataclass(**kwargs)(cls)
    
        return deco
    
    
    class MyField(dataclasses.Field):
        def honk(self):
            print(
                f"You hear {self.name!r} the {self.type.__name__} emitting a loud HONK!"
            )
    
    
    @mydataclass()
    class MyData:
        a: int
        b: str
    
    
    d = MyData(1, "hello")
    for f in dataclasses.fields(d):
        f.honk()
    

    This prints out

    You hear 'a' the int emitting a loud HONK!
    You hear 'b' the str emitting a loud HONK!
    

    as you might imagine.