diff --git a/CHANGELOG.md b/CHANGELOG.md index 6957e678..cad7a605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,7 @@ ## Unreleased * Add configuration via Preferences in addition to environment variables (e.g. `exe` rather than `JULIA_PYTHONCALL_EXE`.) -* Internals: removed the cache of unused Python objects. This makes `pydel!` faster and - removes a race condition in free-threaded python. +* Bug fixes. ## 0.9.32 (2026-05-14) * Added `juliacall.TypeValue.__numpy_dtype__` attribute to allow converting Julia types diff --git a/src/Core/Py.jl b/src/Core/Py.jl index 9c73a12a..0705bded 100644 --- a/src/Core/Py.jl +++ b/src/Core/Py.jl @@ -270,30 +270,35 @@ Base.hasproperty(x::Py, k::String) = pyhasattr(x, k) Base.setproperty!(x::Py, k::Symbol, v) = pysetattr(x, string(k), v) Base.setproperty!(x::Py, k::String, v) = pysetattr(x, k, v) -function Base.propertynames(x::Py, private::Bool = false) - properties = C.on_main_thread() do - # this follows the logic of rlcompleter.py - function classmembers(c) - r = pydir(c) - if pyhasattr(c, "__bases__") - for b in c.__bases__ - r = pyiadd(r, classmembers(b)) - end +function _propertynames(x::Py, private::Bool) + # this follows the logic of rlcompleter.py + function classmembers(c) + r = pydir(c) + if pyhasattr(c, "__bases__") + for b in c.__bases__ + r = pyiadd(r, classmembers(b)) end - return r end + return r + end - words = pyset(pydir(x::Py)) - words.discard("__builtins__") - if pyhasattr(x, "__class__") - words.add("__class__") - words.update(classmembers(x.__class__)) - end - map(pystr_asstring, words) - end::Vector{String} # explicit type since on_main_thread() is type-unstable + words = pyset(pydir(x::Py)) + words.discard("__builtins__") + if pyhasattr(x, "__class__") + words.add("__class__") + words.update(classmembers(x.__class__)) + end + return Symbol[Symbol(pystr_asstring(word)) for word in words] +end - # private || filter!(w->!startswith(w, "_"), words) - map(Symbol, properties) +function Base.propertynames(x::Py, private::Bool = false) + if C.PyGILState_Check() == 1 + _propertynames(x, private) + else + C.on_main_thread() do + _propertynames(x, private) + end::Vector{Symbol} + end end Base.Bool(x::Py) = pytruth(x) diff --git a/test/Core.jl b/test/Core.jl index 8f2e428a..22c69e37 100644 --- a/test/Core.jl +++ b/test/Core.jl @@ -832,12 +832,23 @@ end end @testitem "propertynames" begin - x = pyint(7) - task = Threads.@spawn propertynames(x) - properties = propertynames(x) - @test :__init__ in properties - prop_task = fetch(task) - @test properties == prop_task + @testset "basic" begin + x = pyint(7) + task = Threads.@spawn propertynames(x) + properties = propertynames(x) + @test :__init__ in properties + prop_task = fetch(task) + @test properties == prop_task + end + @testset "gil (#751)" begin + o = pyint(751) + t = Base.Threads.@spawn begin + PythonCall.GIL.@lock begin + propertynames(o) + end + end + PythonCall.GIL.@unlock wait(t) + end end @testitem "on_main_thread" begin