@@ -757,22 +757,16 @@ def _is_kw_only(a_type, dataclasses):
757757 return a_type is dataclasses .KW_ONLY
758758
759759
760- def _is_type (annotation , cls , a_module , a_type , is_type_predicate ):
761- # Given a type annotation string, does it refer to a_type in
762- # a_module? For example, when checking that annotation denotes a
763- # ClassVar, then a_module is typing, and a_type is
764- # typing.ClassVar.
760+ def _get_type_from_annotation (annotation , cls ):
761+ # Loosely parse a string annotation and return its type.
765762
766- # It's possible to look up a_module given a_type, but it involves
767- # looking in sys.modules (again!), and seems like a waste since
768- # the caller already knows a_module.
763+ # We can't perform a full type hint evaluation at the point where @dataclass
764+ # was invoked because class's module is not fully initialized yet. So we resort
765+ # to parsing string annotation using regexp, and extracting a type before
766+ # the first square bracket.
769767
770768 # - annotation is a string type annotation
771769 # - cls is the class that this annotation was found in
772- # - a_module is the module we want to match
773- # - a_type is the type in that module we want to match
774- # - is_type_predicate is a function called with (obj, a_module)
775- # that determines if obj is of the desired type.
776770
777771 # Since this test does not do a local namespace lookup (and
778772 # instead only a module (global) lookup), there are some things it
@@ -803,24 +797,21 @@ def _is_type(annotation, cls, a_module, a_type, is_type_predicate):
803797 # https://github.com/python/cpython/issues/77634 for details.
804798 global _MODULE_IDENTIFIER_RE
805799 if _MODULE_IDENTIFIER_RE is None :
806- _MODULE_IDENTIFIER_RE = re .compile (r'(?: \s*(\w+) \s*\.)? \s*( \w+)' )
800+ _MODULE_IDENTIFIER_RE = re .compile (r'^ \s*(\w+(?: \s*\.\s*\w+)* )' )
807801
808802 match = _MODULE_IDENTIFIER_RE .prefixmatch (annotation )
809- if match :
810- ns = None
811- module_name = match [1 ]
812- if not module_name :
813- # No module name, assume the class's module did
814- # "from dataclasses import InitVar".
815- ns = sys .modules .get (cls .__module__ ).__dict__
816- else :
817- # Look up module_name in the class's module.
818- module = sys .modules .get (cls .__module__ )
819- if module and module .__dict__ .get (module_name ) is a_module :
820- ns = sys .modules .get (a_type .__module__ ).__dict__
821- if ns and is_type_predicate (ns .get (match [2 ]), a_module ):
822- return True
823- return False
803+ if not match :
804+ return None
805+
806+ # Note: _MODULE_IDENTIFIER_RE guarantees that path is non-empty
807+ path = match [1 ].split ("." )
808+ root = sys .modules .get (cls .__module__ )
809+ for path_item in path :
810+ root = getattr (root , path_item .strip (), None )
811+ if root is None :
812+ return None
813+
814+ return root
824815
825816
826817def _get_field (cls , a_name , a_type , default_kw_only ):
@@ -858,17 +849,18 @@ def _get_field(cls, a_name, a_type, default_kw_only):
858849 # is actually of the correct type.
859850
860851 # For the complete discussion, see https://bugs.python.org/issue33453
852+ if isinstance (a_type , str ):
853+ a_type_annotation = _get_type_from_annotation (a_type , cls )
854+ else :
855+ a_type_annotation = a_type
861856
862857 # If typing has not been imported, then it's impossible for any
863858 # annotation to be a ClassVar. So, only look for ClassVar if
864859 # typing has been imported by any module (not necessarily cls's
865860 # module).
866861 typing = sys .modules .get ('typing' )
867862 if typing :
868- if (_is_classvar (a_type , typing )
869- or (isinstance (f .type , str )
870- and _is_type (f .type , cls , typing , typing .ClassVar ,
871- _is_classvar ))):
863+ if _is_classvar (a_type_annotation , typing ):
872864 f ._field_type = _FIELD_CLASSVAR
873865
874866 # If the type is InitVar, or if it's a matching string annotation,
@@ -877,10 +869,7 @@ def _get_field(cls, a_name, a_type, default_kw_only):
877869 # The module we're checking against is the module we're
878870 # currently in (dataclasses.py).
879871 dataclasses = sys .modules [__name__ ]
880- if (_is_initvar (a_type , dataclasses )
881- or (isinstance (f .type , str )
882- and _is_type (f .type , cls , dataclasses , dataclasses .InitVar ,
883- _is_initvar ))):
872+ if _is_initvar (a_type_annotation , dataclasses ):
884873 f ._field_type = _FIELD_INITVAR
885874
886875 # Validations for individual fields. This is delayed until now,
@@ -1073,10 +1062,11 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
10731062 dataclasses = sys .modules [__name__ ]
10741063 for name , type in cls_annotations .items ():
10751064 # See if this is a marker to change the value of kw_only.
1076- if (_is_kw_only (type , dataclasses )
1077- or (isinstance (type , str )
1078- and _is_type (type , cls , dataclasses , dataclasses .KW_ONLY ,
1079- _is_kw_only ))):
1065+ if isinstance (type , str ):
1066+ a_type_annotation = _get_type_from_annotation (type , cls )
1067+ else :
1068+ a_type_annotation = type
1069+ if _is_kw_only (a_type_annotation , dataclasses ):
10801070 # Switch the default to kw_only=True, and ignore this
10811071 # annotation: it's not a real field.
10821072 if KW_ONLY_seen :
0 commit comments