11# -*- coding: utf-8 -*-
22"""Additional containers and container utilities."""
33
4- __all__ = ["box" , "unbox" , "frozendict" , "roview" , "view" , "ShadowedSequence" ,
4+ __all__ = ["box" , "ThreadLocalBox" , "unbox" , "Shim" ,
5+ "frozendict" , "roview" , "view" , "ShadowedSequence" ,
56 "mogrify" ,
67 "get_abcs" , "in_slice" , "index_in_slice" ,
78 "SequenceView" , "MutableSequenceView" ] # ABCs
1617 MappingView
1718from inspect import isclass
1819from operator import lt , le , ge , gt
20+ import threading
1921
2022from .llist import cons
2123from .misc import getattrrec
@@ -201,7 +203,6 @@ def __eq__(self, other):
201203 def set (self , x ):
202204 """Store a new value in the box, replacing the old one.
203205
204- Syntactic sugar for assigning to the attribute `.x`.
205206 As a convenience, returns the new value.
206207
207208 Since a function call is an expression, you can use this form
@@ -219,6 +220,39 @@ def __lshift__(self, x):
219220 for a `box`, so we just return the new value.)
220221 """
221222 return self .set (x )
223+ def get (self ):
224+ """Return the value currently in the box.
225+
226+ The syntactic sugar for `b.get()` is `unbox(b)`.
227+ """
228+ return self .x
229+
230+ # We re-implement instead of making `box` use an `env` as a place
231+ # so that the thread-locality feature is pay-as-you-go (no loss in
232+ # performance for the regular, non-thread-local `box`.)
233+ class ThreadLocalBox (box ):
234+ """Like box, but the store is thread-local."""
235+ def __init__ (self , x = None ):
236+ self .storage = threading .local ()
237+ self .storage .x = x
238+ def __repr__ (self ):
239+ """**WARNING**: the repr shows only the content seen by the current thread."""
240+ return "ThreadLocalBox({})" .format (repr (self .storage .x ))
241+ def __contains__ (self , x ):
242+ return self .storage .x == x
243+ def __iter__ (self ):
244+ return (x for x in (self .storage .x ,))
245+ def __len (self ):
246+ return 1
247+ def __eq__ (self , other ):
248+ return other == self .storage .x
249+ def set (self , x ):
250+ self .storage .x = x
251+ return x
252+ def __lshift__ (self , x ):
253+ return self .set (x )
254+ def get (self ):
255+ return self .storage .x
222256
223257def unbox (b ):
224258 """Return the value from inside the box b.
@@ -230,7 +264,32 @@ def unbox(b):
230264 """
231265 if not isinstance (b , box ):
232266 raise TypeError ("Expected box, got {} with value '{}'" .format (type (b ), b ))
233- return b .x
267+ return b .get ()
268+
269+ class Shim :
270+ """Attribute access redirector.
271+
272+ Hold a target object inside a box. When an attribute of this object
273+ is accessed (whether to get or set it), redirect that attribute
274+ access to the target currently inside the box.
275+
276+ The point is that the target may be switched at any time, simply by sending
277+ a new value into the box instance you gave to the `Shim` constructor.
278+
279+ Another use case is to combo with `ThreadLocalBox`, e.g. to redirect
280+ stdin/stdout only when used from some specific threads.
281+ """
282+ def __init__ (self , thebox ):
283+ """thebox: a `box` instance that will hold the target."""
284+ self ._box = thebox
285+ def __getattr__ (self , k ):
286+ thing = unbox (self ._box )
287+ return getattr (thing , k )
288+ def __setattr__ (self , k , v ):
289+ if k == "_box" :
290+ return super ().__setattr__ (k , v )
291+ thing = unbox (self ._box )
292+ return setattr (thing , k , v )
234293
235294_the_empty_frozendict = None
236295class frozendict :
0 commit comments