rustpython_vm/builtins/
classmethod.rs

1use super::{PyBoundMethod, PyStr, PyType, PyTypeRef};
2use crate::{
3    class::PyClassImpl,
4    common::lock::PyMutex,
5    types::{Constructor, GetDescriptor, Initializer, Representable},
6    AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
7};
8
9/// classmethod(function) -> method
10///
11/// Convert a function to be a class method.
12///
13/// A class method receives the class as implicit first argument,
14/// just like an instance method receives the instance.
15/// To declare a class method, use this idiom:
16///
17///   class C:
18///       @classmethod
19///       def f(cls, arg1, arg2, ...):
20///           ...
21///
22/// It can be called either on the class (e.g. C.f()) or on an instance
23/// (e.g. C().f()).  The instance is ignored except for its class.
24/// If a class method is called for a derived class, the derived class
25/// object is passed as the implied first argument.
26///
27/// Class methods are different than C++ or Java static methods.
28/// If you want those, see the staticmethod builtin.
29#[pyclass(module = false, name = "classmethod")]
30#[derive(Debug)]
31pub struct PyClassMethod {
32    callable: PyMutex<PyObjectRef>,
33}
34
35impl From<PyObjectRef> for PyClassMethod {
36    fn from(callable: PyObjectRef) -> Self {
37        Self {
38            callable: PyMutex::new(callable),
39        }
40    }
41}
42
43impl PyPayload for PyClassMethod {
44    fn class(ctx: &Context) -> &'static Py<PyType> {
45        ctx.types.classmethod_type
46    }
47}
48
49impl GetDescriptor for PyClassMethod {
50    fn descr_get(
51        zelf: PyObjectRef,
52        obj: Option<PyObjectRef>,
53        cls: Option<PyObjectRef>,
54        vm: &VirtualMachine,
55    ) -> PyResult {
56        let (zelf, _obj) = Self::_unwrap(&zelf, obj, vm)?;
57        let cls = cls.unwrap_or_else(|| _obj.class().to_owned().into());
58        let call_descr_get: PyResult<PyObjectRef> = zelf.callable.lock().get_attr("__get__", vm);
59        match call_descr_get {
60            Err(_) => Ok(PyBoundMethod::new_ref(cls, zelf.callable.lock().clone(), &vm.ctx).into()),
61            Ok(call_descr_get) => call_descr_get.call((cls.clone(), cls), vm),
62        }
63    }
64}
65
66impl Constructor for PyClassMethod {
67    type Args = PyObjectRef;
68
69    fn py_new(cls: PyTypeRef, callable: Self::Args, vm: &VirtualMachine) -> PyResult {
70        let doc = callable.get_attr("__doc__", vm);
71
72        let result = PyClassMethod {
73            callable: PyMutex::new(callable),
74        }
75        .into_ref_with_type(vm, cls)?;
76        let obj = PyObjectRef::from(result);
77
78        if let Ok(doc) = doc {
79            obj.set_attr("__doc__", doc, vm)?;
80        }
81
82        Ok(obj)
83    }
84}
85
86impl Initializer for PyClassMethod {
87    type Args = PyObjectRef;
88
89    fn init(zelf: PyRef<Self>, callable: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
90        *zelf.callable.lock() = callable;
91        Ok(())
92    }
93}
94
95impl PyClassMethod {
96    pub fn new_ref(callable: PyObjectRef, ctx: &Context) -> PyRef<Self> {
97        PyRef::new_ref(
98            Self {
99                callable: PyMutex::new(callable),
100            },
101            ctx.types.classmethod_type.to_owned(),
102            None,
103        )
104    }
105}
106
107#[pyclass(
108    with(GetDescriptor, Constructor, Representable),
109    flags(BASETYPE, HAS_DICT)
110)]
111impl PyClassMethod {
112    #[pygetset(magic)]
113    fn func(&self) -> PyObjectRef {
114        self.callable.lock().clone()
115    }
116
117    #[pygetset(magic)]
118    fn wrapped(&self) -> PyObjectRef {
119        self.callable.lock().clone()
120    }
121
122    #[pygetset(magic)]
123    fn module(&self, vm: &VirtualMachine) -> PyResult {
124        self.callable.lock().get_attr("__module__", vm)
125    }
126
127    #[pygetset(magic)]
128    fn qualname(&self, vm: &VirtualMachine) -> PyResult {
129        self.callable.lock().get_attr("__qualname__", vm)
130    }
131
132    #[pygetset(magic)]
133    fn name(&self, vm: &VirtualMachine) -> PyResult {
134        self.callable.lock().get_attr("__name__", vm)
135    }
136
137    #[pygetset(magic)]
138    fn annotations(&self, vm: &VirtualMachine) -> PyResult {
139        self.callable.lock().get_attr("__annotations__", vm)
140    }
141
142    #[pygetset(magic)]
143    fn isabstractmethod(&self, vm: &VirtualMachine) -> PyObjectRef {
144        match vm.get_attribute_opt(self.callable.lock().clone(), "__isabstractmethod__") {
145            Ok(Some(is_abstract)) => is_abstract,
146            _ => vm.ctx.new_bool(false).into(),
147        }
148    }
149
150    #[pygetset(magic, setter)]
151    fn set_isabstractmethod(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
152        self.callable
153            .lock()
154            .set_attr("__isabstractmethod__", value, vm)?;
155        Ok(())
156    }
157}
158
159impl Representable for PyClassMethod {
160    #[inline]
161    fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
162        let callable = zelf.callable.lock().repr(vm).unwrap();
163        let class = Self::class(&vm.ctx);
164
165        let repr = match (
166            class
167                .qualname(vm)
168                .downcast_ref::<PyStr>()
169                .map(|n| n.as_str()),
170            class.module(vm).downcast_ref::<PyStr>().map(|m| m.as_str()),
171        ) {
172            (None, _) => return Err(vm.new_type_error("Unknown qualified name".into())),
173            (Some(qualname), Some(module)) if module != "builtins" => {
174                format!("<{module}.{qualname}({callable})>")
175            }
176            _ => format!("<{}({})>", class.slot_name(), callable),
177        };
178        Ok(repr)
179    }
180}
181
182pub(crate) fn init(context: &Context) {
183    PyClassMethod::extend_class(context, context.types.classmethod_type);
184}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.