Skip to content

Commit 6328157

Browse files
committed
Fix error in threading.
Initializing the contexts property of the context_class of classes with ContextMeta as their metaclass was tricky to do properly.
1 parent 501146f commit 6328157

File tree

1 file changed

+36
-10
lines changed

1 file changed

+36
-10
lines changed

pymc3/model.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,20 +170,22 @@ class ContextMeta(type):
170170
"""Functionality for objects that put themselves in a context using
171171
the `with` statement.
172172
"""
173-
_context_class = None # type: Union[Type, str]
174173

175174
def __new__(cls, name, bases, dct, **kargs):
175+
# this serves only to strip off keyword args, per the warning from
176+
# StackExchange:
176177
# DO NOT send "**kargs" to "type.__new__". It won't catch them and
177178
# you'll get a "TypeError: type() takes 1 or 3 arguments" exception.
178-
# dct['get_context'] = classmethod(_get_context)
179-
# dct['get_contexts'] = classmethod(_get_contexts)
180179
return super().__new__(cls, name, bases, dct)
181180

181+
# FIXME: is there a more elegant way to automatically add methods to the class that
182+
# are instance methods instead of class methods?
182183
def __init__(cls, name, bases, nmspc, context_class: Optional[Type]=None, **kwargs):
184+
"""Add ``__enter__`` and ``__exit__`` methods to the new class automatically."""
183185
if context_class is not None:
184186
cls._context_class = context_class
185187
super().__init__(name, bases, nmspc)
186-
cls.contexts = threading.local()
188+
187189
def __enter__(self):
188190
self.__class__.context_class.get_contexts().append(self)
189191
# self._theano_config is set in Model.__new__
@@ -200,9 +202,11 @@ def __exit__(self, typ, value, traceback):
200202
cls.__enter__ = __enter__
201203
cls.__exit__ = __exit__
202204

205+
203206
def get_context(cls, error_if_none=True) -> Optional[T]:
204207
"""Return the most recently pushed context object of type ``cls``
205-
on the stack, or ``None``."""
208+
on the stack, or ``None``. If ``error_if_none`` is True (default),
209+
raise a ``TypeError`` instead of returning ``None``."""
206210
idx = -1
207211
while True:
208212
try:
@@ -217,12 +221,32 @@ def get_context(cls, error_if_none=True) -> Optional[T]:
217221
idx = idx - 1
218222

219223
def get_contexts(cls) -> List[T]:
220-
# no race-condition here, cls.contexts is a thread-local object
224+
"""Return a stack of context instances for the ``context_class``
225+
of ``cls``."""
226+
# This lazily creates the context class's contexts
227+
# thread-local object, as needed. This seems inelegant to me,
228+
# but since the context class is not guaranteed to exist when
229+
# the metaclass is being instantiated, I couldn't figure out a
230+
# better way. [2019/10/11:rpg]
231+
232+
# no race-condition here, contexts is a thread-local object
221233
# be sure not to override contexts in a subclass however!
222-
if not hasattr(cls.context_class, 'stack'):
223-
cls.context_class.stack = []
224-
return cls.context_class.stack
225-
234+
context_class = cls.context_class
235+
assert isinstance(context_class, type), \
236+
"Name of context class, %s was not resolvable to a class"%context_class
237+
if not hasattr(context_class, 'contexts'):
238+
context_class.contexts = threading.local()
239+
240+
contexts = context_class.contexts
241+
242+
if not hasattr(contexts, 'stack'):
243+
contexts.stack = []
244+
return contexts.stack
245+
246+
# the following complex property accessor is necessary because the
247+
# context_class may not have been created at the point it is
248+
# specified, so the context_class may be a class *name* rather
249+
# than a class.
226250
@property
227251
def context_class(cls) -> Type:
228252
def resolve_type(c: Union[Type, str]) -> Type:
@@ -239,11 +263,13 @@ def resolve_type(c: Union[Type, str]) -> Type:
239263
(cls.__name__, cls._context_class))
240264
return cls._context_class
241265

266+
# Inherit context class from parent
242267
def __init_subclass__(cls, **kwargs):
243268
super().__init_subclass__(**kwargs)
244269
cls.context_class = super().context_class
245270

246271
# Initialize object in its own context...
272+
# Merged from InitContextMeta in the original.
247273
def __call__(cls, *args, **kwargs):
248274
instance = cls.__new__(cls, *args, **kwargs)
249275
with instance: # appends context

0 commit comments

Comments
 (0)