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
Consider making side_effect(file_obj=) arg more generic #114
Comments
Hmm, interesting. (1) This makes me think that at the minimum we should change the documentation (and maybe the argument name) to be more generic - it works for anything that needs to be closed, not just file objects. (2) it does seem like files are a justifiably special case with (3) Adding a generic "do this thing after you're done I'll look at the language for (1) and noodle around with (3). |
I thought the "well if you want to run a generic function after, why not before" was a reductio ad absurdum, but now that I've implemented it I kind of like it? See #115. |
I came to the same conclusion moments after my head hit the pillow: why not do consume(print(x, file=output) for output in with_iter(open("temp.tmp","w")) for x in range(3)). |
I am with Erik on the fact that we are basically reinventing context management. Erik's suggestion was one of the first I tried, but it fails. In [1]: [print(x, file=out) for out in with_iter(open('temp.tmp', 'w')) for x in range(3)]
(snip)
342 with context_manager as iterable:
--> 343 for item in iterable:
344 yield item
345
UnsupportedOperation: not readable To make this work we need to yield a reference to the context manager from inside a |
Right,
At this point I like the |
I could see divorcing this from def contextualize(obj):
"""Wrap *obj*, an object that supports the context manager protocol,
in a ``with`` statement and then yield the resultant object.
The object's ``__enter__()`` method runs before this function yields, and
its ``__exit__()`` method runs after control returns to this function.
This can be used to operate on objects that can close automatically when
using a ``with`` statement, like IO objects:
>>> from io import StringIO
>>> from more_itertools import consume
>>> it = [u'1', u'2', u'3']
>>> file_obj = StringIO()
>>> consume(
... print(x, file=f) for f in contextualize(file_obj) for x in it
... )
>>> file_obj.closed
True
"""
with obj as context_obj:
yield context_obj |
Here's a thought. consume(side_effect(lambda file, x: print(x, file=file), contextualize(open('temp.tmp','w'), xs)) To make this work, the iteration would need to be a double loop (Cartesian product), equivalent to (func(x,y) for x in xs for y in ys) I am not sure how you would generalize the chunking in side-effect. Perhaps a list of chunk-sizes when there is more than one sequence? |
Hmm, I see what you mean, but I'm not sure we should go there... 🐲 I think we should pick between #115 ( #115 Pros: #115 Cons: Basically re-invents context management - #112 Pros: Could be used places other than #112 Cons: Not all that itertools-y? |
I just realized that I can get the desired effect from my last example without rewriting consume(side_effect(lambda f, x: print(x, file=f), product(contextualize(open('tmp.tmp','w')), xs)) Does this example make I personally like that I can do this without holding a reference to the file handler, allowing the side-effect to be defined and consumed in the one expression. This would be my one knock on the current solution, Unfortunately, the expression using What if def contextzip(context, seq):
with context as c:
for item in seq:
yield (c, item) This allows the user to bundle a context manager with the items of the sequence. They could do what-ever is required with the pair, assured that the context manager will be processed correctly behind the scenes. Then the log to a file example becomes print_to_file = lambda *args: print(args[1], file=args[0])
consume(side_effect(print_to_file, contextzip(open('temp.tmp','w'), xs))) |
Thank you both for thinking through all this in such depth! I really like @contextmanager
def fake_file():
"""Pretend we're opening and closing a file."""
print('open')
yield 'some_file'
print('close')
--> [print(x, c) for c in context(fake_file()) for x in range(3)]
The simpler I am still thinking about whether |
[x for c in context(fake_file())
for x in side_effect(lambda x: print('logging: ', x * 2, c), range(3))] That works even with Python 3's tighter list comprehension scoping. (As before, you have to be careful to put your context manager in the first If the open/close behavior you're looking for is not in a context manager, it gets wordy. In that case, before/after are certainly a win on conciseness and arguably on clarity as well. Given that the original use case was logging to a file, I think we should merge |
I think you're right, |
I'm good with this outcome; I'll back off the |
That's #118. I think ready for a release after that? I have a branch mostly ready for that with some cleanup. |
Great! Thanks, @bbayles and @yardsale8! |
Closing a file after yielding its last line is all well and good, but I wonder: can we be more general and add power without losing much brevity?
@bbayles? @yardsale8?
The text was updated successfully, but these errors were encountered: