@@ -139,20 +139,13 @@ def namespace(self):
139139 from the encapsulated environment."""
140140 return VarLookupDict (self ._namespaces )
141141
142- def add_outer_namespace (self , namespace ):
143- """Expose the contents of a dict-like object to the encapsulated
144- environment.
142+ def with_outer_namespace (self , outer_namespace ):
143+ """Return a new EvalEnvironment with an extra namespace added.
145144
146- The given namespace will be checked last, after all existing namespace
147- lookups have failed.
148- """
149- # ModelDesc.from_formula unconditionally calls
150- # eval_env.add_outer_namespace(builtins)
151- # which means that if someone uses the same environment for a bunch of
152- # formulas, our namespace chain will grow without bound, which would
153- # suck.
154- if id (namespace ) not in self ._namespace_ids ():
155- self ._namespaces .append (namespace )
145+ This namespace will be used only for variables that are not found in
146+ any existing namespace, i.e., it is "outside" them all."""
147+ return self .__class__ (self ._namespaces + [outer_namespace ],
148+ self .flags )
156149
157150 def eval (self , expr , source_name = "<string>" , inner_namespace = {}):
158151 """Evaluate some Python code in the encapsulated environment.
@@ -350,6 +343,10 @@ def test_EvalEnvironment_eval_namespace():
350343 env2 = EvalEnvironment .capture (0 )
351344 assert env2 .eval ("2 * a" ) == 6
352345
346+ env3 = env .with_outer_namespace ({"a" : 10 , "b" : 3 })
347+ assert env3 .eval ("2 * a" ) == 2
348+ assert env3 .eval ("2 * b" ) == 6
349+
353350def test_EvalEnvironment_eval_flags ():
354351 from nose .tools import assert_raises
355352 if sys .version_info >= (3 ,):
@@ -362,22 +359,26 @@ def test_EvalEnvironment_eval_flags():
362359 assert env .eval ("a != 0" ) == True
363360 assert_raises (SyntaxError , env .eval , "a <> 0" )
364361 assert env .subset (["a" ]).flags == 0
362+ assert env .with_outer_namespace ({"b" : 10 }).flags == 0
365363
366364 env2 = EvalEnvironment ([{"a" : 11 }], flags = test_flag )
367365 assert env2 .eval ("a <> 0" ) == True
368366 assert_raises (SyntaxError , env2 .eval , "a != 0" )
369367 assert env2 .subset (["a" ]).flags == test_flag
368+ assert env2 .with_outer_namespace ({"b" : 10 }).flags == test_flag
370369 else :
371370 test_flag = __future__ .division .compiler_flag
372371 assert test_flag & _ALL_FUTURE_FLAGS
373372
374373 env = EvalEnvironment ([{"a" : 11 }], flags = 0 )
375374 assert env .eval ("a / 2" ) == 11 // 2 == 5
376375 assert env .subset (["a" ]).flags == 0
376+ assert env .with_outer_namespace ({"b" : 10 }).flags == 0
377377
378378 env2 = EvalEnvironment ([{"a" : 11 }], flags = test_flag )
379379 assert env2 .eval ("a / 2" ) == 11 * 1. / 2 != 5
380380 env2 .subset (["a" ]).flags == test_flag
381+ assert env2 .with_outer_namespace ({"b" : 10 }).flags == test_flag
381382
382383def test_EvalEnvironment_subset ():
383384 env = EvalEnvironment ([{"a" : 1 }, {"b" : 2 }, {"c" : 3 }])
@@ -404,17 +405,12 @@ def test_EvalEnvironment_eq():
404405 env4 = capture_local_env ()
405406 assert env3 != env4
406407
407- def test_EvalEnvironment_add_outer_namespace ():
408- a = 1
409- env = EvalEnvironment .capture (0 )
410- env2 = EvalEnvironment .capture (0 )
411- assert env .namespace ["a" ] == 1
412- assert "b" not in env .namespace
413- assert env == env2
414- env .add_outer_namespace ({"a" : 10 , "b" : 2 })
415- assert env .namespace ["a" ] == 1
416- assert env .namespace ["b" ] == 2
417- assert env != env2
408+ _builtins_dict = {}
409+ six .exec_ ("from patsy.builtins import *" , {}, _builtins_dict )
410+ # This is purely to make the existence of patsy.builtins visible to systems
411+ # like py2app and py2exe. It's basically free, since the above line guarantees
412+ # that patsy.builtins will be present in sys.modules in any case.
413+ import patsy .builtins
418414
419415class EvalFactor (object ):
420416 def __init__ (self , code , origin = None ):
@@ -464,10 +460,12 @@ def memorize_passes_needed(self, state, eval_env):
464460 # and that will be passed back to later memorize functions
465461 state ["transforms" ] = {}
466462
463+ eval_env = eval_env .with_outer_namespace (_builtins_dict )
467464 env_namespace = eval_env .namespace
468465 subset_names = [name for name in ast_names (self .code )
469466 if name in env_namespace ]
470- state ["eval_env" ] = eval_env .subset (subset_names )
467+ eval_env = eval_env .subset (subset_names )
468+ state ["eval_env" ] = eval_env
471469
472470 # example code: == "2 * center(x)"
473471 i = [0 ]
@@ -535,17 +533,17 @@ def new_name_maker(token):
535533
536534 return len (pass_bins )
537535
538- def _eval (self , code , eval_env , memorize_state , data ):
536+ def _eval (self , code , memorize_state , data ):
539537 inner_namespace = VarLookupDict ([data , memorize_state ["transforms" ]])
540538 return call_and_wrap_exc ("Error evaluating factor" ,
541539 self ,
542- eval_env .eval ,
543- code , inner_namespace = inner_namespace )
540+ memorize_state ["eval_env" ].eval ,
541+ code ,
542+ inner_namespace = inner_namespace )
544543
545544 def memorize_chunk (self , state , which_pass , data ):
546545 for obj_name in state ["pass_bins" ][which_pass ]:
547546 self ._eval (state ["memorize_code" ][obj_name ],
548- state ["eval_env" ],
549547 state ,
550548 data )
551549
@@ -554,8 +552,9 @@ def memorize_finish(self, state, which_pass):
554552 state ["transforms" ][obj_name ].memorize_finish ()
555553
556554 def eval (self , memorize_state , data ):
557- return self ._eval (memorize_state ["eval_code" ], memorize_state ["eval_env" ],
558- memorize_state , data )
555+ return self ._eval (memorize_state ["eval_code" ],
556+ memorize_state ,
557+ data )
559558
560559def test_EvalFactor_basics ():
561560 e = EvalFactor ("a+b" )
0 commit comments