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

Clarified documentation for implementing iteration. #882

Merged
merged 6 commits into from
Apr 28, 2020

Conversation

abingham
Copy link

Added a note about implementing the distinction between iterables and iterators. I stumbled over this a bit when I was implementing some bindings, so I thought some clarification would help.

Copy link
Member

@kngwyu kngwyu left a comment

Choose a reason for hiding this comment

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

Good catch, thank you!
But generally, we don't need to implement __next__ for containers.
To clarify the relationship between container and iterator, I propose the below example.

#[pyclass]
struct Iter {
    inner: std::vec::IntoIter<usize>,
}

#[pyproto]
impl PyIterProtocol for Iter {
    fn __next__(mut slf: PyRefMut<'p, Self>) -> PyResult<Option<usize>> {
        Ok(slf.inner.next())
    }
}

#[pyclass]
struct Container {
    iter: Vec<usize>,
}

#[pyproto]
impl PyIterProtocol for Container {
    fn __iter__(slf: PyRef<'p, Self>) -> PyResult<Py<Iter>> {
        let iter = Iter {
            inner: slf.iter.clone().into_iter(),
        };
        PyCell::new(slf.py(), iter).map(Into::into)
    }
}

You can test this example by

    let gil = Python::acquire_gil();
    let py = gil.python();
    let inst = pyo3::PyCell::new(
        py,
        Container {
            iter: vec![1, 2, 3, 4],
        },
    )
    .unwrap();
    py_assert!(py, inst, "list(inst) == [1, 2, 3, 4]");

(please use # to hide the test).

In addition, it would be helpful to refer to Python doc.

@abingham
Copy link
Author

abingham commented Apr 27, 2020

Ah, I hadn't thought to check for default implementations of the protocol functions!

we don't need to implement next for containers.

The version provided by the trait just panics (it calls unimplemented!() which I think panics, at least), and this seems like a mistake. Callers in Python could, at least in principle, call __next__() on anything and expect their program to, at worst, throw an exception. So it seems like bad advice to tell people not to implement it.

Regarding your suggestion for the iterator implementation: iterators always need to implement __iter__(). It's a requirement of the Python iterator protocol, and it certainly shouldn't panic. It's called as a matter of course in certain Python code.

I may well be missing something important about the current implementation of PyIterProtocol, but it feels a bit unfinished. Let me know if I'm way off base, though.

@kngwyu
Copy link
Member

kngwyu commented Apr 28, 2020

The version provided by the trait just panics (it calls unimplemented!() which I think panics, at least), and this seems like a mistake.

No. It's a bit difficult to explain, but unimplemented is just a hacky implementation detail and never called.
If a user doesn't provide __next__, we just don't set tp_iternext for the class.
E.g., when we call next(inst) in the above example, we get:

TypeError: 'Container' object is not an iterator

So I don't want to recommend users to implement a stub for __next__.

Regarding your suggestion for the iterator implementation: iterators always need to implement iter().

Yeah, you're right. Please fix the example to do so.

@abingham
Copy link
Author

It's a bit difficult to explain

I bet! I looked through that code a bit, but it's a bit dense :) I'll make the change you suggest regarding __next__. Thanks for taking the time to explain.

@abingham
Copy link
Author

@kngwyu The tests are failing because py_assert! doesn't seem to be defined. Is it supposed to be made available somehow in the doc testing environment, or do I need to import it somehow?

@kngwyu
Copy link
Member

kngwyu commented Apr 28, 2020

@abingham
Ah, sorry we can use it only in the test files.
Please use pyo3::py_run!(py, inst, "assert ~") instead.

@kngwyu
Copy link
Member

kngwyu commented Apr 28, 2020

LGTM, thank you!

@kngwyu kngwyu merged commit 746c352 into PyO3:master Apr 28, 2020
@abingham
Copy link
Author

Thank you! I learned a lot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants