Skip to content

Commit 95a894a

Browse files
Merge pull request #954 from youknowone/property-kwargs
Fix property to take keyword arguments
2 parents e1416f5 + 268164e commit 95a894a

File tree

2 files changed

+127
-90
lines changed

2 files changed

+127
-90
lines changed

tests/snippets/property.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ def foo(self):
5959
with assertRaises(TypeError):
6060
property.__new__(object)
6161

62+
# assert p.__doc__ is None
63+
6264

6365
p1 = property("a", "b", "c")
6466

@@ -75,3 +77,7 @@ def foo(self):
7577
assert p1.deleter(None).fdel == "c"
7678

7779
assert p1.__get__(None, object) is p1
80+
# assert p1.__doc__ is 'a'.__doc__
81+
82+
p2 = property('a', doc='pdoc')
83+
# assert p2.__doc__ == 'pdoc'

vm/src/obj/objproperty.rs

Lines changed: 121 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
33
*/
44

5-
use crate::function::IntoPyNativeFunc;
6-
use crate::function::OptionalArg;
7-
use crate::obj::objstr::PyStringRef;
5+
use crate::function::{IntoPyNativeFunc, OptionalArg, PyFuncArgs};
86
use crate::obj::objtype::PyClassRef;
97
use crate::pyobject::{
10-
IdProtocol, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol,
8+
IdProtocol, PyClassImpl, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue,
9+
TypeProtocol,
1110
};
1211
use crate::vm::VirtualMachine;
1312

