Skip to content
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

Expand cycler() signature. #14

Merged
merged 4 commits into from
Aug 21, 2015
Merged
Show file tree
Hide file tree
Changes from 2 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
67 changes: 56 additions & 11 deletions cycler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
You can add cyclers::

from cycler import cycler
cc = (cycler('color', list('rgb')) +
cycler('linestyle', ['-', '--', '-.']))
cc = (cycler(color=list('rgb')) +
cycler(linestyle=['-', '--', '-.']))
for d in cc:
print(d)

Expand All @@ -22,8 +22,8 @@
You can multiply cyclers::

from cycler import cycler
cc = (cycler('color', list('rgb')) *
cycler('linestyle', ['-', '--', '-.']))
cc = (cycler(color=list('rgb')) *
cycler(linestyle=['-', '--', '-.']))
for d in cc:
print(d)

Expand Down Expand Up @@ -164,8 +164,8 @@ def __getitem__(self, key):
# TODO : maybe add numpy style fancy slicing
if isinstance(key, slice):
trans = self._transpose()
return reduce(add, (cycler(k, v[key])
for k, v in six.iteritems(trans)))
return cycler(**dict((k, v[key])
for k, v in six.iteritems(trans)))
else:
raise ValueError("Can only use slices with Cycler.__getitem__")

Expand Down Expand Up @@ -203,8 +203,8 @@ def __mul__(self, other):
return Cycler(self, other, product)
elif isinstance(other, int):
trans = self._transpose()
return reduce(add, (cycler(k, v*other)
for k, v in six.iteritems(trans)))
return cycler(**dict((k, v*other)
for k, v in six.iteritems(trans)))
else:
return NotImplemented

Expand Down Expand Up @@ -268,7 +268,7 @@ def __repr__(self):
if self._right is None:
lab = self.keys.pop()
itr = list(v[lab] for v in self)
return "cycler({lab!r}, {itr!r})".format(lab=lab, itr=itr)
return "cycler({lab}={itr!r})".format(lab=lab, itr=itr)
else:
op = op_map.get(self._op, '?')
msg = "({left!r} {op} {right!r})"
Expand Down Expand Up @@ -329,10 +329,55 @@ def simplify(self):
# I would believe that there is some performance implications

trans = self._transpose()
return reduce(add, (cycler(k, v) for k, v in six.iteritems(trans)))
return cycler(**dict((k, v) for k, v in six.iteritems(trans)))


def cycler(label, itr):
def cycler(*args, **kwargs):
"""
Create a new `Cycler` object from the combination of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> "from a single positional argument or from one or more keyword arguments."

positional arguments or keyword arguments.

cycler(arg)
cycler(label1=itr1[, label2=iter2[, ...]])

Form 1 simply copies a given `Cycler` object.
Form 2 composes a `Cycler` as an inner product of the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"inner product" might not mean much to many docstring readers here. Maybe add something like, "All iterables are cycled simultaneously."

pairs of keyword arguments.

Parameters
----------
label : str
The property key.

itr : iterable
Finite length iterable of the property values.

Returns
-------
cycler : Cycler
New `Cycler` for the given property

"""
if args and kwargs:
raise TypeError("cyl() can only accept positional OR keyword "
"arguments -- not both.")

if len(args) == 1:
if not isinstance(args[0], Cycler):
raise TypeError("If only one positional argument given, it must "
" be a Cycler instance.")
return copy.copy(args[0])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want to do a keep copy, I can in to some really weird bugs earlier due to underlying dict objects getting shared. I think this will play badly with the re-labeling functionality.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want me to use copy.deepcopy() everywhere, or just here?

elif len(args) > 1:
raise TypeError("Only a single Cycler can be accepted as the lone "
"positional argument. Use keyword arguments instead.")

if kwargs:
return reduce(add, (_cycler(k, v) for k, v in six.iteritems(kwargs)))

raise TypeError("Must have at least a positional OR keyword arguments")


