diff --git a/CHANGELOG.md b/CHANGELOG.md index e1bc832e..e3612481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog ## Unreleased -* Added option `lib` to JuliaCall. Setting this will skip the discovery subprocess. +* Add method `pynext(x, d)` to return a default value `d` if there are no more elements. +* Add option `lib` to JuliaCall. Setting this will skip the discovery subprocess. * Bug fixes. ## 0.9.34 (2026-05-18) diff --git a/src/Core/builtins.jl b/src/Core/builtins.jl index 7e772662..10b05c8c 100644 --- a/src/Core/builtins.jl +++ b/src/Core/builtins.jl @@ -507,11 +507,27 @@ Equivalent to `iter(x)` in Python. pyiter(x) = pynew(errcheck(@autopy x C.PyObject_GetIter(x_))) """ - pynext(x) + pynext(x, [d]) -Equivalent to `next(x)` in Python. +Equivalent to `next(x, d)` in Python. + +Returns the next item from the iterator `x`. If there are no more items, returns `d` if +given, else raises `StopIteration`. """ -pynext(x) = pybuiltins.next(x) +function pynext(x) + ptr = errcheck_ambig(C.PyIter_Next(x)) + if ptr == C.PyNULL + errset(pybuiltins.StopIteration) + pythrow() + else + pynew(ptr) + end +end + +function pynext(x, d) + ptr = errcheck_ambig(C.PyIter_Next(x)) + ptr == C.PyNULL ? d : pynew(ptr) +end """ unsafe_pynext(x) diff --git a/test/Core.jl b/test/Core.jl index 22c69e37..0327b05b 100644 --- a/test/Core.jl +++ b/test/Core.jl @@ -223,25 +223,45 @@ end @testitem "iter" begin - @test_throws PyException pyiter(pybuiltins.None) - @test_throws PyException pyiter(pybuiltins.True) - # unsafe_pynext - it = pyiter(pyrange(2)) - x = PythonCall.unsafe_pynext(it) - @test !PythonCall.pyisnull(x) - @test pyeq(Bool, x, 0) - x = PythonCall.unsafe_pynext(it) - @test !PythonCall.pyisnull(x) - @test pyeq(Bool, x, 1) - x = PythonCall.unsafe_pynext(it) - @test PythonCall.pyisnull(x) - # pynext - it = pyiter(pyrange(2)) - x = pynext(it) - @test pyeq(Bool, x, 0) - x = pynext(it) - @test pyeq(Bool, x, 1) - @test_throws PyException pynext(it) + @testset "non-iterables" begin + @test_throws PyException pyiter(pybuiltins.None) + @test_throws PyException pyiter(pybuiltins.True) + end + @testset "unsafe_pynext" begin + it = pyiter(pyrange(2)) + x = PythonCall.unsafe_pynext(it) + @test x isa Py + @test !PythonCall.pyisnull(x) + @test pyeq(Bool, x, 0) + x = PythonCall.unsafe_pynext(it) + @test x isa Py + @test !PythonCall.pyisnull(x) + @test pyeq(Bool, x, 1) + x = PythonCall.unsafe_pynext(it) + @test x isa Py + @test PythonCall.pyisnull(x) + end + @testset "pynext" begin + it = pyiter(pyrange(2)) + x = pynext(it) + @test x isa Py + @test pyeq(Bool, x, 0) + x = pynext(it) + @test x isa Py + @test pyeq(Bool, x, 1) + @test_throws PyException pynext(it) + end + @testset "pynext with default" begin + it = pyiter(pyrange(2)) + x = pynext(it, nothing) + @test x isa Py + @test pyeq(Bool, x, 0) + x = pynext(it, nothing) + @test x isa Py + @test pyeq(Bool, x, 1) + x = pynext(it, nothing) + @test x === nothing + end end @testitem "number" begin