15

I've been using python 3.7 lately and was looking for ways to leverage the new dataclasses. Basically I had a method that iterates over the dataclass fields and checks if they have a default value:

from dataclasses import fields, MISSING

@classmethod
def from_json(cls)
    datacls_fields = fields(cls)
    for field in datacls_fields:
        if  (field.default != MISSING):
            #...

However in the official documentation, it says:

MISSING value is a sentinel object used to detect if the default and default_factory parameters are provided. This sentinel is used because None is a valid value for default. No code should directly use the MISSING value.

Anyone knows a better/more pythonic way to do it?

addonis1990
  • 489
  • 1
  • 6
  • 17
  • Perhaps the pythonic question could be: why do you need to figure out fields with default value? – mehdix Dec 03 '18 at 11:41
  • 13
    I think the warning against using `MISSING` is just saying that it shouldn't be a meaningful value for your code. If one of your dataclasses had a field with a default value of `MISSING`, for example, that could cause all sorts of weird behaviour. Using it in checks like this won't cause any problems. – Patrick Haugh Dec 03 '18 at 15:57
  • 4
    I think, as @PatrickHaugh says, that your code is correct and not really doing anything dangerous. If anything, to make this more pythonic, I would use `is not MISSING` and drop the parentheses around the condition. – Jacobo de Vera Jul 23 '19 at 11:16

2 Answers2

5

This is the definition of MISSING in python source code in dataclasses.py:

# A sentinel object to detect if a parameter is supplied or not.  Use
# a class to give it a better repr.
class _MISSING_TYPE:
    pass
MISSING = _MISSING_TYPE()

The definition is pretty clear, its use case is only to check if a parameter has been supplied or not, and make a distinction between a value of None and an unsupplied value :

def my_func(a=MISSING):
    if a is not MISSING:
        # a value has been supplied, whatever his value

So it is perfectly ok to use it in your code for value comparison. By telling No code should directly use the MISSING value they just warn us that this variable has no specific usage (other that for comparison) and should not be used in the code to avoid unexpected behavior.

You should update your code to use a more pythonic syntax is not MISSING :

from dataclasses import fields, MISSING

@classmethod
def from_json(cls)
    datacls_fields = fields(cls)
    for field in datacls_fields:
        if field.default is not MISSING:

Charlesthk
  • 9,394
  • 5
  • 43
  • 45
1

I think the pythonic solution relies on using the magic __dict__ (documentation: https://docs.python.org/3/library/stdtypes.html#object.__dict__).

class Foo:
def __init__(self, a=1, b=2, c=3):
    self.a = a
    self.b = b
    self.c = c

x = Foo()
y = Foo(a=5)
print(x.__dict__ == Foo().__dict__)  # True
print(y.__dict__ == Foo().__dict__)  # False

Basically, when used on an object instance, it returns a dictionary of field-value pairs (key-value).

In order to check whether these are default, I simply compare this dictionary on a given instance with the same dictionary created on a default nameless object.

The code might be more explanatory.

P.S. This is the answer to whether all fields have default values. In case that you need the information of exactly which fields do not have default values, this is my proposed solution (based on the same logic as above, but instead of comparing the dictionaries for equality, we find the difference between them):

# Need the actual fields back?
x_dict = x.__dict__
y_dict = y.__dict__
default = Foo().__dict__
print(dict(set(x_dict.items()) - set(default.items())))
print(dict(set(y_dict.items()) - set(default.items())))

Output when using this code:

{}
{'a': 5}

See How to get the difference between two dictionaries in Python? for more info on how to find a difference between two dictionaries.

waykiki
  • 914
  • 2
  • 9
  • 19