33Conversions between Python and Mathics3
44"""
55
6- from typing import Any
6+ import math
7+ from dataclasses import dataclass
8+ from typing import Any , Final , Optional
79
810import numpy
911
1618 Real ,
1719 String ,
1820)
21+ from mathics .core .element import ELEMENTS_FULLY_EVALUATED
1922from mathics .core .number import get_type
2023from mathics .core .symbols import (
2124 BaseElement ,
2427 SymbolNull ,
2528 SymbolTrue ,
2629)
27- from mathics .core .systemsymbols import SymbolRule
30+ from mathics .core .systemsymbols import SymbolAssociation , SymbolRule
2831
2932
3033def from_bool (arg : bool ) -> BooleanType :
@@ -48,6 +51,46 @@ def from_complex(arg: complex) -> Complex:
4851 return Complex (real_value , imag_value )
4952
5053
54+ @dataclass (frozen = True )
55+ class ToPythonOptions :
56+ """
57+ Stores options associated with the to_python[] builtin.
58+
59+ One initialized, this structure is immutable or frozen.
60+ """
61+
62+ use_associations : Optional [bool ] = None
63+ """'True" if ordering should be lowercase first, 'False" if should uppercase first,
64+ and 'None' if we should use the natural alphabet ordering case."""
65+
66+ @classmethod
67+ def from_dict (cls , options : dict [str , Any ]) -> "ToPythonOptions" :
68+ """Factory method that normalizes, type-checks, and builds the frozen structure
69+ from a raw dict[str, str].
70+ """
71+
72+ # This will hold our cleaned, type-converted parameters
73+ processed_args : dict [str , Any ] = {
74+ "use_associations" : False ,
75+ }
76+
77+ # Iterate through the user-provided options dictionary
78+ for key , option_value in options .items ():
79+
80+ if not key :
81+ raise TypeError (f"ToPythonOptions: bad key: { key } " )
82+
83+ # Type parsing and validation based on the target field name
84+ processed_args [key ] = option_value
85+
86+ # Initialize and return the frozen dataclass using our verified arguments
87+ return cls (** processed_args )
88+
89+
90+ DEFAULT_PYTHON_OPTIONS : Final [ToPythonOptions ] = ToPythonOptions .from_dict (
91+ {"use_associations" : False }
92+ )
93+
5194# Historically, from_python() was identified as a bottleneck.
5295
5396# A large part of this was due to the inefficient monolithic
@@ -63,7 +106,6 @@ def from_complex(arg: complex) -> Complex:
63106# of a bottleneck. So care may be warranted to make
64107# sure from_python() isn't too slow.
65108
66-
67109# TODO:
68110# I think there are number of subtleties to be explained here.
69111# In particular, the expression might been the result of evaluation
@@ -76,7 +118,7 @@ def from_complex(arg: complex) -> Complex:
76118# symbol like underscore.
77119
78120
79- def from_python (arg : Any ) -> BaseElement :
121+ def from_python (arg : Any , options = DEFAULT_PYTHON_OPTIONS ) -> BaseElement :
80122 """Converts a Python expression into a Mathics3 expression."""
81123 from mathics .core .convert .expression import to_mathics_list
82124 from mathics .core .expression import Expression
@@ -108,20 +150,58 @@ def from_python(arg: Any) -> BaseElement:
108150 # else:
109151 # return Symbol(arg)
110152 elif isinstance (arg , dict ):
153+ if options .use_associations :
154+ return association_from_dict (arg , options )
155+ # List of Rules was used before Associations came
156+ # into use in Wolfram Language. List of Rules
157+ # is still used a bit.
158+ # Note that Python dictionaries from the standpoint of
159+ # evaluation are fully evaluated
111160 entries = [
112161 Expression (
113162 SymbolRule ,
114163 from_python (key ),
115- from_python (arg [key ]),
164+ from_python (value ),
165+ elements_properties = ELEMENTS_FULLY_EVALUATED ,
116166 )
117- for key in arg
167+ for key , value in arg . items ()
118168 ]
119- return ListExpression (* entries )
169+ return ListExpression (* entries , elements_properties = ELEMENTS_FULLY_EVALUATED )
120170 elif isinstance (arg , list ) or isinstance (arg , tuple ):
121171 return to_mathics_list (* arg , elements_conversion_fn = from_python )
122172 elif isinstance (arg , bytearray ) or isinstance (arg , bytes ):
123173 return ByteArray (arg )
124174 elif isinstance (arg , numpy .ndarray ):
125175 return NumericArray (arg )
176+ elif arg == math .inf :
177+ from mathics .core .expression_predefined import MATHICS3_INFINITY
178+
179+ return MATHICS3_INFINITY
180+ elif arg == - math .inf :
181+ from mathics .core .expression_predefined import MATHICS3_NEG_INFINITY
182+
183+ return MATHICS3_NEG_INFINITY
126184 else :
127185 raise NotImplementedError
186+
187+
188+ def association_from_dict (arg : dict , options : ToPythonOptions ) -> BaseElement :
189+ """
190+ Convert a Python dictionary into a Mathics3 Association.
191+ """
192+ from mathics .core .expression import Expression
193+
194+ entries = [
195+ Expression (
196+ SymbolRule ,
197+ from_python (key , options ),
198+ from_python (value , options ),
199+ )
200+ for key , value in arg .items ()
201+ ]
202+ result = Expression (
203+ SymbolAssociation ,
204+ * entries ,
205+ elements_properties = ELEMENTS_FULLY_EVALUATED ,
206+ )
207+ return result
0 commit comments