Skip to content

add option_context context manager #2516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from Dec 13, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion RELEASE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pandas 0.10.0
instead. This is a legacy hack and can lead to subtle bugs.
- inf/-inf are no longer considered as NA by isnull/notnull. To be clear, this
is legacy cruft from early pandas. This behavior can be globally re-enabled
using pandas.core.common.use_inf_as_na (#2050, #1919)
using the new option ``mode.use_inf_as_null`` (#2050, #1919)
- ``pandas.merge`` will now default to ``sort=False``. For many use cases
sorting the join keys is not necessary, and doing it by default is wasteful
- ``names`` handling in file parsing: if explicit column `names` passed,
Expand Down
2 changes: 1 addition & 1 deletion doc/source/missing_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ arise and we wish to also consider that "missing" or "null".

Until recently, for legacy reasons ``inf`` and ``-inf`` were also
considered to be "null" in computations. This is no longer the case by
default; use the :func: `~pandas.core.common.use_inf_as_null` function to recover it.
default; use the ``mode.use_inf_as_null`` option to recover it.

.. _missing.isnull:

Expand Down
7 changes: 4 additions & 3 deletions pandas/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ def isnull_old(obj):
else:
return obj is None

def use_inf_as_null(flag):
'''
def _use_inf_as_null(key):
'''Option change callback for null/inf behaviour
Choose which replacement for numpy.isnan / -numpy.isfinite is used.

Parameters
Expand All @@ -113,6 +113,7 @@ def use_inf_as_null(flag):
* http://stackoverflow.com/questions/4859217/
programmatically-creating-variables-in-python/4859312#4859312
'''
flag = get_option(key)
if flag == True:
globals()['isnull'] = isnull_old
else:
Expand Down Expand Up @@ -1179,7 +1180,7 @@ def in_interactive_session():
returns True if running under python/ipython interactive shell
"""
import __main__ as main
return not hasattr(main, '__file__') or get_option('test.interactive')
return not hasattr(main, '__file__') or get_option('mode.sim_interactive')

def in_qtconsole():
"""
Expand Down
66 changes: 46 additions & 20 deletions pandas/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
- all options in a certain sub - namespace can be reset at once.
- the user can set / get / reset or ask for the description of an option.
- a developer can register and mark an option as deprecated.

- you can register a callback to be invoked when the the option value
is set or reset. Changing the stored value is considered misuse, but
is not verboten.

Implementation
==============
Expand Down Expand Up @@ -54,7 +56,7 @@
import warnings

DeprecatedOption = namedtuple('DeprecatedOption', 'key msg rkey removal_ver')
RegisteredOption = namedtuple('RegisteredOption', 'key defval doc validator')
RegisteredOption = namedtuple('RegisteredOption', 'key defval doc validator cb')

_deprecated_options = {} # holds deprecated option metdata
_registered_options = {} # holds registered option metdata
Expand All @@ -65,37 +67,33 @@
##########################################
# User API

def _get_option(pat):
def _get_single_key(pat, silent):
keys = _select_options(pat)
if len(keys) == 0:
_warn_if_deprecated(pat)
if not silent:
_warn_if_deprecated(pat)
raise KeyError('No such keys(s)')
if len(keys) > 1:
raise KeyError('Pattern matched multiple keys')
key = keys[0]

_warn_if_deprecated(key)
if not silent:
_warn_if_deprecated(key)

key = _translate_key(key)

return key

def _get_option(pat, silent=False):
key = _get_single_key(pat,silent)

# walk the nested dict
root, k = _get_root(key)

return root[k]


def _set_option(pat, value):

keys = _select_options(pat)
if len(keys) == 0:
_warn_if_deprecated(pat)
raise KeyError('No such keys(s)')
if len(keys) > 1:
raise KeyError('Pattern matched multiple keys')
key = keys[0]

_warn_if_deprecated(key)
key = _translate_key(key)
def _set_option(pat, value, silent=False):
key = _get_single_key(pat,silent)

o = _get_registered_option(key)
if o and o.validator:
Expand All @@ -105,6 +103,9 @@ def _set_option(pat, value):
root, k = _get_root(key)
root[k] = value

if o.cb:
o.cb(key)


def _describe_option(pat='', _print_desc=True):

Expand Down Expand Up @@ -270,7 +271,29 @@ def __doc__(self):
######################################################
# Functions for use by pandas developers, in addition to User - api

def register_option(key, defval, doc='', validator=None):
class option_context(object):
def __init__(self,*args):
assert len(args) % 2 == 0 and len(args)>=2, \
"Need to invoke as option_context(pat,val,[(pat,val),..))."
ops = zip(args[::2],args[1::2])
undo=[]
for pat,val in ops:
undo.append((pat,_get_option(pat,silent=True)))

self.undo = undo

for pat,val in ops:
_set_option(pat,val,silent=True)

def __enter__(self):
pass

def __exit__(self, *args):
if self.undo:
for pat, val in self.undo:
_set_option(pat, val)

def register_option(key, defval, doc='', validator=None, cb=None):
"""Register an option in the package-wide pandas config object

Parameters
Expand All @@ -280,6 +303,9 @@ def register_option(key, defval, doc='', validator=None):
doc - a string description of the option
validator - a function of a single argument, should raise `ValueError` if
called with a value which is not a legal value for the option.
cb - a function of a single argument "key", which is called
immediately after an option value is set/reset. key is
the full name of the option.

Returns
-------
Expand Down Expand Up @@ -321,7 +347,7 @@ def register_option(key, defval, doc='', validator=None):

# save the option metadata
_registered_options[key] = RegisteredOption(key=key, defval=defval,
doc=doc, validator=validator)
doc=doc, validator=validator,cb=cb)


def deprecate_option(key, msg=None, rkey=None, removal_ver=None):
Expand Down
23 changes: 20 additions & 3 deletions pandas/core/config_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,27 @@
cf.register_option('expand_frame_repr', True, pc_expand_repr_doc)
cf.register_option('line_width', 80, pc_line_width_doc)

tc_interactive_doc="""
tc_sim_interactive_doc="""
: boolean
Default False
Whether to simulate interactive mode for purposes of testing
"""
with cf.config_prefix('test'):
cf.register_option('interactive', False, tc_interactive_doc)
with cf.config_prefix('mode'):
cf.register_option('sim_interactive', False, tc_sim_interactive_doc)

use_inf_as_null_doc="""
: boolean
True means treat None, NaN, INF, -INF as null (old way),
False means None and NaN are null, but INF, -INF are not null
(new way).
"""

# we don't want to start importing evrything at the global context level
# or we'll hit circular deps.
def use_inf_as_null_cb(key):
from pandas.core.common import _use_inf_as_null
_use_inf_as_null(key)

with cf.config_prefix('mode'):
cf.register_option('use_inf_as_null', False, use_inf_as_null_doc,
cb=use_inf_as_null_cb)
26 changes: 13 additions & 13 deletions pandas/tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import unittest

from pandas import Series, DataFrame, date_range, DatetimeIndex
from pandas.core.common import notnull, isnull, use_inf_as_null
from pandas.core.common import notnull, isnull
import pandas.core.common as com
import pandas.util.testing as tm
import pandas.core.config as cf

import numpy as np

Expand All @@ -29,20 +30,19 @@ def test_notnull():
assert not notnull(None)
assert not notnull(np.NaN)

use_inf_as_null(False)
assert notnull(np.inf)
assert notnull(-np.inf)
with cf.option_context("mode.use_inf_as_null",False):
assert notnull(np.inf)
assert notnull(-np.inf)

use_inf_as_null(True)
assert not notnull(np.inf)
assert not notnull(-np.inf)
with cf.option_context("mode.use_inf_as_null",True):
assert not notnull(np.inf)
assert not notnull(-np.inf)

use_inf_as_null(False)

float_series = Series(np.random.randn(5))
obj_series = Series(np.random.randn(5), dtype=object)
assert(isinstance(notnull(float_series), Series))
assert(isinstance(notnull(obj_series), Series))
with cf.option_context("mode.use_inf_as_null",False):
float_series = Series(np.random.randn(5))
obj_series = Series(np.random.randn(5), dtype=object)
assert(isinstance(notnull(float_series), Series))
assert(isinstance(notnull(obj_series), Series))

def test_isnull():
assert not isnull(1.)
Expand Down
40 changes: 40 additions & 0 deletions pandas/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,46 @@ def test_config_prefix(self):
self.assertEqual(self.cf.get_option('a'), 1)
self.assertEqual(self.cf.get_option('b'), 2)

def test_callback(self):
k=[None]
v=[None]
def callback(key):
k.append(key)
v.append(self.cf.get_option(key))

self.cf.register_option('d.a', 'foo',cb=callback)
self.cf.register_option('d.b', 'foo',cb=callback)

del k[-1],v[-1]
self.cf.set_option("d.a","fooz")
self.assertEqual(k[-1],"d.a")
self.assertEqual(v[-1],"fooz")

del k[-1],v[-1]
self.cf.set_option("d.b","boo")
self.assertEqual(k[-1],"d.b")
self.assertEqual(v[-1],"boo")

del k[-1],v[-1]
self.cf.reset_option("d.b")
self.assertEqual(k[-1],"d.b")


def test_set_ContextManager(self):
def eq(val):
self.assertEqual(self.cf.get_option("a"),val)

self.cf.register_option('a',0)
eq(0)
with self.cf.option_context("a",15):
eq(15)
with self.cf.option_context("a",25):
eq(25)
eq(15)
eq(0)

self.cf.set_option("a",17)
eq(17)

# fmt.reset_printoptions and fmt.set_printoptions were altered
# to use core.config, test_format exercises those paths.
Loading