14-
/// Read-only property, doesn't have __set__ or __delete__
13+
// Read-only property, doesn't have __set__ or __delete__
14+
#[pyclass]
1515
#[derive(Debug)]
1616
pub struct PyReadOnlyProperty {
1717
getter: PyObjectRef,
@@ -25,27 +25,62 @@ impl PyValue for PyReadOnlyProperty {
2525

2626
pub type PyReadOnlyPropertyRef = PyRef<PyReadOnlyProperty>;
2727

28-
impl PyReadOnlyPropertyRef {
28+
#[pyimpl]
29+
impl PyReadOnlyProperty {
30+
#[pymethod(name = "__get__")]
2931
fn get(
30-
self,
32+
zelf: PyRef<Self>,
3133
obj: PyObjectRef,
3234
_owner: OptionalArg<PyClassRef>,
3335
vm: &VirtualMachine,
3436
) -> PyResult {
3537
if obj.is(vm.ctx.none.as_object()) {
36-
Ok(self.into_object())
38+
Ok(zelf.into_object())
3739
} else {
38-
vm.invoke(self.getter.clone(), obj)
40+
vm.invoke(zelf.getter.clone(), obj)
3941
}
4042
}
4143
}
4244

43-
/// Fully fledged property
45+
/// Property attribute.
46+
///
47+
/// fget
48+
/// function to be used for getting an attribute value
49+
/// fset
50+
/// function to be used for setting an attribute value
51+
/// fdel
52+
/// function to be used for del'ing an attribute
53+
/// doc
54+
/// docstring
55+
///
56+
/// Typical use is to define a managed attribute x:
57+
///
58+
/// class C(object):
59+
/// def getx(self): return self._x
60+
/// def setx(self, value): self._x = value
61+
/// def delx(self): del self._x
62+
/// x = property(getx, setx, delx, "I'm the 'x' property.")
63+
///
64+
/// Decorators make defining new properties or modifying existing ones easy:
65+
///
66+
/// class C(object):
67+
/// @property
68+
/// def x(self):
69+
/// "I am the 'x' property."
70+
/// return self._x
71+
/// @x.setter
72+
/// def x(self, value):
73+
/// self._x = value
74+
/// @x.deleter
75+
/// def x(self):
76+
/// del self._x
77+
#[pyclass]
4478
#[derive(Debug)]
4579
pub struct PyProperty {
4680
getter: Option<PyObjectRef>,
4781
setter: Option<PyObjectRef>,
4882
deleter: Option<PyObjectRef>,
83+
doc: Option<PyObjectRef>,
4984
}
5085

5186
impl PyValue for PyProperty {
@@ -56,43 +91,61 @@ impl PyValue for PyProperty {
5691

5792
pub type PyPropertyRef = PyRef<PyProperty>;
5893

59-
impl PyPropertyRef {
94+
#[pyimpl]
95+
impl PyProperty {
96+
#[pymethod(name = "__new__")]
6097
fn new_property(
6198
cls: PyClassRef,
62-
fget: OptionalArg<PyObjectRef>,
63-
fset: OptionalArg<PyObjectRef>,
64-
fdel: OptionalArg<PyObjectRef>,
65-
_doc: OptionalArg<PyStringRef>,
99+
args: PyFuncArgs,
66100
vm: &VirtualMachine,
67101
) -> PyResult<PyPropertyRef> {
102+
arg_check!(
103+
vm,
104+
args,
105+
required = [],
106+
optional = [(fget, None), (fset, None), (fdel, None), (doc, None)]
107+
);
108+
109+
fn into_option(vm: &VirtualMachine, arg: Option<&PyObjectRef>) -> Option<PyObjectRef> {
110+
arg.and_then(|arg| {
111+
if vm.ctx.none().is(arg) {
112+
None
113+
} else {
114+
Some(arg.clone())
115+
}
116+
})
117+
}
118+
68119
PyProperty {
69-
getter: fget.into_option(),
70-
setter: fset.into_option(),
71-
deleter: fdel.into_option(),
120+
getter: into_option(vm, fget),
121+
setter: into_option(vm, fset),
122+
deleter: into_option(vm, fdel),
123+
doc: into_option(vm, doc),
72124
}
73125
.into_ref_with_type(vm, cls)
74126
}
75127

76128
// Descriptor methods
77129

78130
// specialised version that doesn't check for None
79-
pub(crate) fn instance_binding_get(self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
131+
pub(crate) fn instance_binding_get(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
80132
if let Some(getter) = self.getter.as_ref() {
81133
vm.invoke(getter.clone(), obj)
82134
} else {
83135
Err(vm.new_attribute_error("unreadable attribute".to_string()))
84136
}
85137
}
86138

139+
#[pymethod(name = "__get__")]
87140
fn get(
88-
self,
141+
zelf: PyRef<Self>,
89142
obj: PyObjectRef,
90143
_owner: OptionalArg<PyClassRef>,
91144
vm: &VirtualMachine,
92145
) -> PyResult {
93-
if let Some(getter) = self.getter.as_ref() {
146+
if let Some(getter) = zelf.getter.as_ref() {
94147
if obj.is(vm.ctx.none.as_object()) {
95-
Ok(self.into_object())
148+
Ok(zelf.into_object())
96149
} else {
97150
vm.invoke(getter.clone(), obj)
98151
}
@@ -101,15 +154,17 @@ impl PyPropertyRef {
101154
}
102155
}
103156

104-
fn set(self, obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
157+
#[pymethod(name = "__set__")]
158+
fn set(&self, obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
105159
if let Some(setter) = self.setter.as_ref() {
106160
vm.invoke(setter.clone(), vec![obj, value])
107161
} else {
108162
Err(vm.new_attribute_error("can't set attribute".to_string()))
109163
}
110164
}
111165

112-
fn delete(self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
166+
#[pymethod(name = "__delete__")]
167+
fn delete(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
113168
if let Some(deleter) = self.deleter.as_ref() {
114169
vm.invoke(deleter.clone(), obj)
115170
} else {
@@ -119,45 +174,66 @@ impl PyPropertyRef {
119174

120175
// Access functions
121176

122-
fn fget(self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
177+
#[pyproperty]
178+
fn fget(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
123179
self.getter.clone()
124180
}
125181

126-
fn fset(self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
182+
#[pyproperty]
183+
fn fset(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
127184
self.setter.clone()
128185
}
129186

130-
fn fdel(self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
187+
#[pyproperty]
188+
fn fdel(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
131189
self.deleter.clone()
132190
}
133191

134192
// Python builder functions
135193

136-
fn getter(self, getter: Option<PyObjectRef>, vm: &VirtualMachine) -> PyResult<Self> {
194+
#[pymethod]
195+
fn getter(
196+
zelf: PyRef<Self>,
197+
getter: Option<PyObjectRef>,
198+
vm: &VirtualMachine,
199+
) -> PyResult<PyPropertyRef> {
137200
PyProperty {
138-
getter: getter.or_else(|| self.getter.clone()),
139-
setter: self.setter.clone(),
140-
deleter: self.deleter.clone(),
201+
getter: getter.or_else(|| zelf.getter.clone()),
202+
setter: zelf.setter.clone(),
203+
deleter: zelf.deleter.clone(),
204+
doc: None,
141205
}
142-
.into_ref_with_type(vm, TypeProtocol::class(&self))
206+
.into_ref_with_type(vm, TypeProtocol::class(&zelf))
143207
}
144208

145-
fn setter(self, setter: Option<PyObjectRef>, vm: &VirtualMachine) -> PyResult<Self> {
209+
#[pymethod]
210+
fn setter(
211+
zelf: PyRef<Self>,
212+
setter: Option<PyObjectRef>,
213+
vm: &VirtualMachine,
214+
) -> PyResult<PyPropertyRef> {
146215
PyProperty {
147-
getter: self.getter.clone(),
148-
setter: setter.or_else(|| self.setter.clone()),
149-
deleter: self.deleter.clone(),
216+
getter: zelf.getter.clone(),
217+
setter: setter.or_else(|| zelf.setter.clone()),
218+
deleter: zelf.deleter.clone(),
219+
doc: None,
150220
}
151-
.into_ref_with_type(vm, TypeProtocol::class(&self))
221+
.into_ref_with_type(vm, TypeProtocol::class(&zelf))
152222
}
153223

154-
fn deleter(self, deleter: Option<PyObjectRef>, vm: &VirtualMachine) -> PyResult<Self> {
224+
#[pymethod]
225+
fn deleter(
226+
zelf: PyRef<Self>,
227+
deleter: Option<PyObjectRef>,
228+
vm: &VirtualMachine,
229+
) -> PyResult<PyPropertyRef> {
155230
PyProperty {
156-
getter: self.getter.clone(),
157-
setter: self.setter.clone(),
158-
deleter: deleter.or_else(|| self.deleter.clone()),
231+
getter: zelf.getter.clone(),
232+
setter: zelf.setter.clone(),
233+
deleter: deleter.or_else(|| zelf.deleter.clone()),
234+
doc: None,
159235
}
160-
.into_ref_with_type(vm, TypeProtocol::class(&self))
236+
.into_ref_with_type(vm, TypeProtocol::class(&zelf))
161237
}
162238
}
163239

@@ -200,6 +276,7 @@ impl<'a> PropertyBuilder<'a> {
200276
getter: self.getter.clone(),
201277
setter: self.setter.clone(),
202278
deleter: None,
279+
doc: None,
203280
};
204281

205282
PyObject::new(payload, self.ctx.property_type(), None)
@@ -216,52 +293,6 @@ impl<'a> PropertyBuilder<'a> {
216293
}
217294

218295
pub fn init(context: &PyContext) {
219-
extend_class!(context, &context.readonly_property_type, {
220-
"__get__" => context.new_rustfunc(PyReadOnlyPropertyRef::get),
221-
});
222-
223-
let property_doc =
224-
"Property attribute.\n\n \
225-
fget\n \
226-
function to be used for getting an attribute value\n \
227-
fset\n \
228-
function to be used for setting an attribute value\n \
229-
fdel\n \
230-
function to be used for del\'ing an attribute\n \
231-
doc\n \
232-
docstring\n\n\
233-
Typical use is to define a managed attribute x:\n\n\
234-
class C(object):\n \
235-
def getx(self): return self._x\n \
236-
def setx(self, value): self._x = value\n \
237-
def delx(self): del self._x\n \
238-
x = property(getx, setx, delx, \"I\'m the \'x\' property.\")\n\n\
239-
Decorators make defining new properties or modifying existing ones easy:\n\n\
240-
class C(object):\n \
241-
@property\n \
242-
def x(self):\n \"I am the \'x\' property.\"\n \
243-
return self._x\n \
244-
@x.setter\n \
245-
def x(self, value):\n \
246-
self._x = value\n \
247-
@x.deleter\n \
248-
def x(self):\n \
249-
del self._x";
250-
251-
extend_class!(context, &context.property_type, {
252-
"__new__" => context.new_rustfunc(PyPropertyRef::new_property),
253-
"__doc__" => context.new_str(property_doc.to_string()),
254-
255-
"__get__" => context.new_rustfunc(PyPropertyRef::get),
256-
"__set__" => context.new_rustfunc(PyPropertyRef::set),
257-
"__delete__" => context.new_rustfunc(PyPropertyRef::delete),
258-
259-
"fget" => context.new_property(PyPropertyRef::fget),
260-
"fset" => context.new_property(PyPropertyRef::fset),
261-
"fdel" => context.new_property(PyPropertyRef::fdel),
262-
263-
"getter" => context.new_rustfunc(PyPropertyRef::getter),
264-
"setter" => context.new_rustfunc(PyPropertyRef::setter),
265-
"deleter" => context.new_rustfunc(PyPropertyRef::deleter),
266-
});
296+
PyReadOnlyProperty::extend_class(context, &context.readonly_property_type);
297+
PyProperty::extend_class(context, &context.property_type);
267298
}

0 commit comments

Comments
 (0)