PageRenderTime 57ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/notify_user/pymodules/python2.7/lib/python/matplotlib-1.5.1-py2.7-linux-x86_64.egg/matplotlib/cbook.py

https://gitlab.com/pooja043/Globus_Docker_4
Python | 1693 lines | 1418 code | 53 blank | 222 comment | 68 complexity | 4bd823725e3e40ee22593e927d85efca MD5 | raw file
  1. """
  2. A collection of utility functions and classes. Originally, many
  3. (but not all) were from the Python Cookbook -- hence the name cbook.
  4. This module is safe to import from anywhere within matplotlib;
  5. it imports matplotlib only at runtime.
  6. """
  7. from __future__ import (absolute_import, division, print_function,
  8. unicode_literals)
  9. from matplotlib.externals import six
  10. from matplotlib.externals.six.moves import xrange, zip
  11. from itertools import repeat
  12. import collections
  13. import datetime
  14. import errno
  15. from functools import reduce
  16. import glob
  17. import gzip
  18. import io
  19. import locale
  20. import os
  21. import re
  22. import sys
  23. import time
  24. import traceback
  25. import types
  26. import warnings
  27. from weakref import ref, WeakKeyDictionary
  28. import numpy as np
  29. import numpy.ma as ma
  30. class MatplotlibDeprecationWarning(UserWarning):
  31. """
  32. A class for issuing deprecation warnings for Matplotlib users.
  33. In light of the fact that Python builtin DeprecationWarnings are ignored
  34. by default as of Python 2.7 (see link below), this class was put in to
  35. allow for the signaling of deprecation, but via UserWarnings which are not
  36. ignored by default.
  37. http://docs.python.org/dev/whatsnew/2.7.html#the-future-for-python-2-x
  38. """
  39. pass
  40. mplDeprecation = MatplotlibDeprecationWarning
  41. def _generate_deprecation_message(since, message='', name='',
  42. alternative='', pending=False,
  43. obj_type='attribute'):
  44. if not message:
  45. altmessage = ''
  46. if pending:
  47. message = (
  48. 'The %(func)s %(obj_type)s will be deprecated in a '
  49. 'future version.')
  50. else:
  51. message = (
  52. 'The %(func)s %(obj_type)s was deprecated in version '
  53. '%(since)s.')
  54. if alternative:
  55. altmessage = ' Use %s instead.' % alternative
  56. message = ((message % {
  57. 'func': name,
  58. 'name': name,
  59. 'alternative': alternative,
  60. 'obj_type': obj_type,
  61. 'since': since}) +
  62. altmessage)
  63. return message
  64. def warn_deprecated(
  65. since, message='', name='', alternative='', pending=False,
  66. obj_type='attribute'):
  67. """
  68. Used to display deprecation warning in a standard way.
  69. Parameters
  70. ------------
  71. since : str
  72. The release at which this API became deprecated.
  73. message : str, optional
  74. Override the default deprecation message. The format
  75. specifier `%(func)s` may be used for the name of the function,
  76. and `%(alternative)s` may be used in the deprecation message
  77. to insert the name of an alternative to the deprecated
  78. function. `%(obj_type)` may be used to insert a friendly name
  79. for the type of object being deprecated.
  80. name : str, optional
  81. The name of the deprecated function; if not provided the name
  82. is automatically determined from the passed in function,
  83. though this is useful in the case of renamed functions, where
  84. the new function is just assigned to the name of the
  85. deprecated function. For example::
  86. def new_function():
  87. ...
  88. oldFunction = new_function
  89. alternative : str, optional
  90. An alternative function that the user may use in place of the
  91. deprecated function. The deprecation warning will tell the user about
  92. this alternative if provided.
  93. pending : bool, optional
  94. If True, uses a PendingDeprecationWarning instead of a
  95. DeprecationWarning.
  96. obj_type : str, optional
  97. The object type being deprecated.
  98. Examples
  99. --------
  100. Basic example::
  101. # To warn of the deprecation of "matplotlib.name_of_module"
  102. warn_deprecated('1.4.0', name='matplotlib.name_of_module',
  103. obj_type='module')
  104. """
  105. message = _generate_deprecation_message(
  106. since, message, name, alternative, pending, obj_type)
  107. warnings.warn(message, mplDeprecation, stacklevel=1)
  108. def deprecated(since, message='', name='', alternative='', pending=False,
  109. obj_type='function'):
  110. """
  111. Decorator to mark a function as deprecated.
  112. Parameters
  113. ------------
  114. since : str
  115. The release at which this API became deprecated. This is
  116. required.
  117. message : str, optional
  118. Override the default deprecation message. The format
  119. specifier `%(func)s` may be used for the name of the function,
  120. and `%(alternative)s` may be used in the deprecation message
  121. to insert the name of an alternative to the deprecated
  122. function. `%(obj_type)` may be used to insert a friendly name
  123. for the type of object being deprecated.
  124. name : str, optional
  125. The name of the deprecated function; if not provided the name
  126. is automatically determined from the passed in function,
  127. though this is useful in the case of renamed functions, where
  128. the new function is just assigned to the name of the
  129. deprecated function. For example::
  130. def new_function():
  131. ...
  132. oldFunction = new_function
  133. alternative : str, optional
  134. An alternative function that the user may use in place of the
  135. deprecated function. The deprecation warning will tell the user about
  136. this alternative if provided.
  137. pending : bool, optional
  138. If True, uses a PendingDeprecationWarning instead of a
  139. DeprecationWarning.
  140. Examples
  141. --------
  142. Basic example::
  143. @deprecated('1.4.0')
  144. def the_function_to_deprecate():
  145. pass
  146. """
  147. def deprecate(func, message=message, name=name, alternative=alternative,
  148. pending=pending):
  149. import functools
  150. import textwrap
  151. if isinstance(func, classmethod):
  152. try:
  153. func = func.__func__
  154. except AttributeError:
  155. # classmethods in Python2.6 and below lack the __func__
  156. # attribute so we need to hack around to get it
  157. method = func.__get__(None, object)
  158. if hasattr(method, '__func__'):
  159. func = method.__func__
  160. elif hasattr(method, 'im_func'):
  161. func = method.im_func
  162. else:
  163. # Nothing we can do really... just return the original
  164. # classmethod
  165. return func
  166. is_classmethod = True
  167. else:
  168. is_classmethod = False
  169. if not name:
  170. name = func.__name__
  171. message = _generate_deprecation_message(
  172. since, message, name, alternative, pending, obj_type)
  173. @functools.wraps(func)
  174. def deprecated_func(*args, **kwargs):
  175. warnings.warn(message, mplDeprecation, stacklevel=2)
  176. return func(*args, **kwargs)
  177. old_doc = deprecated_func.__doc__
  178. if not old_doc:
  179. old_doc = ''
  180. old_doc = textwrap.dedent(old_doc).strip('\n')
  181. message = message.strip()
  182. new_doc = (('\n.. deprecated:: %(since)s'
  183. '\n %(message)s\n\n' %
  184. {'since': since, 'message': message}) + old_doc)
  185. if not old_doc:
  186. # This is to prevent a spurious 'unexected unindent' warning from
  187. # docutils when the original docstring was blank.
  188. new_doc += r'\ '
  189. deprecated_func.__doc__ = new_doc
  190. if is_classmethod:
  191. deprecated_func = classmethod(deprecated_func)
  192. return deprecated_func
  193. return deprecate
  194. # On some systems, locale.getpreferredencoding returns None,
  195. # which can break unicode; and the sage project reports that
  196. # some systems have incorrect locale specifications, e.g.,
  197. # an encoding instead of a valid locale name. Another
  198. # pathological case that has been reported is an empty string.
  199. # On some systems, getpreferredencoding sets the locale, which has
  200. # side effects. Passing False eliminates those side effects.
  201. def unicode_safe(s):
  202. import matplotlib
  203. if isinstance(s, bytes):
  204. try:
  205. preferredencoding = locale.getpreferredencoding(
  206. matplotlib.rcParams['axes.formatter.use_locale']).strip()
  207. if not preferredencoding:
  208. preferredencoding = None
  209. except (ValueError, ImportError, AttributeError):
  210. preferredencoding = None
  211. if preferredencoding is None:
  212. return six.text_type(s)
  213. else:
  214. return six.text_type(s, preferredencoding)
  215. return s
  216. class converter(object):
  217. """
  218. Base class for handling string -> python type with support for
  219. missing values
  220. """
  221. def __init__(self, missing='Null', missingval=None):
  222. self.missing = missing
  223. self.missingval = missingval
  224. def __call__(self, s):
  225. if s == self.missing:
  226. return self.missingval
  227. return s
  228. def is_missing(self, s):
  229. return not s.strip() or s == self.missing
  230. class tostr(converter):
  231. 'convert to string or None'
  232. def __init__(self, missing='Null', missingval=''):
  233. converter.__init__(self, missing=missing, missingval=missingval)
  234. class todatetime(converter):
  235. 'convert to a datetime or None'
  236. def __init__(self, fmt='%Y-%m-%d', missing='Null', missingval=None):
  237. 'use a :func:`time.strptime` format string for conversion'
  238. converter.__init__(self, missing, missingval)
  239. self.fmt = fmt
  240. def __call__(self, s):
  241. if self.is_missing(s):
  242. return self.missingval
  243. tup = time.strptime(s, self.fmt)
  244. return datetime.datetime(*tup[:6])
  245. class todate(converter):
  246. 'convert to a date or None'
  247. def __init__(self, fmt='%Y-%m-%d', missing='Null', missingval=None):
  248. 'use a :func:`time.strptime` format string for conversion'
  249. converter.__init__(self, missing, missingval)
  250. self.fmt = fmt
  251. def __call__(self, s):
  252. if self.is_missing(s):
  253. return self.missingval
  254. tup = time.strptime(s, self.fmt)
  255. return datetime.date(*tup[:3])
  256. class tofloat(converter):
  257. 'convert to a float or None'
  258. def __init__(self, missing='Null', missingval=None):
  259. converter.__init__(self, missing)
  260. self.missingval = missingval
  261. def __call__(self, s):
  262. if self.is_missing(s):
  263. return self.missingval
  264. return float(s)
  265. class toint(converter):
  266. 'convert to an int or None'
  267. def __init__(self, missing='Null', missingval=None):
  268. converter.__init__(self, missing)
  269. def __call__(self, s):
  270. if self.is_missing(s):
  271. return self.missingval
  272. return int(s)
  273. class _BoundMethodProxy(object):
  274. '''
  275. Our own proxy object which enables weak references to bound and unbound
  276. methods and arbitrary callables. Pulls information about the function,
  277. class, and instance out of a bound method. Stores a weak reference to the
  278. instance to support garbage collection.
  279. @organization: IBM Corporation
  280. @copyright: Copyright (c) 2005, 2006 IBM Corporation
  281. @license: The BSD License
  282. Minor bugfixes by Michael Droettboom
  283. '''
  284. def __init__(self, cb):
  285. self._hash = hash(cb)
  286. self._destroy_callbacks = []
  287. try:
  288. try:
  289. if six.PY3:
  290. self.inst = ref(cb.__self__, self._destroy)
  291. else:
  292. self.inst = ref(cb.im_self, self._destroy)
  293. except TypeError:
  294. self.inst = None
  295. if six.PY3:
  296. self.func = cb.__func__
  297. self.klass = cb.__self__.__class__
  298. else:
  299. self.func = cb.im_func
  300. self.klass = cb.im_class
  301. except AttributeError:
  302. self.inst = None
  303. self.func = cb
  304. self.klass = None
  305. def add_destroy_callback(self, callback):
  306. self._destroy_callbacks.append(_BoundMethodProxy(callback))
  307. def _destroy(self, wk):
  308. for callback in self._destroy_callbacks:
  309. try:
  310. callback(self)
  311. except ReferenceError:
  312. pass
  313. def __getstate__(self):
  314. d = self.__dict__.copy()
  315. # de-weak reference inst
  316. inst = d['inst']
  317. if inst is not None:
  318. d['inst'] = inst()
  319. return d
  320. def __setstate__(self, statedict):
  321. self.__dict__ = statedict
  322. inst = statedict['inst']
  323. # turn inst back into a weakref
  324. if inst is not None:
  325. self.inst = ref(inst)
  326. def __call__(self, *args, **kwargs):
  327. '''
  328. Proxy for a call to the weak referenced object. Take
  329. arbitrary params to pass to the callable.
  330. Raises `ReferenceError`: When the weak reference refers to
  331. a dead object
  332. '''
  333. if self.inst is not None and self.inst() is None:
  334. raise ReferenceError
  335. elif self.inst is not None:
  336. # build a new instance method with a strong reference to the
  337. # instance
  338. mtd = types.MethodType(self.func, self.inst())
  339. else:
  340. # not a bound method, just return the func
  341. mtd = self.func
  342. # invoke the callable and return the result
  343. return mtd(*args, **kwargs)
  344. def __eq__(self, other):
  345. '''
  346. Compare the held function and instance with that held by
  347. another proxy.
  348. '''
  349. try:
  350. if self.inst is None:
  351. return self.func == other.func and other.inst is None
  352. else:
  353. return self.func == other.func and self.inst() == other.inst()
  354. except Exception:
  355. return False
  356. def __ne__(self, other):
  357. '''
  358. Inverse of __eq__.
  359. '''
  360. return not self.__eq__(other)
  361. def __hash__(self):
  362. return self._hash
  363. class CallbackRegistry(object):
  364. """
  365. Handle registering and disconnecting for a set of signals and
  366. callbacks:
  367. >>> def oneat(x):
  368. ... print('eat', x)
  369. >>> def ondrink(x):
  370. ... print('drink', x)
  371. >>> from matplotlib.cbook import CallbackRegistry
  372. >>> callbacks = CallbackRegistry()
  373. >>> id_eat = callbacks.connect('eat', oneat)
  374. >>> id_drink = callbacks.connect('drink', ondrink)
  375. >>> callbacks.process('drink', 123)
  376. drink 123
  377. >>> callbacks.process('eat', 456)
  378. eat 456
  379. >>> callbacks.process('be merry', 456) # nothing will be called
  380. >>> callbacks.disconnect(id_eat)
  381. >>> callbacks.process('eat', 456) # nothing will be called
  382. In practice, one should always disconnect all callbacks when they
  383. are no longer needed to avoid dangling references (and thus memory
  384. leaks). However, real code in matplotlib rarely does so, and due
  385. to its design, it is rather difficult to place this kind of code.
  386. To get around this, and prevent this class of memory leaks, we
  387. instead store weak references to bound methods only, so when the
  388. destination object needs to die, the CallbackRegistry won't keep
  389. it alive. The Python stdlib weakref module can not create weak
  390. references to bound methods directly, so we need to create a proxy
  391. object to handle weak references to bound methods (or regular free
  392. functions). This technique was shared by Peter Parente on his
  393. `"Mindtrove" blog
  394. <http://mindtrove.info/articles/python-weak-references/>`_.
  395. """
  396. def __init__(self):
  397. self.callbacks = dict()
  398. self._cid = 0
  399. self._func_cid_map = {}
  400. def __getstate__(self):
  401. # We cannot currently pickle the callables in the registry, so
  402. # return an empty dictionary.
  403. return {}
  404. def __setstate__(self, state):
  405. # re-initialise an empty callback registry
  406. self.__init__()
  407. def connect(self, s, func):
  408. """
  409. register *func* to be called when a signal *s* is generated
  410. func will be called
  411. """
  412. self._func_cid_map.setdefault(s, WeakKeyDictionary())
  413. # Note proxy not needed in python 3.
  414. # TODO rewrite this when support for python2.x gets dropped.
  415. proxy = _BoundMethodProxy(func)
  416. if proxy in self._func_cid_map[s]:
  417. return self._func_cid_map[s][proxy]
  418. proxy.add_destroy_callback(self._remove_proxy)
  419. self._cid += 1
  420. cid = self._cid
  421. self._func_cid_map[s][proxy] = cid
  422. self.callbacks.setdefault(s, dict())
  423. self.callbacks[s][cid] = proxy
  424. return cid
  425. def _remove_proxy(self, proxy):
  426. for signal, proxies in list(six.iteritems(self._func_cid_map)):
  427. try:
  428. del self.callbacks[signal][proxies[proxy]]
  429. except KeyError:
  430. pass
  431. if len(self.callbacks[signal]) == 0:
  432. del self.callbacks[signal]
  433. del self._func_cid_map[signal]
  434. def disconnect(self, cid):
  435. """
  436. disconnect the callback registered with callback id *cid*
  437. """
  438. for eventname, callbackd in list(six.iteritems(self.callbacks)):
  439. try:
  440. del callbackd[cid]
  441. except KeyError:
  442. continue
  443. else:
  444. for signal, functions in list(
  445. six.iteritems(self._func_cid_map)):
  446. for function, value in list(six.iteritems(functions)):
  447. if value == cid:
  448. del functions[function]
  449. return
  450. def process(self, s, *args, **kwargs):
  451. """
  452. process signal *s*. All of the functions registered to receive
  453. callbacks on *s* will be called with *\*args* and *\*\*kwargs*
  454. """
  455. if s in self.callbacks:
  456. for cid, proxy in list(six.iteritems(self.callbacks[s])):
  457. try:
  458. proxy(*args, **kwargs)
  459. except ReferenceError:
  460. self._remove_proxy(proxy)
  461. class silent_list(list):
  462. """
  463. override repr when returning a list of matplotlib artists to
  464. prevent long, meaningless output. This is meant to be used for a
  465. homogeneous list of a given type
  466. """
  467. def __init__(self, type, seq=None):
  468. self.type = type
  469. if seq is not None:
  470. self.extend(seq)
  471. def __repr__(self):
  472. return '<a list of %d %s objects>' % (len(self), self.type)
  473. def __str__(self):
  474. return repr(self)
  475. def __getstate__(self):
  476. # store a dictionary of this SilentList's state
  477. return {'type': self.type, 'seq': self[:]}
  478. def __setstate__(self, state):
  479. self.type = state['type']
  480. self.extend(state['seq'])
  481. class IgnoredKeywordWarning(UserWarning):
  482. """
  483. A class for issuing warnings about keyword arguments that will be ignored
  484. by matplotlib
  485. """
  486. pass
  487. def local_over_kwdict(local_var, kwargs, *keys):
  488. """
  489. Enforces the priority of a local variable over potentially conflicting
  490. argument(s) from a kwargs dict. The following possible output values are
  491. considered in order of priority:
  492. local_var > kwargs[keys[0]] > ... > kwargs[keys[-1]]
  493. The first of these whose value is not None will be returned. If all are
  494. None then None will be returned. Each key in keys will be removed from the
  495. kwargs dict in place.
  496. Parameters
  497. ------------
  498. local_var: any object
  499. The local variable (highest priority)
  500. kwargs: dict
  501. Dictionary of keyword arguments; modified in place
  502. keys: str(s)
  503. Name(s) of keyword arguments to process, in descending order of
  504. priority
  505. Returns
  506. ---------
  507. out: any object
  508. Either local_var or one of kwargs[key] for key in keys
  509. Raises
  510. --------
  511. IgnoredKeywordWarning
  512. For each key in keys that is removed from kwargs but not used as
  513. the output value
  514. """
  515. out = local_var
  516. for key in keys:
  517. kwarg_val = kwargs.pop(key, None)
  518. if kwarg_val is not None:
  519. if out is None:
  520. out = kwarg_val
  521. else:
  522. warnings.warn('"%s" keyword argument will be ignored' % key,
  523. IgnoredKeywordWarning)
  524. return out
  525. def strip_math(s):
  526. 'remove latex formatting from mathtext'
  527. remove = (r'\mathdefault', r'\rm', r'\cal', r'\tt', r'\it', '\\', '{', '}')
  528. s = s[1:-1]
  529. for r in remove:
  530. s = s.replace(r, '')
  531. return s
  532. class Bunch(object):
  533. """
  534. Often we want to just collect a bunch of stuff together, naming each
  535. item of the bunch; a dictionary's OK for that, but a small do- nothing
  536. class is even handier, and prettier to use. Whenever you want to
  537. group a few variables::
  538. >>> point = Bunch(datum=2, squared=4, coord=12)
  539. >>> point.datum
  540. By: Alex Martelli
  541. From: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52308
  542. """
  543. def __init__(self, **kwds):
  544. self.__dict__.update(kwds)
  545. def __repr__(self):
  546. keys = six.iterkeys(self.__dict__)
  547. return 'Bunch(%s)' % ', '.join(['%s=%s' % (k, self.__dict__[k])
  548. for k
  549. in keys])
  550. def unique(x):
  551. 'Return a list of unique elements of *x*'
  552. return list(six.iterkeys(dict([(val, 1) for val in x])))
  553. def iterable(obj):
  554. 'return true if *obj* is iterable'
  555. try:
  556. iter(obj)
  557. except TypeError:
  558. return False
  559. return True
  560. def is_string_like(obj):
  561. 'Return True if *obj* looks like a string'
  562. if isinstance(obj, six.string_types):
  563. return True
  564. # numpy strings are subclass of str, ma strings are not
  565. if ma.isMaskedArray(obj):
  566. if obj.ndim == 0 and obj.dtype.kind in 'SU':
  567. return True
  568. else:
  569. return False
  570. try:
  571. obj + ''
  572. except:
  573. return False
  574. return True
  575. def is_sequence_of_strings(obj):
  576. """
  577. Returns true if *obj* is iterable and contains strings
  578. """
  579. if not iterable(obj):
  580. return False
  581. if is_string_like(obj) and not isinstance(obj, np.ndarray):
  582. try:
  583. obj = obj.values
  584. except AttributeError:
  585. # not pandas
  586. return False
  587. for o in obj:
  588. if not is_string_like(o):
  589. return False
  590. return True
  591. def is_writable_file_like(obj):
  592. 'return true if *obj* looks like a file object with a *write* method'
  593. return hasattr(obj, 'write') and six.callable(obj.write)
  594. def file_requires_unicode(x):
  595. """
  596. Returns `True` if the given writable file-like object requires Unicode
  597. to be written to it.
  598. """
  599. try:
  600. x.write(b'')
  601. except TypeError:
  602. return True
  603. else:
  604. return False
  605. def is_scalar(obj):
  606. 'return true if *obj* is not string like and is not iterable'
  607. return not is_string_like(obj) and not iterable(obj)
  608. def is_numlike(obj):
  609. 'return true if *obj* looks like a number'
  610. try:
  611. obj + 1
  612. except:
  613. return False
  614. else:
  615. return True
  616. def to_filehandle(fname, flag='rU', return_opened=False):
  617. """
  618. *fname* can be a filename or a file handle. Support for gzipped
  619. files is automatic, if the filename ends in .gz. *flag* is a
  620. read/write flag for :func:`file`
  621. """
  622. if is_string_like(fname):
  623. if fname.endswith('.gz'):
  624. # get rid of 'U' in flag for gzipped files.
  625. flag = flag.replace('U', '')
  626. fh = gzip.open(fname, flag)
  627. elif fname.endswith('.bz2'):
  628. # get rid of 'U' in flag for bz2 files
  629. flag = flag.replace('U', '')
  630. import bz2
  631. fh = bz2.BZ2File(fname, flag)
  632. else:
  633. fh = open(fname, flag)
  634. opened = True
  635. elif hasattr(fname, 'seek'):
  636. fh = fname
  637. opened = False
  638. else:
  639. raise ValueError('fname must be a string or file handle')
  640. if return_opened:
  641. return fh, opened
  642. return fh
  643. def is_scalar_or_string(val):
  644. """Return whether the given object is a scalar or string like."""
  645. return is_string_like(val) or not iterable(val)
  646. def _string_to_bool(s):
  647. if not is_string_like(s):
  648. return s
  649. if s == 'on':
  650. return True
  651. if s == 'off':
  652. return False
  653. raise ValueError("string argument must be either 'on' or 'off'")
  654. def get_sample_data(fname, asfileobj=True):
  655. """
  656. Return a sample data file. *fname* is a path relative to the
  657. `mpl-data/sample_data` directory. If *asfileobj* is `True`
  658. return a file object, otherwise just a file path.
  659. Set the rc parameter examples.directory to the directory where we should
  660. look, if sample_data files are stored in a location different than
  661. default (which is 'mpl-data/sample_data` at the same level of 'matplotlib`
  662. Python module files).
  663. If the filename ends in .gz, the file is implicitly ungzipped.
  664. """
  665. import matplotlib
  666. if matplotlib.rcParams['examples.directory']:
  667. root = matplotlib.rcParams['examples.directory']
  668. else:
  669. root = os.path.join(os.path.dirname(__file__),
  670. "mpl-data", "sample_data")
  671. path = os.path.join(root, fname)
  672. if asfileobj:
  673. if (os.path.splitext(fname)[-1].lower() in
  674. ('.csv', '.xrc', '.txt')):
  675. mode = 'r'
  676. else:
  677. mode = 'rb'
  678. base, ext = os.path.splitext(fname)
  679. if ext == '.gz':
  680. return gzip.open(path, mode)
  681. else:
  682. return open(path, mode)
  683. else:
  684. return path
  685. def flatten(seq, scalarp=is_scalar_or_string):
  686. """
  687. Returns a generator of flattened nested containers
  688. For example:
  689. >>> from matplotlib.cbook import flatten
  690. >>> l = (('John', ['Hunter']), (1, 23), [[([42, (5, 23)], )]])
  691. >>> print(list(flatten(l)))
  692. ['John', 'Hunter', 1, 23, 42, 5, 23]
  693. By: Composite of Holger Krekel and Luther Blissett
  694. From: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/121294
  695. and Recipe 1.12 in cookbook
  696. """
  697. for item in seq:
  698. if scalarp(item):
  699. yield item
  700. else:
  701. for subitem in flatten(item, scalarp):
  702. yield subitem
  703. class Sorter(object):
  704. """
  705. Sort by attribute or item
  706. Example usage::
  707. sort = Sorter()
  708. list = [(1, 2), (4, 8), (0, 3)]
  709. dict = [{'a': 3, 'b': 4}, {'a': 5, 'b': 2}, {'a': 0, 'b': 0},
  710. {'a': 9, 'b': 9}]
  711. sort(list) # default sort
  712. sort(list, 1) # sort by index 1
  713. sort(dict, 'a') # sort a list of dicts by key 'a'
  714. """
  715. def _helper(self, data, aux, inplace):
  716. aux.sort()
  717. result = [data[i] for junk, i in aux]
  718. if inplace:
  719. data[:] = result
  720. return result
  721. def byItem(self, data, itemindex=None, inplace=1):
  722. if itemindex is None:
  723. if inplace:
  724. data.sort()
  725. result = data
  726. else:
  727. result = data[:]
  728. result.sort()
  729. return result
  730. else:
  731. aux = [(data[i][itemindex], i) for i in range(len(data))]
  732. return self._helper(data, aux, inplace)
  733. def byAttribute(self, data, attributename, inplace=1):
  734. aux = [(getattr(data[i], attributename), i) for i in range(len(data))]
  735. return self._helper(data, aux, inplace)
  736. # a couple of handy synonyms
  737. sort = byItem
  738. __call__ = byItem
  739. class Xlator(dict):
  740. """
  741. All-in-one multiple-string-substitution class
  742. Example usage::
  743. text = "Larry Wall is the creator of Perl"
  744. adict = {
  745. "Larry Wall" : "Guido van Rossum",
  746. "creator" : "Benevolent Dictator for Life",
  747. "Perl" : "Python",
  748. }
  749. print multiple_replace(adict, text)
  750. xlat = Xlator(adict)
  751. print xlat.xlat(text)
  752. """
  753. def _make_regex(self):
  754. """ Build re object based on the keys of the current dictionary """
  755. return re.compile("|".join(map(re.escape, list(six.iterkeys(self)))))
  756. def __call__(self, match):
  757. """ Handler invoked for each regex *match* """
  758. return self[match.group(0)]
  759. def xlat(self, text):
  760. """ Translate *text*, returns the modified text. """
  761. return self._make_regex().sub(self, text)
  762. def soundex(name, len=4):
  763. """ soundex module conforming to Odell-Russell algorithm """
  764. # digits holds the soundex values for the alphabet
  765. soundex_digits = '01230120022455012623010202'
  766. sndx = ''
  767. fc = ''
  768. # Translate letters in name to soundex digits
  769. for c in name.upper():
  770. if c.isalpha():
  771. if not fc:
  772. fc = c # Remember first letter
  773. d = soundex_digits[ord(c) - ord('A')]
  774. # Duplicate consecutive soundex digits are skipped
  775. if not sndx or (d != sndx[-1]):
  776. sndx += d
  777. # Replace first digit with first letter
  778. sndx = fc + sndx[1:]
  779. # Remove all 0s from the soundex code
  780. sndx = sndx.replace('0', '')
  781. # Return soundex code truncated or 0-padded to len characters
  782. return (sndx + (len * '0'))[:len]
  783. class Null(object):
  784. """ Null objects always and reliably "do nothing." """
  785. def __init__(self, *args, **kwargs):
  786. pass
  787. def __call__(self, *args, **kwargs):
  788. return self
  789. def __str__(self):
  790. return "Null()"
  791. def __repr__(self):
  792. return "Null()"
  793. if six.PY3:
  794. def __bool__(self):
  795. return 0
  796. else:
  797. def __nonzero__(self):
  798. return 0
  799. def __getattr__(self, name):
  800. return self
  801. def __setattr__(self, name, value):
  802. return self
  803. def __delattr__(self, name):
  804. return self
  805. def mkdirs(newdir, mode=0o777):
  806. """
  807. make directory *newdir* recursively, and set *mode*. Equivalent to ::
  808. > mkdir -p NEWDIR
  809. > chmod MODE NEWDIR
  810. """
  811. # this functionality is now in core python as of 3.2
  812. # LPY DROP
  813. if six.PY3:
  814. os.makedirs(newdir, mode=mode, exist_ok=True)
  815. else:
  816. try:
  817. os.makedirs(newdir, mode=mode)
  818. except OSError as exception:
  819. if exception.errno != errno.EEXIST:
  820. raise
  821. class GetRealpathAndStat(object):
  822. def __init__(self):
  823. self._cache = {}
  824. def __call__(self, path):
  825. result = self._cache.get(path)
  826. if result is None:
  827. realpath = os.path.realpath(path)
  828. if sys.platform == 'win32':
  829. stat_key = realpath
  830. else:
  831. stat = os.stat(realpath)
  832. stat_key = (stat.st_ino, stat.st_dev)
  833. result = realpath, stat_key
  834. self._cache[path] = result
  835. return result
  836. get_realpath_and_stat = GetRealpathAndStat()
  837. def dict_delall(d, keys):
  838. 'delete all of the *keys* from the :class:`dict` *d*'
  839. for key in keys:
  840. try:
  841. del d[key]
  842. except KeyError:
  843. pass
  844. class RingBuffer(object):
  845. """ class that implements a not-yet-full buffer """
  846. def __init__(self, size_max):
  847. self.max = size_max
  848. self.data = []
  849. class __Full:
  850. """ class that implements a full buffer """
  851. def append(self, x):
  852. """ Append an element overwriting the oldest one. """
  853. self.data[self.cur] = x
  854. self.cur = (self.cur + 1) % self.max
  855. def get(self):
  856. """ return list of elements in correct order """
  857. return self.data[self.cur:] + self.data[:self.cur]
  858. def append(self, x):
  859. """append an element at the end of the buffer"""
  860. self.data.append(x)
  861. if len(self.data) == self.max:
  862. self.cur = 0
  863. # Permanently change self's class from non-full to full
  864. self.__class__ = __Full
  865. def get(self):
  866. """ Return a list of elements from the oldest to the newest. """
  867. return self.data
  868. def __get_item__(self, i):
  869. return self.data[i % len(self.data)]
  870. def get_split_ind(seq, N):
  871. """
  872. *seq* is a list of words. Return the index into seq such that::
  873. len(' '.join(seq[:ind])<=N
  874. .
  875. """
  876. sLen = 0
  877. # todo: use Alex's xrange pattern from the cbook for efficiency
  878. for (word, ind) in zip(seq, xrange(len(seq))):
  879. sLen += len(word) + 1 # +1 to account for the len(' ')
  880. if sLen >= N:
  881. return ind
  882. return len(seq)
  883. def wrap(prefix, text, cols):
  884. 'wrap *text* with *prefix* at length *cols*'
  885. pad = ' ' * len(prefix.expandtabs())
  886. available = cols - len(pad)
  887. seq = text.split(' ')
  888. Nseq = len(seq)
  889. ind = 0
  890. lines = []
  891. while ind < Nseq:
  892. lastInd = ind
  893. ind += get_split_ind(seq[ind:], available)
  894. lines.append(seq[lastInd:ind])
  895. # add the prefix to the first line, pad with spaces otherwise
  896. ret = prefix + ' '.join(lines[0]) + '\n'
  897. for line in lines[1:]:
  898. ret += pad + ' '.join(line) + '\n'
  899. return ret
  900. # A regular expression used to determine the amount of space to
  901. # remove. It looks for the first sequence of spaces immediately
  902. # following the first newline, or at the beginning of the string.
  903. _find_dedent_regex = re.compile("(?:(?:\n\r?)|^)( *)\S")
  904. # A cache to hold the regexs that actually remove the indent.
  905. _dedent_regex = {}
  906. def dedent(s):
  907. """
  908. Remove excess indentation from docstring *s*.
  909. Discards any leading blank lines, then removes up to n whitespace
  910. characters from each line, where n is the number of leading
  911. whitespace characters in the first line. It differs from
  912. textwrap.dedent in its deletion of leading blank lines and its use
  913. of the first non-blank line to determine the indentation.
  914. It is also faster in most cases.
  915. """
  916. # This implementation has a somewhat obtuse use of regular
  917. # expressions. However, this function accounted for almost 30% of
  918. # matplotlib startup time, so it is worthy of optimization at all
  919. # costs.
  920. if not s: # includes case of s is None
  921. return ''
  922. match = _find_dedent_regex.match(s)
  923. if match is None:
  924. return s
  925. # This is the number of spaces to remove from the left-hand side.
  926. nshift = match.end(1) - match.start(1)
  927. if nshift == 0:
  928. return s
  929. # Get a regex that will remove *up to* nshift spaces from the
  930. # beginning of each line. If it isn't in the cache, generate it.
  931. unindent = _dedent_regex.get(nshift, None)
  932. if unindent is None:
  933. unindent = re.compile("\n\r? {0,%d}" % nshift)
  934. _dedent_regex[nshift] = unindent
  935. result = unindent.sub("\n", s).strip()
  936. return result
  937. def listFiles(root, patterns='*', recurse=1, return_folders=0):
  938. """
  939. Recursively list files
  940. from Parmar and Martelli in the Python Cookbook
  941. """
  942. import os.path
  943. import fnmatch
  944. # Expand patterns from semicolon-separated string to list
  945. pattern_list = patterns.split(';')
  946. results = []
  947. for dirname, dirs, files in os.walk(root):
  948. # Append to results all relevant files (and perhaps folders)
  949. for name in files:
  950. fullname = os.path.normpath(os.path.join(dirname, name))
  951. if return_folders or os.path.isfile(fullname):
  952. for pattern in pattern_list:
  953. if fnmatch.fnmatch(name, pattern):
  954. results.append(fullname)
  955. break
  956. # Block recursion if recursion was disallowed
  957. if not recurse:
  958. break
  959. return results
  960. def get_recursive_filelist(args):
  961. """
  962. Recurse all the files and dirs in *args* ignoring symbolic links
  963. and return the files as a list of strings
  964. """
  965. files = []
  966. for arg in args:
  967. if os.path.isfile(arg):
  968. files.append(arg)
  969. continue
  970. if os.path.isdir(arg):
  971. newfiles = listFiles(arg, recurse=1, return_folders=1)
  972. files.extend(newfiles)
  973. return [f for f in files if not os.path.islink(f)]
  974. def pieces(seq, num=2):
  975. "Break up the *seq* into *num* tuples"
  976. start = 0
  977. while 1:
  978. item = seq[start:start + num]
  979. if not len(item):
  980. break
  981. yield item
  982. start += num
  983. def exception_to_str(s=None):
  984. if six.PY3:
  985. sh = io.StringIO()
  986. else:
  987. sh = io.BytesIO()
  988. if s is not None:
  989. print(s, file=sh)
  990. traceback.print_exc(file=sh)
  991. return sh.getvalue()
  992. def allequal(seq):
  993. """
  994. Return *True* if all elements of *seq* compare equal. If *seq* is
  995. 0 or 1 length, return *True*
  996. """
  997. if len(seq) < 2:
  998. return True
  999. val = seq[0]
  1000. for i in xrange(1, len(seq)):
  1001. thisval = seq[i]
  1002. if thisval != val:
  1003. return False
  1004. return True
  1005. def alltrue(seq):
  1006. """
  1007. Return *True* if all elements of *seq* evaluate to *True*. If
  1008. *seq* is empty, return *False*.
  1009. """
  1010. if not len(seq):
  1011. return False
  1012. for val in seq:
  1013. if not val:
  1014. return False
  1015. return True
  1016. def onetrue(seq):
  1017. """
  1018. Return *True* if one element of *seq* is *True*. It *seq* is
  1019. empty, return *False*.
  1020. """
  1021. if not len(seq):
  1022. return False
  1023. for val in seq:
  1024. if val:
  1025. return True
  1026. return False
  1027. def allpairs(x):
  1028. """
  1029. return all possible pairs in sequence *x*
  1030. Condensed by Alex Martelli from this thread_ on c.l.python
  1031. .. _thread: http://groups.google.com/groups?q=all+pairs+group:*python*&hl=en&lr=&ie=UTF-8&selm=mailman.4028.1096403649.5135.python-list%40python.org&rnum=1
  1032. """
  1033. return [(s, f) for i, f in enumerate(x) for s in x[i + 1:]]
  1034. class maxdict(dict):
  1035. """
  1036. A dictionary with a maximum size; this doesn't override all the
  1037. relevant methods to contrain size, just setitem, so use with
  1038. caution
  1039. """
  1040. def __init__(self, maxsize):
  1041. dict.__init__(self)
  1042. self.maxsize = maxsize
  1043. self._killkeys = []
  1044. def __setitem__(self, k, v):
  1045. if k not in self:
  1046. if len(self) >= self.maxsize:
  1047. del self[self._killkeys[0]]
  1048. del self._killkeys[0]
  1049. self._killkeys.append(k)
  1050. dict.__setitem__(self, k, v)
  1051. class Stack(object):
  1052. """
  1053. Implement a stack where elements can be pushed on and you can move
  1054. back and forth. But no pop. Should mimic home / back / forward
  1055. in a browser
  1056. """
  1057. def __init__(self, default=None):
  1058. self.clear()
  1059. self._default = default
  1060. def __call__(self):
  1061. 'return the current element, or None'
  1062. if not len(self._elements):
  1063. return self._default
  1064. else:
  1065. return self._elements[self._pos]
  1066. def __len__(self):
  1067. return self._elements.__len__()
  1068. def __getitem__(self, ind):
  1069. return self._elements.__getitem__(ind)
  1070. def forward(self):
  1071. 'move the position forward and return the current element'
  1072. N = len(self._elements)
  1073. if self._pos < N - 1:
  1074. self._pos += 1
  1075. return self()
  1076. def back(self):
  1077. 'move the position back and return the current element'
  1078. if self._pos > 0:
  1079. self._pos -= 1
  1080. return self()
  1081. def push(self, o):
  1082. """
  1083. push object onto stack at current position - all elements
  1084. occurring later than the current position are discarded
  1085. """
  1086. self._elements = self._elements[:self._pos + 1]
  1087. self._elements.append(o)
  1088. self._pos = len(self._elements) - 1
  1089. return self()
  1090. def home(self):
  1091. 'push the first element onto the top of the stack'
  1092. if not len(self._elements):
  1093. return
  1094. self.push(self._elements[0])
  1095. return self()
  1096. def empty(self):
  1097. return len(self._elements) == 0
  1098. def clear(self):
  1099. 'empty the stack'
  1100. self._pos = -1
  1101. self._elements = []
  1102. def bubble(self, o):
  1103. """
  1104. raise *o* to the top of the stack and return *o*. *o* must be
  1105. in the stack
  1106. """
  1107. if o not in self._elements:
  1108. raise ValueError('Unknown element o')
  1109. old = self._elements[:]
  1110. self.clear()
  1111. bubbles = []
  1112. for thiso in old:
  1113. if thiso == o:
  1114. bubbles.append(thiso)
  1115. else:
  1116. self.push(thiso)
  1117. for thiso in bubbles:
  1118. self.push(o)
  1119. return o
  1120. def remove(self, o):
  1121. 'remove element *o* from the stack'
  1122. if o not in self._elements:
  1123. raise ValueError('Unknown element o')
  1124. old = self._elements[:]
  1125. self.clear()
  1126. for thiso in old:
  1127. if thiso == o:
  1128. continue
  1129. else:
  1130. self.push(thiso)
  1131. def popall(seq):
  1132. 'empty a list'
  1133. for i in xrange(len(seq)):
  1134. seq.pop()
  1135. def finddir(o, match, case=False):
  1136. """
  1137. return all attributes of *o* which match string in match. if case
  1138. is True require an exact case match.
  1139. """
  1140. if case:
  1141. names = [(name, name) for name in dir(o) if is_string_like(name)]
  1142. else:
  1143. names = [(name.lower(), name) for name in dir(o)
  1144. if is_string_like(name)]
  1145. match = match.lower()
  1146. return [orig for name, orig in names if name.find(match) >= 0]
  1147. def reverse_dict(d):
  1148. 'reverse the dictionary -- may lose data if values are not unique!'
  1149. return dict([(v, k) for k, v in six.iteritems(d)])
  1150. def restrict_dict(d, keys):
  1151. """
  1152. Return a dictionary that contains those keys that appear in both
  1153. d and keys, with values from d.
  1154. """
  1155. return dict([(k, v) for (k, v) in six.iteritems(d) if k in keys])
  1156. def report_memory(i=0): # argument may go away
  1157. 'return the memory consumed by process'
  1158. from matplotlib.compat.subprocess import Popen, PIPE
  1159. pid = os.getpid()
  1160. if sys.platform == 'sunos5':
  1161. try:
  1162. a2 = Popen('ps -p %d -o osz' % pid, shell=True,
  1163. stdout=PIPE).stdout.readlines()
  1164. except OSError:
  1165. raise NotImplementedError(
  1166. "report_memory works on Sun OS only if "
  1167. "the 'ps' program is found")
  1168. mem = int(a2[-1].strip())
  1169. elif sys.platform.startswith('linux'):
  1170. try:
  1171. a2 = Popen('ps -p %d -o rss,sz' % pid, shell=True,
  1172. stdout=PIPE).stdout.readlines()
  1173. except OSError:
  1174. raise NotImplementedError(
  1175. "report_memory works on Linux only if "
  1176. "the 'ps' program is found")
  1177. mem = int(a2[1].split()[1])
  1178. elif sys.platform.startswith('darwin'):
  1179. try:
  1180. a2 = Popen('ps -p %d -o rss,vsz' % pid, shell=True,
  1181. stdout=PIPE).stdout.readlines()
  1182. except OSError:
  1183. raise NotImplementedError(
  1184. "report_memory works on Mac OS only if "
  1185. "the 'ps' program is found")
  1186. mem = int(a2[1].split()[0])
  1187. elif sys.platform.startswith('win'):
  1188. try:
  1189. a2 = Popen(["tasklist", "/nh", "/fi", "pid eq %d" % pid],
  1190. stdout=PIPE).stdout.read()
  1191. except OSError:
  1192. raise NotImplementedError(
  1193. "report_memory works on Windows only if "
  1194. "the 'tasklist' program is found")
  1195. mem = int(a2.strip().split()[-2].replace(',', ''))
  1196. else:
  1197. raise NotImplementedError(
  1198. "We don't have a memory monitor for %s" % sys.platform)
  1199. return mem
  1200. _safezip_msg = 'In safezip, len(args[0])=%d but len(args[%d])=%d'
  1201. def safezip(*args):
  1202. 'make sure *args* are equal len before zipping'
  1203. Nx = len(args[0])
  1204. for i, arg in enumerate(args[1:]):
  1205. if len(arg) != Nx:
  1206. raise ValueError(_safezip_msg % (Nx, i + 1, len(arg)))
  1207. return list(zip(*args))
  1208. def issubclass_safe(x, klass):
  1209. 'return issubclass(x, klass) and return False on a TypeError'
  1210. try:
  1211. return issubclass(x, klass)
  1212. except TypeError:
  1213. return False
  1214. def safe_masked_invalid(x):
  1215. x = np.asanyarray(x)
  1216. try:
  1217. xm = np.ma.masked_invalid(x, copy=False)
  1218. xm.shrink_mask()
  1219. except TypeError:
  1220. return x
  1221. return xm
  1222. class MemoryMonitor(object):
  1223. def __init__(self, nmax=20000):
  1224. self._nmax = nmax
  1225. self._mem = np.zeros((self._nmax,), np.int32)
  1226. self.clear()
  1227. def clear(self):
  1228. self._n = 0
  1229. self._overflow = False
  1230. def __call__(self):
  1231. mem = report_memory()
  1232. if self._n < self._nmax:
  1233. self._mem[self._n] = mem
  1234. self._n += 1
  1235. else:
  1236. self._overflow = True
  1237. return mem
  1238. def report(self, segments=4):
  1239. n = self._n
  1240. segments = min(n, segments)
  1241. dn = int(n / segments)
  1242. ii = list(xrange(0, n, dn))
  1243. ii[-1] = n - 1
  1244. print()
  1245. print('memory report: i, mem, dmem, dmem/nloops')
  1246. print(0, self._mem[0])
  1247. for i in range(1, len(ii)):
  1248. di = ii[i] - ii[i - 1]
  1249. if di == 0:
  1250. continue
  1251. dm = self._mem[ii[i]] - self._mem[ii[i - 1]]
  1252. print('%5d %5d %3d %8.3f' % (ii[i], self._mem[ii[i]],
  1253. dm, dm / float(di)))
  1254. if self._overflow:
  1255. print("Warning: array size was too small for the number of calls.")
  1256. def xy(self, i0=0, isub=1):
  1257. x = np.arange(i0, self._n, isub)
  1258. return x, self._mem[i0:self._n:isub]
  1259. def plot(self, i0=0, isub=1, fig=None):
  1260. if fig is None:
  1261. from .pylab import figure
  1262. fig = figure()
  1263. ax = fig.add_subplot(111)
  1264. ax.plot(*self.xy(i0, isub))
  1265. fig.canvas.draw()
  1266. def print_cycles(objects, outstream=sys.stdout, show_progress=False):
  1267. """
  1268. *objects*
  1269. A list of objects to find cycles in. It is often useful to
  1270. pass in gc.garbage to find the cycles that are preventing some
  1271. objects from being garbage collected.
  1272. *outstream*
  1273. The stream for output.
  1274. *show_progress*
  1275. If True, print the number of objects reached as they are found.
  1276. """
  1277. import gc
  1278. from types import FrameType
  1279. def print_path(path):
  1280. for i, step in enumerate(path):
  1281. # next "wraps around"
  1282. next = path[(i + 1) % len(path)]
  1283. outstream.write(" %s -- " % str(type(step)))
  1284. if isinstance(step, dict):
  1285. for key, val in six.iteritems(step):
  1286. if val is next:
  1287. outstream.write("[%s]" % repr(key))
  1288. break
  1289. if key is next:
  1290. outstream.write("[key] = %s" % repr(val))
  1291. break
  1292. elif isinstance(step, list):
  1293. outstream.write("[%d]" % step.index(next))
  1294. elif isinstance(step, tuple):
  1295. outstream.write("( tuple )")
  1296. else:
  1297. outstream.write(repr(step))
  1298. outstream.write(" ->\n")
  1299. outstream.write("\n")
  1300. def recurse(obj, start, all, current_path):
  1301. if show_progress:
  1302. outstream.write("%d\r" % len(all))
  1303. all[id(obj)] = None
  1304. referents = gc.get_referents(obj)
  1305. for referent in referents:
  1306. # If we've found our way back to the start, this is
  1307. # a cycle, so print it out
  1308. if referent is start:
  1309. print_path(current_path)
  1310. # Don't go back through the original list of objects, or
  1311. # through temporary references to the object, since those
  1312. # are just an artifact of the cycle detector itself.
  1313. elif referent is objects or isinstance(referent, FrameType):
  1314. continue
  1315. # We haven't seen this object before, so recurse
  1316. elif id(referent) not in all:
  1317. recurse(referent, start, all, current_path + [obj])
  1318. for obj in objects:
  1319. outstream.write("Examining: %r\n" % (obj,))
  1320. recurse(obj, obj, {}, [])
  1321. class Grouper(object):
  1322. """
  1323. This class provides a lightweight way to group arbitrary objects
  1324. together into disjoint sets when a full-blown graph data structure
  1325. would be overkill.
  1326. Objects can be joined using :meth:`join`, tested for connectedness
  1327. using :meth:`joined`, and all disjoint sets can be retreived by
  1328. using the object as an iterator.
  1329. The objects being joined must be hashable and weak-referenceable.
  1330. For example:
  1331. >>> from matplotlib.cbook import Grouper
  1332. >>> class Foo(object):
  1333. ... def __init__(self, s):
  1334. ... self.s = s
  1335. ... def __repr__(self):
  1336. ... return self.s
  1337. ...
  1338. >>> a, b, c, d, e, f = [Foo(x) for x in 'abcdef']
  1339. >>> grp = Grouper()
  1340. >>> grp.join(a, b)
  1341. >>> grp.join(b, c)
  1342. >>> grp.join(d, e)
  1343. >>> sorted(map(tuple, grp))
  1344. [(a, b, c), (d, e)]
  1345. >>> grp.joined(a, b)
  1346. True
  1347. >>> grp.joined(a, c)
  1348. True
  1349. >>> grp.joined(a, d)
  1350. False
  1351. """
  1352. def __init__(self, init=()):
  1353. mapping = self._mapping = {}
  1354. for x in init:
  1355. mapping[ref(x)] = [ref(x)]
  1356. def __contains__(self, item):
  1357. return ref(item) in self._mapping
  1358. def clean(self):
  1359. """
  1360. Clean dead weak references from the dictionary
  1361. """
  1362. mapping = self._mapping
  1363. to_drop = [key for key in mapping if key() is None]
  1364. for key in to_drop:
  1365. val = mapping.pop(key)
  1366. val.remove(key)
  1367. def join(self, a, *args):
  1368. """
  1369. Join given arguments