def _cycler(label, itr):
"""
Create a new `Cycler` object from a property name and
iterable of values.
Expand Down
31 changes: 21 additions & 10 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ hashable (as it will eventually be used as the key in a :obj:`dict`).
from cycler import cycler


color_cycle = cycler('color', ['r', 'g', 'b'])
color_cycle = cycler(color=['r', 'g', 'b'])
color_cycle

The `Cycler` knows it's length and keys:
Expand All @@ -61,12 +61,12 @@ the label
for v in color_cycle:
print(v)

`Cycler` objects can be passed as the second argument to :func:`cycler`
`Cycler` objects can be passed as the argument to :func:`cycler`
which returns a new `Cycler` with a new label, but the same values.

.. ipython:: python

cycler('ec', color_cycle)
cycler(ec=color_cycle)


Iterating over a `Cycler` results in the finite list of entries, to
Expand Down Expand Up @@ -94,12 +94,12 @@ Equal length `Cycler` s with different keys can be added to get the

.. ipython:: python

lw_cycle = cycler('lw', range(1, 4))
lw_cycle = cycler(lw=range(1, 4))

wc = lw_cycle + color_cycle

The result has the same length and has keys which are the union of the
two input `Cycler` s.
two input `Cycler`'s.

.. ipython:: python

Expand All @@ -123,6 +123,17 @@ As with arithmetic, addition is commutative
for j, (a, b) in enumerate(zip(lw_c, c_lw)):
print('({j}) A: {A!r} B: {B!r}'.format(j=j, A=a, B=b))

For convenience, the :func:`cycler` function can have multiple
key-value pairs and will automatically compose them into a single
`Cycler` via addition

.. ipython:: python

wc = cycler(c=['r', 'g', 'b'], lw=range(3))

for s in wc:
print(s)


Multiplication
~~~~~~~~~~~~~~
Expand All @@ -131,7 +142,7 @@ Any pair of `Cycler` can be multiplied

.. ipython:: python

m_cycle = cycler('marker', ['s', 'o'])
m_cycle = cycler(marker=['s', 'o'])

m_c = m_cycle * color_cycle

Expand Down Expand Up @@ -199,7 +210,7 @@ We can use `Cycler` instances to cycle over one or more ``kwarg`` to
figsize=(8, 4))
x = np.arange(10)

color_cycle = cycler('c', ['r', 'g', 'b'])
color_cycle = cycler(c=['r', 'g', 'b'])

for i, sty in enumerate(color_cycle):
ax1.plot(x, x*(i+1), **sty)
Expand All @@ -219,7 +230,7 @@ We can use `Cycler` instances to cycle over one or more ``kwarg`` to
figsize=(8, 4))
x = np.arange(10)

color_cycle = cycler('c', ['r', 'g', 'b'])
color_cycle = cycler(c=['r', 'g', 'b'])
ls_cycle = cycler('ls', ['-', '--'])
lw_cycle = cycler('lw', range(1, 4))

Expand All @@ -243,14 +254,14 @@ A :obj:`ValueError` is raised if unequal length `Cycler` s are added together
.. ipython:: python
:okexcept:

cycler('c', ['r', 'g', 'b']) + cycler('ls', ['-', '--'])
cycler(c=['r', 'g', 'b']) + cycler(ls=['-', '--'])

or if two cycles which have overlapping keys are composed

.. ipython:: python
:okexcept:

color_cycle = cycler('c', ['r', 'g', 'b'])
color_cycle = cycler(c=['r', 'g', 'b'])

color_cycle + color_cycle

Expand Down
77 changes: 44 additions & 33 deletions test_cycler.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@ def _cycles_equal(c1, c2):


def test_creation():
c = cycler('c', 'rgb')
c = cycler(c='rgb')
yield _cycler_helper, c, 3, ['c'], [['r', 'g', 'b']]
c = cycler('c', list('rgb'))
c = cycler(c=list('rgb'))
yield _cycler_helper, c, 3, ['c'], [['r', 'g', 'b']]
c = cycler(cycler(c='rgb'))
yield _cycler_helper, c, 3, ['c'], [['r', 'g', 'b']]


def test_compose():
c1 = cycler('c', 'rgb')
c2 = cycler('lw', range(3))
c3 = cycler('lw', range(15))
c1 = cycler(c='rgb')
c2 = cycler(lw=range(3))
c3 = cycler(lw=range(15))
# addition
yield _cycler_helper, c1+c2, 3, ['c', 'lw'], [list('rgb'), range(3)]
yield _cycler_helper, c2+c1, 3, ['c', 'lw'], [list('rgb'), range(3)]
Expand All @@ -54,75 +56,84 @@ def test_compose():


def test_inplace():
c1 = cycler('c', 'rgb')
c2 = cycler('lw', range(3))
c1 = cycler(c='rgb')
c2 = cycler(lw=range(3))
c2 += c1
yield _cycler_helper, c2, 3, ['c', 'lw'], [list('rgb'), range(3)]

c3 = cycler('c', 'rgb')
c4 = cycler('lw', range(3))
c3 = cycler(c='rgb')
c4 = cycler(lw=range(3))
c3 *= c4
target = zip(*product(list('rgb'), range(3)))
yield (_cycler_helper, c3, 9, ['c', 'lw'], target)


def test_constructor():
c1 = cycler('c', 'rgb')
c2 = cycler('ec', c1)
c1 = cycler(c='rgb')
c2 = cycler(ec=c1)
yield _cycler_helper, c1+c2, 3, ['c', 'ec'], [['r', 'g', 'b']]*2
c3 = cycler('c', c1)
c3 = cycler(c=c1)
yield _cycler_helper, c3+c2, 3, ['c', 'ec'], [['r', 'g', 'b']]*2

# addition using cycler()
yield (_cycler_helper, cycler(c='rgb', lw=range(3)),
3, ['c', 'lw'], [list('rgb'), range(3)])
yield (_cycler_helper, cycler(lw=range(3), c='rgb'),
3, ['c', 'lw'], [list('rgb'), range(3)])
# Purposely mixing them
yield (_cycler_helper, cycler(c=range(3), lw=c1),
3, ['c', 'lw'], [range(3), list('rgb')])


def test_failures():
c1 = cycler('c', 'rgb')
c2 = cycler('c', c1)
c1 = cycler(c='rgb')
c2 = cycler(c=c1)
assert_raises(ValueError, add, c1, c2)
assert_raises(ValueError, iadd, c1, c2)
assert_raises(ValueError, mul, c1, c2)
assert_raises(ValueError, imul, c1, c2)

c3 = cycler('ec', c1)
c3 = cycler(ec=c1)

assert_raises(ValueError, cycler, 'c', c2 + c3)
assert_raises(ValueError, cycler, c=c2+c3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should have both of these tests 🐑

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never mind



def test_simplify():
c1 = cycler('c', 'rgb')
c2 = cycler('ec', c1)
c1 = cycler(c='rgb')
c2 = cycler(ec=c1)
for c in [c1 * c2, c2 * c1, c1 + c2]:
yield _cycles_equal, c, c.simplify()


def test_multiply():
c1 = cycler('c', 'rgb')
c1 = cycler(c='rgb')
yield _cycler_helper, 2*c1, 6, ['c'], ['rgb'*2]

c2 = cycler('ec', c1)
c2 = cycler(ec=c1)
c3 = c1 * c2

yield _cycles_equal, 2*c3, c3*2


def test_mul_fails():
c1 = cycler('c', 'rgb')
c1 = cycler(c='rgb')
assert_raises(TypeError, mul, c1, 2.0)
assert_raises(TypeError, mul, c1, 'a')
assert_raises(TypeError, mul, c1, [])


def test_getitem():
c1 = cycler('lw', range(15))
c1 = cycler(lw=range(15))
widths = list(range(15))
for slc in (slice(None, None, None),
slice(None, None, -1),
slice(1, 5, None),
slice(0, 5, 2)):
yield _cycles_equal, c1[slc], cycler('lw', widths[slc])
yield _cycles_equal, c1[slc], cycler(lw=widths[slc])


def test_fail_getime():
c1 = cycler('lw', range(15))
c1 = cycler(lw=range(15))
assert_raises(ValueError, Cycler.__getitem__, c1, 0)
assert_raises(ValueError, Cycler.__getitem__, c1, [0, 1])

Expand All @@ -135,11 +146,11 @@ def _repr_tester_helper(rpr_func, cyc, target_repr):


def test_repr():
c = cycler('c', 'rgb')
c2 = cycler('lw', range(3))
c = cycler(c='rgb')
c2 = cycler(lw=range(3))

c_sum_rpr = "(cycler('c', ['r', 'g', 'b']) + cycler('lw', [0, 1, 2]))"
c_prod_rpr = "(cycler('c', ['r', 'g', 'b']) * cycler('lw', [0, 1, 2]))"
c_sum_rpr = "(cycler(c=['r', 'g', 'b']) + cycler(lw=[0, 1, 2]))"
c_prod_rpr = "(cycler(c=['r', 'g', 'b']) * cycler(lw=[0, 1, 2]))"

yield _repr_tester_helper, '__repr__', c + c2, c_sum_rpr
yield _repr_tester_helper, '__repr__', c * c2, c_prod_rpr
Expand All @@ -152,7 +163,7 @@ def test_repr():


def test_call():
c = cycler('c', 'rgb')
c = cycler(c='rgb')
c_cycle = c()
assert_true(isinstance(c_cycle, cycle))
j = 0
Expand All @@ -171,14 +182,14 @@ def _eq_test_helper(a, b, res):


def test_eq():
a = cycler('c', 'rgb')
b = cycler('c', 'rgb')
a = cycler(c='rgb')
b = cycler(c='rgb')
yield _eq_test_helper, a, b, True
yield _eq_test_helper, a, b[::-1], False
c = cycler('lw', range(3))
c = cycler(lw=range(3))
yield _eq_test_helper, a+c, c+a, True
yield _eq_test_helper, a+c, c+b, True
yield _eq_test_helper, a*c, c*a, False
yield _eq_test_helper, a, c, False
d = cycler('c', 'ymk')
d = cycler(c='ymk')
yield _eq_test_helper, b, d, False