mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Allow heap getset creation (#5854)
* Update _threading_local.py * allow heap-getset
This commit is contained in:
1 parent
3a85499b11
commit
07a04acfaa
7 files changed
+44
-145
No files matched your search
Vendored
+1
-130
@@ -4,133 +4,6 @@
|
||||
class. Depending on the version of Python you're using, there may be a
|
||||
faster one available. You should always import the `local` class from
|
||||
`threading`.)
|
||||
|
||||
Thread-local objects support the management of thread-local data.
|
||||
If you have data that you want to be local to a thread, simply create
|
||||
a thread-local object and use its attributes:
|
||||
|
||||
>>> mydata = local()
|
||||
>>> mydata.number = 42
|
||||
>>> mydata.number
|
||||
42
|
||||
|
||||
You can also access the local-object's dictionary:
|
||||
|
||||
>>> mydata.__dict__
|
||||
{'number': 42}
|
||||
>>> mydata.__dict__.setdefault('widgets', [])
|
||||
[]
|
||||
>>> mydata.widgets
|
||||
[]
|
||||
|
||||
What's important about thread-local objects is that their data are
|
||||
local to a thread. If we access the data in a different thread:
|
||||
|
||||
>>> log = []
|
||||
>>> def f():
|
||||
... items = sorted(mydata.__dict__.items())
|
||||
... log.append(items)
|
||||
... mydata.number = 11
|
||||
... log.append(mydata.number)
|
||||
|
||||
>>> import threading
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
>>> log
|
||||
[[], 11]
|
||||
|
||||
we get different data. Furthermore, changes made in the other thread
|
||||
don't affect data seen in this thread:
|
||||
|
||||
>>> mydata.number
|
||||
42
|
||||
|
||||
Of course, values you get from a local object, including a __dict__
|
||||
attribute, are for whatever thread was current at the time the
|
||||
attribute was read. For that reason, you generally don't want to save
|
||||
these values across threads, as they apply only to the thread they
|
||||
came from.
|
||||
|
||||
You can create custom local objects by subclassing the local class:
|
||||
|
||||
>>> class MyLocal(local):
|
||||
... number = 2
|
||||
... initialized = False
|
||||
... def __init__(self, **kw):
|
||||
... if self.initialized:
|
||||
... raise SystemError('__init__ called too many times')
|
||||
... self.initialized = True
|
||||
... self.__dict__.update(kw)
|
||||
... def squared(self):
|
||||
... return self.number ** 2
|
||||
|
||||
This can be useful to support default values, methods and
|
||||
initialization. Note that if you define an __init__ method, it will be
|
||||
called each time the local object is used in a separate thread. This
|
||||
is necessary to initialize each thread's dictionary.
|
||||
|
||||
Now if we create a local object:
|
||||
|
||||
>>> mydata = MyLocal(color='red')
|
||||
|
||||
Now we have a default number:
|
||||
|
||||
>>> mydata.number
|
||||
2
|
||||
|
||||
an initial color:
|
||||
|
||||
>>> mydata.color
|
||||
'red'
|
||||
>>> del mydata.color
|
||||
|
||||
And a method that operates on the data:
|
||||
|
||||
>>> mydata.squared()
|
||||
4
|
||||
|
||||
As before, we can access the data in a separate thread:
|
||||
|
||||
>>> log = []
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
>>> log
|
||||
[[('color', 'red'), ('initialized', True)], 11]
|
||||
|
||||
without affecting this thread's data:
|
||||
|
||||
>>> mydata.number
|
||||
2
|
||||
>>> mydata.color
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'MyLocal' object has no attribute 'color'
|
||||
|
||||
Note that subclasses can define slots, but they are not thread
|
||||
local. They are shared across threads:
|
||||
|
||||
>>> class MyLocal(local):
|
||||
... __slots__ = 'number'
|
||||
|
||||
>>> mydata = MyLocal()
|
||||
>>> mydata.number = 42
|
||||
>>> mydata.color = 'red'
|
||||
|
||||
So, the separate thread:
|
||||
|
||||
>>> thread = threading.Thread(target=f)
|
||||
>>> thread.start()
|
||||
>>> thread.join()
|
||||
|
||||
affects what we see:
|
||||
|
||||
>>> # TODO: RUSTPYTHON, __slots__
|
||||
>>> mydata.number #doctest: +SKIP
|
||||
11
|
||||
|
||||
>>> del mydata
|
||||
"""
|
||||
|
||||
from weakref import ref
|
||||
@@ -194,7 +67,6 @@ class _localimpl:
|
||||
|
||||
@contextmanager
|
||||
def _patch(self):
|
||||
old = object.__getattribute__(self, '__dict__')
|
||||
impl = object.__getattribute__(self, '_local__impl')
|
||||
try:
|
||||
dct = impl.get_dict()
|
||||
@@ -205,13 +77,12 @@ def _patch(self):
|
||||
with impl.locallock:
|
||||
object.__setattr__(self, '__dict__', dct)
|
||||
yield
|
||||
object.__setattr__(self, '__dict__', old)
|
||||
|
||||
|
||||
class local:
|
||||
__slots__ = '_local__impl', '__dict__'
|
||||
|
||||
def __new__(cls, *args, **kw):
|
||||
def __new__(cls, /, *args, **kw):
|
||||
if (args or kw) and (cls.__init__ is object.__init__):
|
||||
raise TypeError("Initialization arguments are not supported")
|
||||
self = object.__new__(cls)
|
||||
|
||||
@@ -18,7 +18,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
|
||||
|
||||
macro_rules! create_property {
|
||||
($ctx: expr, $attributes: expr, $name: expr, $class: expr, $element: ident) => {
|
||||
let attr = $ctx.new_getset(
|
||||
let attr = $ctx.new_static_getset(
|
||||
$name,
|
||||
$class,
|
||||
move |this: &PyExpatLikeXmlParser| this.$element.read().clone(),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use super::PyType;
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
|
||||
builtins::type_::PointerSlot,
|
||||
class::PyClassImpl,
|
||||
function::{IntoPyGetterFunc, IntoPySetterFunc, PyGetterFunc, PySetterFunc, PySetterValue},
|
||||
types::{GetDescriptor, Unconstructible},
|
||||
@@ -12,7 +13,7 @@ use crate::{
|
||||
#[pyclass(module = false, name = "getset_descriptor")]
|
||||
pub struct PyGetSet {
|
||||
name: String,
|
||||
class: &'static Py<PyType>,
|
||||
class: PointerSlot<Py<PyType>>, // A class type freed before getset is non-sense.
|
||||
getter: Option<PyGetterFunc>,
|
||||
setter: Option<PySetterFunc>,
|
||||
// doc: Option<String>,
|
||||
@@ -72,7 +73,7 @@ impl PyGetSet {
|
||||
pub fn new(name: String, class: &'static Py<PyType>) -> Self {
|
||||
Self {
|
||||
name,
|
||||
class,
|
||||
class: PointerSlot::from(class),
|
||||
getter: None,
|
||||
setter: None,
|
||||
}
|
||||
@@ -138,13 +139,17 @@ impl PyGetSet {
|
||||
|
||||
#[pygetset]
|
||||
fn __qualname__(&self) -> String {
|
||||
format!("{}.{}", self.class.slot_name(), self.name.clone())
|
||||
format!(
|
||||
"{}.{}",
|
||||
unsafe { self.class.borrow_static() }.slot_name(),
|
||||
self.name.clone()
|
||||
)
|
||||
}
|
||||
|
||||
#[pymember]
|
||||
fn __objclass__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(zelf.class.to_owned().into())
|
||||
Ok(unsafe { zelf.class.borrow_static() }.to_owned().into())
|
||||
}
|
||||
}
|
||||
impl Unconstructible for PyGetSet {}
|
||||
|
||||
@@ -386,7 +386,7 @@ pub(crate) fn init(context: &Context) {
|
||||
// This is a bit unfortunate, but this instance attribute overlaps with the
|
||||
// class __doc__ string..
|
||||
extend_class!(context, context.types.property_type, {
|
||||
"__doc__" => context.new_getset(
|
||||
"__doc__" => context.new_static_getset(
|
||||
"__doc__",
|
||||
context.types.property_type,
|
||||
PyProperty::doc_getter,
|
||||
|
||||
+10
-7
@@ -31,7 +31,7 @@ use crate::{
|
||||
};
|
||||
use indexmap::{IndexMap, map::Entry};
|
||||
use itertools::Itertools;
|
||||
use std::{borrow::Borrow, collections::HashSet, fmt, ops::Deref, pin::Pin, ptr::NonNull};
|
||||
use std::{borrow::Borrow, collections::HashSet, ops::Deref, pin::Pin, ptr::NonNull};
|
||||
|
||||
#[pyclass(module = false, name = "type", traverse = "manual")]
|
||||
pub struct PyType {
|
||||
@@ -69,6 +69,9 @@ pub struct HeapTypeExt {
|
||||
|
||||
pub struct PointerSlot<T>(NonNull<T>);
|
||||
|
||||
unsafe impl<T> Sync for PointerSlot<T> {}
|
||||
unsafe impl<T> Send for PointerSlot<T> {}
|
||||
|
||||
impl<T> PointerSlot<T> {
|
||||
pub unsafe fn borrow_static(&self) -> &'static T {
|
||||
unsafe { self.0.as_ref() }
|
||||
@@ -126,14 +129,14 @@ unsafe impl Traverse for PyAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PyType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.name(), f)
|
||||
impl std::fmt::Display for PyType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.name(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PyType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
impl std::fmt::Debug for PyType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[PyType {}]", &self.name())
|
||||
}
|
||||
}
|
||||
@@ -908,7 +911,7 @@ impl Constructor for PyType {
|
||||
let __dict__ = identifier!(vm, __dict__);
|
||||
attributes.entry(__dict__).or_insert_with(|| {
|
||||
vm.ctx
|
||||
.new_getset(
|
||||
.new_static_getset(
|
||||
"__dict__",
|
||||
vm.ctx.types.type_type,
|
||||
subtype_get_dict,
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ pub trait PyClassImpl: PyClassDef {
|
||||
let __dict__ = identifier!(ctx, __dict__);
|
||||
class.set_attr(
|
||||
__dict__,
|
||||
ctx.new_getset(
|
||||
ctx.new_static_getset(
|
||||
"__dict__",
|
||||
class,
|
||||
crate::builtins::object::object_get_dict,
|
||||
|
||||
+21
-1
@@ -570,7 +570,7 @@ impl Context {
|
||||
PyRef::new_ref(getset, self.types.getset_type.to_owned(), None)
|
||||
}
|
||||
|
||||
pub fn new_getset<G, S, T, U>(
|
||||
pub fn new_static_getset<G, S, T, U>(
|
||||
&self,
|
||||
name: impl Into<String>,
|
||||
class: &'static Py<PyType>,
|
||||
@@ -586,6 +586,26 @@ impl Context {
|
||||
PyRef::new_ref(getset, self.types.getset_type.to_owned(), None)
|
||||
}
|
||||
|
||||
/// Creates a new `PyGetSet` with a heap type.
|
||||
///
|
||||
/// # Safety
|
||||
/// In practice, this constructor is safe because a getset is always owned by its `class` type.
|
||||
/// However, it can be broken if used unconventionally.
|
||||
pub unsafe fn new_getset<G, S, T, U>(
|
||||
&self,
|
||||
name: impl Into<String>,
|
||||
class: &Py<PyType>,
|
||||
g: G,
|
||||
s: S,
|
||||
) -> PyRef<PyGetSet>
|
||||
where
|
||||
G: IntoPyGetterFunc<T>,
|
||||
S: IntoPySetterFunc<U>,
|
||||
{
|
||||
let class = unsafe { &*(class as *const _) };
|
||||
self.new_static_getset(name, class, g, s)
|
||||
}
|
||||
|
||||
pub fn new_base_object(&self, class: PyTypeRef, dict: Option<PyDictRef>) -> PyObjectRef {
|
||||
debug_assert_eq!(
|
||||
class.slots.flags.contains(PyTypeFlags::HAS_DICT),
|
||||
|
||||
Reference in new issue
Block a user