bpo-43764: Add match_args=False parameter to dataclass decorator and to make_dataclasses function. (GH-25337)

Add match_args=False parameter to dataclass decorator and to make_dataclass function.
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index ceda822..e8eb206 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -154,12 +154,17 @@
 
 # __match_args__
 #
-# |  no   |  yes  |  <--- class has __match_args__ in __dict__?
-# +=======+=======+
-# | add   |       |  <- the default
-# +=======+=======+
-# __match_args__ is always added unless the class already defines it. It is a
-# tuple of __init__ parameter names; non-init fields must be matched by keyword.
+#    +--- match_args= parameter
+#    |
+#    v    |       |       |
+#         |  no   |  yes  |  <--- class has __match_args__ in __dict__?
+# +=======+=======+=======+
+# | False |       |       |
+# +-------+-------+-------+
+# | True  | add   |       |  <- the default
+# +=======+=======+=======+
+# __match_args__ is a tuple of __init__ parameter names; non-init fields must
+# be matched by keyword.
 
 
 # Raised when an attempt is made to modify a frozen class.
@@ -830,7 +835,8 @@ def _hash_exception(cls, fields, globals):
 # version of this table.
 
 
-def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
+def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
+                   match_args):
     # Now that dicts retain insertion order, there's no reason to use
     # an ordered dict.  I am leveraging that ordering here, because
     # derived class fields overwrite base class fields, but the order
@@ -1016,8 +1022,9 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
         cls.__doc__ = (cls.__name__ +
                        str(inspect.signature(cls)).replace(' -> NoneType', ''))
 
-    if '__match_args__' not in cls.__dict__:
-        cls.__match_args__ = tuple(f.name for f in field_list if f.init)
+    if match_args:
+        _set_new_attribute(cls, '__match_args__',
+                           tuple(f.name for f in field_list if f.init))
 
     abc.update_abstractmethods(cls)
 
@@ -1025,7 +1032,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
 
 
 def dataclass(cls=None, /, *, init=True, repr=True, eq=True, order=False,
-              unsafe_hash=False, frozen=False):
+              unsafe_hash=False, frozen=False, match_args=True):
     """Returns the same class as was passed in, with dunder methods
     added based on the fields defined in the class.
 
@@ -1035,11 +1042,13 @@ def dataclass(cls=None, /, *, init=True, repr=True, eq=True, order=False,
     repr is true, a __repr__() method is added. If order is true, rich
     comparison dunder methods are added. If unsafe_hash is true, a
     __hash__() method function is added. If frozen is true, fields may
-    not be assigned to after instance creation.
+    not be assigned to after instance creation. If match_args is true,
+    the __match_args__ tuple is added.
     """
 
     def wrap(cls):
-        return _process_class(cls, init, repr, eq, order, unsafe_hash, frozen)
+        return _process_class(cls, init, repr, eq, order, unsafe_hash,
+                              frozen, match_args)
 
     # See if we're being called as @dataclass or @dataclass().
     if cls is None:
@@ -1198,7 +1207,7 @@ def _astuple_inner(obj, tuple_factory):
 
 def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True,
                    repr=True, eq=True, order=False, unsafe_hash=False,
-                   frozen=False):
+                   frozen=False, match_args=True):
     """Return a new dynamically created dataclass.
 
     The dataclass name will be 'cls_name'.  'fields' is an iterable
@@ -1259,7 +1268,8 @@ class C(Base):
     # of generic dataclassses.
     cls = types.new_class(cls_name, bases, {}, lambda ns: ns.update(namespace))
     return dataclass(cls, init=init, repr=repr, eq=eq, order=order,
-                     unsafe_hash=unsafe_hash, frozen=frozen)
+                     unsafe_hash=unsafe_hash, frozen=frozen,
+                     match_args=match_args)
 
 
 def replace(obj, /, **changes):
diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py
index 29f29e1..4beed69 100644
--- a/Lib/test/test_dataclasses.py
+++ b/Lib/test/test_dataclasses.py
@@ -3440,6 +3440,47 @@ class X:
             c: int
         self.assertEqual(X.__match_args__, ("a", "b", "c"))
 
+    def test_match_args_argument(self):
+        @dataclass(match_args=False)
+        class X:
+            a: int
+        self.assertNotIn('__match_args__', X.__dict__)
+
+        @dataclass(match_args=False)
+        class Y:
+            a: int
+            __match_args__ = ('b',)
+        self.assertEqual(Y.__match_args__, ('b',))
+
+        @dataclass(match_args=False)
+        class Z(Y):
+            z: int
+        self.assertEqual(Z.__match_args__, ('b',))
+
+        # Ensure parent dataclass __match_args__ is seen, if child class
+        # specifies match_args=False.
+        @dataclass
+        class A:
+            a: int
+            z: int
+        @dataclass(match_args=False)
+        class B(A):
+            b: int
+        self.assertEqual(B.__match_args__, ('a', 'z'))
+
+    def test_make_dataclasses(self):
+        C = make_dataclass('C', [('x', int), ('y', int)])
+        self.assertEqual(C.__match_args__, ('x', 'y'))
+
+        C = make_dataclass('C', [('x', int), ('y', int)], match_args=True)
+        self.assertEqual(C.__match_args__, ('x', 'y'))
+
+        C = make_dataclass('C', [('x', int), ('y', int)], match_args=False)
+        self.assertNotIn('__match__args__', C.__dict__)
+
+        C = make_dataclass('C', [('x', int), ('y', int)], namespace={'__match_args__': ('z',)})
+        self.assertEqual(C.__match_args__, ('z',))
+
 
 if __name__ == '__main__':
     unittest.main()