Repository: RustPython/RustPython (language: rust)

Issue:
pyclass macro to recognize Py/PyRef pattern



Discussion / hints:

Fix patch:
diff --git a/.cspell.json b/.cspell.json
index 276869671f..3f60bb076d 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -61,6 +61,7 @@
     "rsplitn",
     "rustc",
     "rustfmt",
+    "seekfrom",
     "splitn",
     "subsec",
     "timsort",
diff --git a/derive-impl/src/pyclass.rs b/derive-impl/src/pyclass.rs
index 92fd9e5bc9..cd5bbc9a42 100644
--- a/derive-impl/src/pyclass.rs
+++ b/derive-impl/src/pyclass.rs
@@ -105,39 +105,114 @@ pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result<TokenStream
         Item::Impl(mut imp) => {
             extract_items_into_context(&mut context, imp.items.iter_mut());
 
-            let ty = &imp.self_ty;
+            let (impl_ty, payload_guess) = match imp.self_ty.as_ref() {
+                syn::Type::Path(syn::TypePath {
+                    path: syn::Path { segments, .. },
+                    ..
+                }) if segments.len() == 1 => {
+                    let segment = &segments[0];
+                    let payload_ty = if segment.ident == "Py" || segment.ident == "PyRef" {
+                        match &segment.arguments {
+                            syn::PathArguments::AngleBracketed(
+                                syn::AngleBracketedGenericArguments { args, .. },
+                            ) if args.len() == 1 => {
+                                let arg = &args[0];
+                                match arg {
+                                    syn::GenericArgument::Type(syn::Type::Path(
+                                        syn::TypePath {
+                                            path: syn::Path { segments, .. },
+                                            ..
+                                        },
+                                    )) if segments.len() == 1 => segments[0].ident.clone(),
+                                    _ => {
+                                        return Err(syn::Error::new_spanned(
+                                            segment,
+                                            "Py{Ref}<T> is expected but Py{Ref}<?> is found",
+                                        ))
+                                    }
+                                }
+                            }
+                            _ => {
+                                return Err(syn::Error::new_spanned(
+                                    segment,
+                                    "Py{Ref}<T> is expected but Py{Ref}? is found",
+                                ))
+                            }
+                        }
+                    } else {
+                        if !matches!(segment.arguments, syn::PathArguments::None) {
+                            return Err(syn::Error::new_spanned(
+                                segment,
+                                "PyImpl can only be implemented for Py{Ref}<T> or T",
+                            ));
+                        }
+                        segment.ident.clone()
+                    };
+                    (segment.ident.clone(), payload_ty)
+                }
+                _ => {
+                    return Err(syn::Error::new_spanned(
+                        imp.self_ty,
+                        "PyImpl can only be implemented for Py{Ref}<T> or T",
+                    ))
+                }
+            };
+
             let ExtractedImplAttrs {
+                payload: attr_payload,
                 with_impl,
                 flags,
                 with_slots,
-            } = extract_impl_attrs(attr, &Ident::new(&quote!(ty).to_string(), ty.span()))?;
-
+            } = extract_impl_attrs(attr, &impl_ty)?;
+            let payload_ty = attr_payload.unwrap_or(payload_guess);
             let getset_impl = &context.getset_items;
             let member_impl = &context.member_items;
             let extend_impl = context.impl_extend_items.validate()?;
             let slots_impl = context.extend_slots_items.validate()?;
             let class_extensions = &context.class_extensions;
-            quote! {
-                #imp
-                impl ::rustpython_vm::class::PyClassImpl for #ty {
-                    const TP_FLAGS: ::rustpython_vm::types::PyTypeFlags = #flags;
 
-                    fn impl_extend_class(
+            let extra_methods = iter_chain![
+                parse_quote! {
+                    fn __extend_py_class(
                         ctx: &::rustpython_vm::Context,
                         class: &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType>,
                     ) {
                         #getset_impl
                         #member_impl
                         #extend_impl
-                        #with_impl
                         #(#class_extensions)*
                     }
-
-                    fn extend_slots(slots: &mut ::rustpython_vm::types::PyTypeSlots) {
-                        #with_slots
+                },
+                parse_quote! {
+                    fn __extend_slots(slots: &mut ::rustpython_vm::types::PyTypeSlots) {
                         #slots_impl
                     }
+                },
+            ];
+            imp.items.extend(extra_methods);
+            let is_main_impl = impl_ty == payload_ty;
+            if is_main_impl {
+                quote! {
+                    #imp
+                    impl ::rustpython_vm::class::PyClassImpl for #payload_ty {
+                        const TP_FLAGS: ::rustpython_vm::types::PyTypeFlags = #flags;
+
+                        fn impl_extend_class(
+                            ctx: &::rustpython_vm::Context,
+                            class: &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType>,
+                        ) {
+                            #impl_ty::__extend_py_class(ctx, class);
+                            #with_impl
+                        }
+
+                        fn extend_slots(slots: &mut ::rustpython_vm::types::PyTypeSlots) {
+                            #impl_ty::__extend_slots(slots);
+                            #with_slots
+                        }
+                    }
                 }
+            } else {
+                imp.into_token_stream()
             }
         }
         Item::Trait(mut trai) => {
@@ -1163,6 +1238,7 @@ impl MemberItemMeta {
 }
 
 struct ExtractedImplAttrs {
+    payload: Option<Ident>,
     with_impl: TokenStream,
     with_slots: TokenStream,
     flags: TokenStream,
@@ -1182,6 +1258,7 @@ fn extract_impl_attrs(attr: AttributeArgs, item: &Ident) -> Result<ExtractedImpl
             }
         }
     }];
+    let mut payload = None;
 
     for attr in attr {
         match attr {
@@ -1191,18 +1268,19 @@ fn extract_impl_attrs(attr: AttributeArgs, item: &Ident) -> Result<ExtractedImpl
                         let NestedMeta::Meta(Meta::Path(path)) = meta else {
                             bail_span!(meta, "#[pyclass(with(...))] arguments should be paths")
                         };
-                        let (extend_class, extend_slots) = if path.is_ident("PyRef") {
-                            // special handling for PyRef
-                            (
-                                quote!(PyRef::<Self>::impl_extend_class),
-                                quote!(PyRef::<Self>::extend_slots),
-                            )
-                        } else {
-                            (
-                                quote!(<Self as #path>::__extend_py_class),
-                                quote!(<Self as #path>::__extend_slots),
-                            )
-                        };
+                        let (extend_class, extend_slots) =
+                            if path.is_ident("PyRef") || path.is_ident("Py") {
+                                // special handling for PyRef
+                                (
+                                    quote!(#path::<Self>::__extend_py_class),
+                                    quote!(#path::<Self>::__extend_slots),
+                                )
+                            } else {
+                                (
+                                    quote!(<Self as #path>::__extend_py_class),
+                                    quote!(<Self as #path>::__extend_slots),
+                                )
+                            };
                         let item_span = item.span().resolved_at(Span::call_site());
                         withs.push(quote_spanned! { path.span() =>
                             #extend_class(ctx, class);
@@ -1227,11 +1305,23 @@ fn extract_impl_attrs(attr: AttributeArgs, item: &Ident) -> Result<ExtractedImpl
                     bail_span!(path, "Unknown pyimpl attribute")
                 }
             }
+            NestedMeta::Meta(Meta::NameValue(syn::MetaNameValue { path, lit, .. })) => {
+                if path.is_ident("payload") {
+                    if let syn::Lit::Str(lit) = lit {
+                        payload = Some(Ident::new(&lit.value(), lit.span()));
+                    } else {
+                        bail_span!(lit, "payload must be a string literal")
+                    }
+                } else {
+                    bail_span!(path, "Unknown pyimpl attribute")
+                }
+            }
             attr => bail_span!(attr, "Unknown pyimpl attribute"),
         }
     }
 
     Ok(ExtractedImplAttrs {
+        payload,
         with_impl: quote! {
             #(#withs)*
         },
diff --git a/examples/call_between_rust_and_python.rs b/examples/call_between_rust_and_python.rs
index c139309e85..98fb434fa0 100644
--- a/examples/call_between_rust_and_python.rs
+++ b/examples/call_between_rust_and_python.rs
@@ -86,8 +86,8 @@ python_person.name: {}",
         name: String,
     }
 
-    impl TryFromBorrowedObject for PythonPerson {
-        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+    impl<'a> TryFromBorrowedObject<'a> for PythonPerson {
+        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
             let name = obj.get_attr("name", vm)?.try_into_value::<String>(vm)?;
             Ok(PythonPerson { name })
         }
diff --git a/stdlib/src/array.rs b/stdlib/src/array.rs
index b9481eabaa..5becd160df 100644
--- a/stdlib/src/array.rs
+++ b/stdlib/src/array.rs
@@ -746,7 +746,7 @@ mod array {
         }
 
         #[pymethod]
-        fn append(zelf: PyRef<Self>, x: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+        fn append(zelf: &Py<Self>, x: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
             zelf.try_resizable(vm)?.push(x, vm)
         }
 
@@ -762,12 +762,12 @@ mod array {
         }
 
         #[pymethod]
-        fn remove(zelf: PyRef<Self>, x: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+        fn remove(zelf: &Py<Self>, x: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
             zelf.try_resizable(vm)?.remove(x, vm)
         }
 
         #[pymethod]
-        fn extend(zelf: PyRef<Self>, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+        fn extend(zelf: &Py<Self>, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
             let mut w = zelf.try_resizable(vm)?;
             if zelf.is(&obj) {
                 w.imul(2, vm)
@@ -828,7 +828,7 @@ mod array {
         }
 
         #[pymethod]
-        fn fromunicode(zelf: PyRef<Self>, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+        fn fromunicode(zelf: &Py<Self>, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
             let utf8 = PyStrRef::try_from_object(vm, obj.clone()).map_err(|_| {
                 vm.new_type_error(format!(
                     "fromunicode() argument must be str, not {}",
@@ -922,18 +922,13 @@ mod array {
         }
 
         #[pymethod]
-        fn insert(
-            zelf: PyRef<Self>,
-            i: isize,
-            x: PyObjectRef,
-            vm: &VirtualMachine,
-        ) -> PyResult<()> {
+        fn insert(zelf: &Py<Self>, i: isize, x: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
             let mut w = zelf.try_resizable(vm)?;
             w.insert(i, x, vm)
         }
 
         #[pymethod]
-        fn pop(zelf: PyRef<Self>, i: OptionalArg<isize>, vm: &VirtualMachine) -> PyResult {
+        fn pop(zelf: &Py<Self>, i: OptionalArg<isize>, vm: &VirtualMachine) -> PyResult {
             let mut w = zelf.try_resizable(vm)?;
             if w.len() == 0 {
                 Err(vm.new_index_error("pop from empty array".to_owned()))
@@ -982,7 +977,7 @@ mod array {
         }
 
         #[pymethod]
-        fn fromlist(zelf: PyRef<Self>, list: PyListRef, vm: &VirtualMachine) -> PyResult<()> {
+        fn fromlist(zelf: &Py<Self>, list: PyListRef, vm: &VirtualMachine) -> PyResult<()> {
             zelf.try_resizable(vm)?.fromlist(&list, vm)
         }
 
@@ -1014,7 +1009,7 @@ mod array {
         }
 
         fn _setitem(
-            zelf: PyRef<Self>,
+            zelf: &Py<Self>,
             needle: &PyObject,
             value: PyObjectRef,
             vm: &VirtualMachine,
@@ -1052,7 +1047,7 @@ mod array {
 
         #[pymethod(magic)]
         fn setitem(
-            zelf: PyRef<Self>,
+            zelf: &Py<Self>,
             needle: PyObjectRef,
             value: PyObjectRef,
             vm: &VirtualMachine,
@@ -1120,7 +1115,7 @@ mod array {
         }
 
         #[pymethod(magic)]
-        fn repr(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<String> {
+        fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
             let class = zelf.class();
             let class_name = class.name();
             if zelf.read().typecode() == 'u' {
@@ -1167,7 +1162,7 @@ mod array {
 
         #[pymethod(magic)]
         fn reduce_ex(
-            zelf: PyRef<Self>,
+            zelf: &Py<Self>,
             proto: usize,
             vm: &VirtualMachine,
         ) -> PyResult<(PyObjectRef, PyTupleRef, Option<PyDictRef>)> {
@@ -1191,7 +1186,7 @@ mod array {
 
         #[pymethod(magic)]
         fn reduce(
-            zelf: PyRef<Self>,
+            zelf: &Py<Self>,
             vm: &VirtualMachine,
         ) -> PyResult<(PyObjectRef, PyTupleRef, Option<PyDictRef>)> {
             let array = zelf.read();
@@ -1325,7 +1320,7 @@ mod array {
                 ass_subscript: atomic_func!(|mapping, needle, value, vm| {
                     let zelf = PyArray::mapping_downcast(mapping);
                     if let Some(value) = value {
-                        PyArray::_setitem(zelf.to_owned(), needle, value, vm)
+                        PyArray::_setitem(zelf, needle, value, vm)
                     } else {
                         zelf._delitem(needle, vm)
                     }
diff --git a/stdlib/src/faulthandler.rs b/stdlib/src/faulthandler.rs
index 06c893a3a6..927ecd94a8 100644
--- a/stdlib/src/faulthandler.rs
+++ b/stdlib/src/faulthandler.rs
@@ -2,11 +2,9 @@ pub(crate) use decl::make_module;
 
 #[pymodule(name = "faulthandler")]
 mod decl {
-    use crate::vm::{
-        frame::FrameRef, function::OptionalArg, stdlib::sys::PyStderr, VirtualMachine,
-    };
+    use crate::vm::{frame::Frame, function::OptionalArg, stdlib::sys::PyStderr, VirtualMachine};
 
-    fn dump_frame(frame: &FrameRef, vm: &VirtualMachine) {
+    fn dump_frame(frame: &Frame, vm: &VirtualMachine) {
         let stderr = PyStderr(vm);
         writeln!(
             stderr,
diff --git a/stdlib/src/mmap.rs b/stdlib/src/mmap.rs
index 06cc37d313..b0a72562eb 100644
--- a/stdlib/src/mmap.rs
+++ b/stdlib/src/mmap.rs
@@ -72,8 +72,8 @@ mod mmap {
         Copy = 3,
     }
 
-    impl TryFromBorrowedObject for AccessMode {
-        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+    impl<'a> TryFromBorrowedObject<'a> for AccessMode {
+        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
             let i = u32::try_from_borrowed_object(vm, obj)?;
             Ok(match i {
                 0 => Self::Default,
diff --git a/stdlib/src/resource.rs b/stdlib/src/resource.rs
index 1d3513f47d..51f2fd7857 100644
--- a/stdlib/src/resource.rs
+++ b/stdlib/src/resource.rs
@@ -129,8 +129,8 @@ mod resource {
     }
 
     struct Limits(libc::rlimit);
-    impl TryFromBorrowedObject for Limits {
-        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+    impl<'a> TryFromBorrowedObject<'a> for Limits {
+        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
             let seq: Vec<libc::rlim_t> = obj.try_to_value(vm)?;
             match *seq {
                 [cur, max] => Ok(Self(libc::rlimit {
diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs
index 8982b80c02..cd8a95cd2c 100644
--- a/stdlib/src/sqlite.rs
+++ b/stdlib/src/sqlite.rs
@@ -883,7 +883,8 @@ mod _sqlite {
                 unsafe { cursor.row_factory.swap(zelf.row_factory.to_owned()) };
                 cursor
             } else {
-                Cursor::new(zelf.clone(), zelf.row_factory.to_owned(), vm).into_ref(vm)
+                let row_factory = zelf.row_factory.to_owned();
+                Cursor::new(zelf, row_factory, vm).into_ref(vm)
             };
             Ok(cursor)
         }
@@ -952,7 +953,8 @@ mod _sqlite {
             parameters: OptionalArg<PyObjectRef>,
             vm: &VirtualMachine,
         ) -> PyResult<PyRef<Cursor>> {
-            let cursor = Cursor::new(zelf.clone(), zelf.row_factory.to_owned(), vm).into_ref(vm);
+            let row_factory = zelf.row_factory.to_owned();
+            let cursor = Cursor::new(zelf, row_factory, vm).into_ref(vm);
             Cursor::execute(cursor, sql, parameters, vm)
         }
 
@@ -963,7 +965,8 @@ mod _sqlite {
             seq_of_params: ArgIterable,
             vm: &VirtualMachine,
         ) -> PyResult<PyRef<Cursor>> {
-            let cursor = Cursor::new(zelf.clone(), zelf.row_factory.to_owned(), vm).into_ref(vm);
+            let row_factory = zelf.row_factory.to_owned();
+            let cursor = Cursor::new(zelf, row_factory, vm).into_ref(vm);
             Cursor::executemany(cursor, sql, seq_of_params, vm)
         }
 
@@ -973,15 +976,12 @@ mod _sqlite {
             script: PyStrRef,
             vm: &VirtualMachine,
         ) -> PyResult<PyRef<Cursor>> {
-            Cursor::executescript(
-                Cursor::new(zelf.clone(), zelf.row_factory.to_owned(), vm).into_ref(vm),
-                script,
-                vm,
-            )
+            let row_factory = zelf.row_factory.to_owned();
+            Cursor::executescript(Cursor::new(zelf, row_factory, vm).into_ref(vm), script, vm)
         }
 
         #[pymethod]
-        fn backup(zelf: PyRef<Self>, args: BackupArgs, vm: &VirtualMachine) -> PyResult<()> {
+        fn backup(zelf: &Py<Self>, args: BackupArgs, vm: &VirtualMachine) -> PyResult<()> {
             let BackupArgs {
                 target,
                 pages,
@@ -1550,8 +1550,8 @@ mod _sqlite {
         }
 
         #[pymethod]
-        fn fetchone(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
-            Self::next(&zelf, vm).map(|x| match x {
+        fn fetchone(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
+            Self::next(zelf, vm).map(|x| match x {
                 PyIterReturn::Return(row) => row,
                 PyIterReturn::StopIteration(_) => vm.ctx.none(),
             })
@@ -1559,13 +1559,13 @@ mod _sqlite {
 
         #[pymethod]
         fn fetchmany(
-            zelf: PyRef<Self>,
+            zelf: &Py<Self>,
             max_rows: OptionalArg<c_int>,
             vm: &VirtualMachine,
         ) -> PyResult<Vec<PyObjectRef>> {
             let max_rows = max_rows.unwrap_or_else(|| zelf.arraysize.load(Ordering::Relaxed));
             let mut list = vec![];
-            while let PyIterReturn::Return(row) = Self::next(&zelf, vm)? {
+            while let PyIterReturn::Return(row) = Self::next(zelf, vm)? {
                 list.push(row);
                 if list.len() as c_int >= max_rows {
                     break;
@@ -1575,9 +1575,9 @@ mod _sqlite {
         }
 
         #[pymethod]
-        fn fetchall(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
+        fn fetchall(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
             let mut list = vec![];
-            while let PyIterReturn::Return(row) = Self::next(&zelf, vm)? {
+            while let PyIterReturn::Return(row) = Self::next(zelf, vm)? {
                 list.push(row);
             }
             Ok(list)
diff --git a/stdlib/src/unicodedata.rs b/stdlib/src/unicodedata.rs
index c8332740b2..be78598433 100644
--- a/stdlib/src/unicodedata.rs
+++ b/stdlib/src/unicodedata.rs
@@ -41,8 +41,8 @@ enum NormalizeForm {
     Nfkd,
 }
 
-impl TryFromBorrowedObject for NormalizeForm {
-    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+impl<'a> TryFromBorrowedObject<'a> for NormalizeForm {
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
         obj.try_value_with(
             |form: &PyStr| {
                 Ok(match form.as_str() {
diff --git a/stdlib/src/zlib.rs b/stdlib/src/zlib.rs
index f7df601854..fd3efa28b0 100644
--- a/stdlib/src/zlib.rs
+++ b/stdlib/src/zlib.rs
@@ -581,8 +581,8 @@ mod zlib {
         }
     }
 
-    impl TryFromBorrowedObject for Level {
-        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+    impl<'a> TryFromBorrowedObject<'a> for Level {
+        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
             let int: i32 = obj.try_index(vm)?.try_to_primitive(vm)?;
             Ok(Self::new(int))
         }
diff --git a/vm/src/builtins/asyncgenerator.rs b/vm/src/builtins/asyncgenerator.rs
index b9b8493a16..5b4f6265a3 100644
--- a/vm/src/builtins/asyncgenerator.rs
+++ b/vm/src/builtins/asyncgenerator.rs
@@ -26,7 +26,7 @@ impl PyPayload for PyAsyncGen {
     }
 }
 
-#[pyclass(with(Constructor, Representable))]
+#[pyclass(with(PyRef, Constructor, Representable))]
 impl PyAsyncGen {
     pub fn as_coro(&self) -> &Coro {
         &self.inner
@@ -49,20 +49,45 @@ impl PyAsyncGen {
         self.inner.set_name(name)
     }
 
+    #[pygetset]
+    fn ag_await(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
+        self.inner.frame().yield_from_target()
+    }
+    #[pygetset]
+    fn ag_frame(&self, _vm: &VirtualMachine) -> FrameRef {
+        self.inner.frame()
+    }
+    #[pygetset]
+    fn ag_running(&self, _vm: &VirtualMachine) -> bool {
+        self.inner.running()
+    }
+    #[pygetset]
+    fn ag_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
+        self.inner.frame().code.clone()
+    }
+
+    #[pyclassmethod(magic)]
+    fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
+        PyGenericAlias::new(cls, args, vm)
+    }
+}
+
+#[pyclass]
+impl PyRef<PyAsyncGen> {
     #[pymethod(magic)]
-    fn aiter(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyRef<Self> {
-        zelf
+    fn aiter(self, _vm: &VirtualMachine) -> PyRef<PyAsyncGen> {
+        self
     }
 
     #[pymethod(magic)]
-    fn anext(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyAsyncGenASend {
-        Self::asend(zelf, vm.ctx.none(), vm)
+    fn anext(self, vm: &VirtualMachine) -> PyAsyncGenASend {
+        Self::asend(self, vm.ctx.none(), vm)
     }
 
     #[pymethod]
-    fn asend(zelf: PyRef<Self>, value: PyObjectRef, _vm: &VirtualMachine) -> PyAsyncGenASend {
+    fn asend(self, value: PyObjectRef, _vm: &VirtualMachine) -> PyAsyncGenASend {
         PyAsyncGenASend {
-            ag: zelf,
+            ag: self,
             state: AtomicCell::new(AwaitableState::Init),
             value,
         }
@@ -70,14 +95,14 @@ impl PyAsyncGen {
 
     #[pymethod]
     fn athrow(
-        zelf: PyRef<Self>,
+        self,
         exc_type: PyObjectRef,
         exc_val: OptionalArg,
         exc_tb: OptionalArg,
         vm: &VirtualMachine,
     ) -> PyAsyncGenAThrow {
         PyAsyncGenAThrow {
-            ag: zelf,
+            ag: self,
             aclose: false,
             state: AtomicCell::new(AwaitableState::Init),
             value: (
@@ -89,9 +114,9 @@ impl PyAsyncGen {
     }
 
     #[pymethod]
-    fn aclose(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyAsyncGenAThrow {
+    fn aclose(self, vm: &VirtualMachine) -> PyAsyncGenAThrow {
         PyAsyncGenAThrow {
-            ag: zelf,
+            ag: self,
             aclose: true,
             state: AtomicCell::new(AwaitableState::Init),
             value: (
@@ -101,28 +126,6 @@ impl PyAsyncGen {
             ),
         }
     }
-
-    #[pygetset]
-    fn ag_await(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
-        self.inner.frame().yield_from_target()
-    }
-    #[pygetset]
-    fn ag_frame(&self, _vm: &VirtualMachine) -> FrameRef {
-        self.inner.frame()
-    }
-    #[pygetset]
-    fn ag_running(&self, _vm: &VirtualMachine) -> bool {
-        self.inner.running()
-    }
-    #[pygetset]
-    fn ag_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
-        self.inner.frame().code.clone()
-    }
-
-    #[pyclassmethod(magic)]
-    fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
-        PyGenericAlias::new(cls, args, vm)
-    }
 }
 
 impl Representable for PyAsyncGen {
diff --git a/vm/src/builtins/bool.rs b/vm/src/builtins/bool.rs
index 4da54113f7..e8d5068273 100644
--- a/vm/src/builtins/bool.rs
+++ b/vm/src/builtins/bool.rs
@@ -19,8 +19,8 @@ impl ToPyObject for bool {
     }
 }
 
-impl TryFromBorrowedObject for bool {
-    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<bool> {
+impl<'a> TryFromBorrowedObject<'a> for bool {
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<bool> {
         if obj.fast_isinstance(vm.ctx.types.int_type) {
             Ok(get_value(obj))
         } else {
diff --git a/vm/src/builtins/bytearray.rs b/vm/src/builtins/bytearray.rs
index ad01049b5e..a835d7a763 100644
--- a/vm/src/builtins/bytearray.rs
+++ b/vm/src/builtins/bytearray.rs
@@ -48,6 +48,30 @@ pub struct PyByteArray {
 
 pub type PyByteArrayRef = PyRef<PyByteArray>;
 
+impl From<PyBytesInner> for PyByteArray {
+    fn from(inner: PyBytesInner) -> Self {
+        Self::from_inner(inner)
+    }
+}
+
+impl From<Vec<u8>> for PyByteArray {
+    fn from(elements: Vec<u8>) -> Self {
+        Self::from(PyBytesInner { elements })
+    }
+}
+
+impl PyPayload for PyByteArray {
+    fn class(vm: &VirtualMachine) -> &'static Py<PyType> {
+        vm.ctx.types.bytearray_type
+    }
+}
+
+/// Fill bytearray class methods dictionary.
+pub(crate) fn init(context: &Context) {
+    PyByteArray::extend_class(context, context.types.bytearray_type);
+    PyByteArrayIterator::extend_class(context, context.types.bytearray_iterator_type);
+}
+
 impl PyByteArray {
     pub fn new_ref(data: Vec<u8>, ctx: &Context) -> PyRef<Self> {
         PyRef::new_ref(Self::from(data), ctx.types.bytearray_type.to_owned(), None)
@@ -71,35 +95,84 @@ impl PyByteArray {
     fn repeat(&self, value: isize, vm: &VirtualMachine) -> PyResult<Self> {
         self.inner().mul(value, vm).map(|x| x.into())
     }
-}
 
-impl From<PyBytesInner> for PyByteArray {
-    fn from(inner: PyBytesInner) -> Self {
-        Self::from_inner(inner)
+    fn _setitem_by_index(&self, i: isize, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+        let value = value_from_object(vm, &value)?;
+        self.borrow_buf_mut().setitem_by_index(vm, i, value)
     }
-}
 
-impl From<Vec<u8>> for PyByteArray {
-    fn from(elements: Vec<u8>) -> Self {
-        Self::from(PyBytesInner { elements })
+    fn _setitem(
+        zelf: &Py<Self>,
+        needle: &PyObject,
+        value: PyObjectRef,
+        vm: &VirtualMachine,
+    ) -> PyResult<()> {
+        match SequenceIndex::try_from_borrowed_object(vm, needle, "bytearray")? {
+            SequenceIndex::Int(i) => zelf._setitem_by_index(i, value, vm),
+            SequenceIndex::Slice(slice) => {
+                let items = if zelf.is(&value) {
+                    zelf.borrow_buf().to_vec()
+                } else {
+                    bytes_from_object(vm, &value)?
+                };
+                if let Some(mut w) = zelf.try_resizable_opt() {
+                    w.elements.setitem_by_slice(vm, slice, &items)
+                } else {
+                    zelf.borrow_buf_mut()
+                        .setitem_by_slice_no_resize(vm, slice, &items)
+                }
+            }
+        }
     }
-}
 
-impl PyPayload for PyByteArray {
-    fn class(vm: &VirtualMachine) -> &'static Py<PyType> {
-        vm.ctx.types.bytearray_type
+    fn _getitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult {
+        match SequenceIndex::try_from_borrowed_object(vm, needle, "bytearray")? {
+            SequenceIndex::Int(i) => self
+                .borrow_buf()
+                .getitem_by_index(vm, i)
+                .map(|x| vm.ctx.new_int(x).into()),
+            SequenceIndex::Slice(slice) => self
+                .borrow_buf()
+                .getitem_by_slice(vm, slice)
+                .map(|x| Self::new_ref(x, &vm.ctx).into()),
+        }
     }
-}
 
-/// Fill bytearray class methods dictionary.
-pub(crate) fn init(context: &Context) {
-    PyByteArray::extend_class(context, context.types.bytearray_type);
-    PyByteArrayIterator::extend_class(context, context.types.bytearray_iterator_type);
+    pub fn _delitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
+        match SequenceIndex::try_from_borrowed_object(vm, needle, "bytearray")? {
+            SequenceIndex::Int(i) => self.try_resizable(vm)?.elements.delitem_by_index(vm, i),
+            SequenceIndex::Slice(slice) => {
+                // TODO: delete 0 elements don't need resizable
+                self.try_resizable(vm)?.elements.delitem_by_slice(vm, slice)
+            }
+        }
+    }
+
+    fn irepeat(zelf: &Py<Self>, n: isize, vm: &VirtualMachine) -> PyResult<()> {
+        if n == 1 {
+            return Ok(());
+        }
+        let mut w = match zelf.try_resizable(vm) {
+            Ok(w) => w,
+            Err(err) => {
+                return if zelf.borrow_buf().is_empty() {
+                    // We can multiple an empty vector by any integer
+                    Ok(())
+                } else {
+                    Err(err)
+                };
+            }
+        };
+
+        w.imul(n, vm)
+    }
 }
 
 #[pyclass(
     flags(BASETYPE),
     with(
+        Py,
+        PyRef,
         Constructor,
         Initializer,
         Comparable,
@@ -156,45 +229,6 @@ impl PyByteArray {
         self.inner().contains(needle, vm)
     }
 
-    fn _setitem_by_index(&self, i: isize, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
-        let value = value_from_object(vm, &value)?;
-        self.borrow_buf_mut().setitem_by_index(vm, i, value)
-    }
-
-    fn _setitem(
-        zelf: PyRef<Self>,
-        needle: &PyObject,
-        value: PyObjectRef,
-        vm: &VirtualMachine,
-    ) -> PyResult<()> {
-        match SequenceIndex::try_from_borrowed_object(vm, needle, "bytearray")? {
-            SequenceIndex::Int(i) => zelf._setitem_by_index(i, value, vm),
-            SequenceIndex::Slice(slice) => {
-                let items = if zelf.is(&value) {
-                    zelf.borrow_buf().to_vec()
-                } else {
-                    bytes_from_object(vm, &value)?
-                };
-                if let Some(mut w) = zelf.try_resizable_opt() {
-                    w.elements.setitem_by_slice(vm, slice, &items)
-                } else {
-                    zelf.borrow_buf_mut()
-                        .setitem_by_slice_no_resize(vm, slice, &items)
-                }
-            }
-        }
-    }
-
-    #[pymethod(magic)]
-    fn setitem(
-        zelf: PyRef<Self>,
-        needle: PyObjectRef,
-        value: PyObjectRef,
-        vm: &VirtualMachine,
-    ) -> PyResult<()> {
-        Self::_setitem(zelf, &needle, value, vm)
-    }
-
     #[pymethod(magic)]
     fn iadd(zelf: PyRef<Self>, other: ArgBytesLike, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
         zelf.try_resizable(vm)?
@@ -203,34 +237,11 @@ impl PyByteArray {
         Ok(zelf)
     }
 
-    fn _getitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult {
-        match SequenceIndex::try_from_borrowed_object(vm, needle, "bytearray")? {
-            SequenceIndex::Int(i) => self
-                .borrow_buf()
-                .getitem_by_index(vm, i)
-                .map(|x| vm.ctx.new_int(x).into()),
-            SequenceIndex::Slice(slice) => self
-                .borrow_buf()
-                .getitem_by_slice(vm, slice)
-                .map(|x| Self::new_ref(x, &vm.ctx).into()),
-        }
-    }
-
     #[pymethod(magic)]
     fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
         self._getitem(&needle, vm)
     }
 
-    pub fn _delitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
-        match SequenceIndex::try_from_borrowed_object(vm, needle, "bytearray")? {
-            SequenceIndex::Int(i) => self.try_resizable(vm)?.elements.delitem_by_index(vm, i),
-            SequenceIndex::Slice(slice) => {
-                // TODO: delete 0 elements don't need resizable
-                self.try_resizable(vm)?.elements.delitem_by_slice(vm, slice)
-            }
-        }
-    }
-
     #[pymethod(magic)]
     pub fn delitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
         self._delitem(&needle, vm)
@@ -241,78 +252,6 @@ impl PyByteArray {
         PyBytesInner::maketrans(from, to, vm)
     }
 
-    #[pymethod]
-    fn pop(zelf: PyRef<Self>, index: OptionalArg<isize>, vm: &VirtualMachine) -> PyResult<u8> {
-        let elements = &mut zelf.try_resizable(vm)?.elements;
-        let index = elements
-            .wrap_index(index.unwrap_or(-1))
-            .ok_or_else(|| vm.new_index_error("index out of range".to_owned()))?;
-        Ok(elements.remove(index))
-    }
-
-    #[pymethod]
-    fn insert(
-        zelf: PyRef<Self>,
-        index: isize,
-        object: PyObjectRef,
-        vm: &VirtualMachine,
-    ) -> PyResult<()> {
-        let value = value_from_object(vm, &object)?;
-        let elements = &mut zelf.try_resizable(vm)?.elements;
-        let index = elements.saturate_index(index);
-        elements.insert(index, value);
-        Ok(())
-    }
-
-    #[pymethod]
-    fn append(zelf: PyRef<Self>, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
-        let value = value_from_object(vm, &object)?;
-        zelf.try_resizable(vm)?.elements.push(value);
-        Ok(())
-    }
-
-    #[pymethod]
-    fn remove(zelf: PyRef<Self>, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
-        let value = value_from_object(vm, &object)?;
-        let elements = &mut zelf.try_resizable(vm)?.elements;
-        if let Some(index) = elements.find_byte(value) {
-            elements.remove(index);
-            Ok(())
-        } else {
-            Err(vm.new_value_error("value not found in bytearray".to_owned()))
-        }
-    }
-
-    #[pymethod]
-    fn extend(zelf: PyRef<Self>, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
-        if zelf.is(&object) {
-            Self::irepeat(&zelf, 2, vm)
-        } else {
-            let items = bytes_from_object(vm, &object)?;
-            zelf.try_resizable(vm)?.elements.extend(items);
-            Ok(())
-        }
-    }
-
-    fn irepeat(zelf: &Py<Self>, n: isize, vm: &VirtualMachine) -> PyResult<()> {
-        if n == 1 {
-            return Ok(());
-        }
-        let mut w = match zelf.try_resizable(vm) {
-            Ok(w) => w,
-            Err(err) => {
-                return if zelf.borrow_buf().is_empty() {
-                    // We can multiple an empty vector by any integer
-                    Ok(())
-                } else {
-                    Err(err)
-                };
-            }
-        };
-
-        w.imul(n, vm)
-    }
-
     #[pymethod]
     fn isalnum(&self) -> bool {
         self.inner().isalnum()
@@ -503,59 +442,11 @@ impl PyByteArray {
         self.inner().strip(chars).into()
     }
 
-    #[pymethod]
-    fn lstrip(
-        zelf: PyRef<Self>,
-        chars: OptionalOption<PyBytesInner>,
-        vm: &VirtualMachine,
-    ) -> PyRef<Self> {
-        let inner = zelf.inner();
-        let stripped = inner.lstrip(chars);
-        let elements = &inner.elements;
-        if stripped == elements {
-            drop(inner);
-            zelf
-        } else {
-            vm.new_pyref(PyByteArray::from(stripped.to_vec()))
-        }
-    }
-
-    #[pymethod]
-    fn rstrip(
-        zelf: PyRef<Self>,
-        chars: OptionalOption<PyBytesInner>,
-        vm: &VirtualMachine,
-    ) -> PyRef<Self> {
-        let inner = zelf.inner();
-        let stripped = inner.rstrip(chars);
-        let elements = &inner.elements;
-        if stripped == elements {
-            drop(inner);
-            zelf
-        } else {
-            vm.new_pyref(PyByteArray::from(stripped.to_vec()))
-        }
-    }
-
-    /// removeprefix($self, prefix, /)
-    ///
-    ///
-    /// Return a bytearray object with the given prefix string removed if present.
-    ///
-    /// If the bytearray starts with the prefix string, return string[len(prefix):]
-    /// Otherwise, return a copy of the original bytearray.
     #[pymethod]
     fn removeprefix(&self, prefix: PyBytesInner) -> Self {
         self.inner().removeprefix(prefix).into()
     }
 
-    /// removesuffix(self, prefix, /)
-    ///
-    ///
-    /// Return a bytearray object with the given suffix string removed if present.
-    ///
-    /// If the bytearray ends with the suffix string, return string[:len(suffix)]
-    /// Otherwise, return a copy of the original bytearray.
     #[pymethod]
     fn removesuffix(&self, suffix: PyBytesInner) -> Self {
         self.inner().removesuffix(suffix).to_vec().into()
@@ -638,12 +529,6 @@ impl PyByteArray {
         Ok(self.inner().replace(old, new, count, vm)?.into())
     }
 
-    #[pymethod]
-    fn clear(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<()> {
-        zelf.try_resizable(vm)?.elements.clear();
-        Ok(())
-    }
-
     #[pymethod]
     fn copy(&self) -> Self {
         self.borrow_buf().to_vec().into()
@@ -681,35 +566,135 @@ impl PyByteArray {
     fn reverse(&self) {
         self.borrow_buf_mut().reverse();
     }
+}
+
+#[pyclass]
+impl Py<PyByteArray> {
+    #[pymethod(magic)]
+    fn setitem(
+        &self,
+        needle: PyObjectRef,
+        value: PyObjectRef,
+        vm: &VirtualMachine,
+    ) -> PyResult<()> {
+        PyByteArray::_setitem(self, &needle, value, vm)
+    }
 
     #[pymethod]
-    fn decode(zelf: PyRef<Self>, args: DecodeArgs, vm: &VirtualMachine) -> PyResult<PyStrRef> {
-        bytes_decode(zelf.into(), args, vm)
+    fn pop(&self, index: OptionalArg<isize>, vm: &VirtualMachine) -> PyResult<u8> {
+        let elements = &mut self.try_resizable(vm)?.elements;
+        let index = elements
+            .wrap_index(index.unwrap_or(-1))
+            .ok_or_else(|| vm.new_index_error("index out of range".to_owned()))?;
+        Ok(elements.remove(index))
+    }
+
+    #[pymethod]
+    fn insert(&self, index: isize, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+        let value = value_from_object(vm, &object)?;
+        let elements = &mut self.try_resizable(vm)?.elements;
+        let index = elements.saturate_index(index);
+        elements.insert(index, value);
+        Ok(())
+    }
+
+    #[pymethod]
+    fn append(&self, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+        let value = value_from_object(vm, &object)?;
+        self.try_resizable(vm)?.elements.push(value);
+        Ok(())
+    }
+
+    #[pymethod]
+    fn remove(&self, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+        let value = value_from_object(vm, &object)?;
+        let elements = &mut self.try_resizable(vm)?.elements;
+        let index = elements
+            .find_byte(value)
+            .ok_or_else(|| vm.new_value_error("value not found in bytearray".to_owned()))?;
+        elements.remove(index);
+        Ok(())
+    }
+
+    #[pymethod]
+    fn extend(&self, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+        if self.is(&object) {
+            PyByteArray::irepeat(self, 2, vm)
+        } else {
+            let items = bytes_from_object(vm, &object)?;
+            self.try_resizable(vm)?.elements.extend(items);
+            Ok(())
+        }
+    }
+
+    #[pymethod]
+    fn clear(&self, vm: &VirtualMachine) -> PyResult<()> {
+        self.try_resizable(vm)?.elements.clear();
+        Ok(())
     }
 
     #[pymethod(magic)]
     fn reduce_ex(
-        zelf: PyRef<Self>,
+        &self,
         _proto: usize,
         vm: &VirtualMachine,
     ) -> (PyTypeRef, PyTupleRef, Option<PyDictRef>) {
-        Self::reduce(zelf, vm)
+        Self::reduce(self, vm)
     }
 
     #[pymethod(magic)]
-    fn reduce(
-        zelf: PyRef<Self>,
-        vm: &VirtualMachine,
-    ) -> (PyTypeRef, PyTupleRef, Option<PyDictRef>) {
-        let bytes = PyBytes::from(zelf.borrow_buf().to_vec()).to_pyobject(vm);
+    fn reduce(&self, vm: &VirtualMachine) -> (PyTypeRef, PyTupleRef, Option<PyDictRef>) {
+        let bytes = PyBytes::from(self.borrow_buf().to_vec()).to_pyobject(vm);
         (
-            zelf.class().to_owned(),
+            self.class().to_owned(),
             PyTuple::new_ref(vec![bytes], &vm.ctx),
-            zelf.as_object().dict(),
+            self.as_object().dict(),
         )
     }
 }
 
+#[pyclass]
+impl PyRef<PyByteArray> {
+    #[pymethod]
+    fn lstrip(
+        self,
+        chars: OptionalOption<PyBytesInner>,
+        vm: &VirtualMachine,
+    ) -> PyRef<PyByteArray> {
+        let inner = self.inner();
+        let stripped = inner.lstrip(chars);
+        let elements = &inner.elements;
+        if stripped == elements {
+            drop(inner);
+            self
+        } else {
+            vm.new_pyref(PyByteArray::from(stripped.to_vec()))
+        }
+    }
+
+    #[pymethod]
+    fn rstrip(
+        self,
+        chars: OptionalOption<PyBytesInner>,
+        vm: &VirtualMachine,
+    ) -> PyRef<PyByteArray> {
+        let inner = self.inner();
+        let stripped = inner.rstrip(chars);
+        let elements = &inner.elements;
+        if stripped == elements {
+            drop(inner);
+            self
+        } else {
+            vm.new_pyref(PyByteArray::from(stripped.to_vec()))
+        }
+    }
+
+    #[pymethod]
+    fn decode(self, args: DecodeArgs, vm: &VirtualMachine) -> PyResult<PyStrRef> {
+        bytes_decode(self.into(), args, vm)
+    }
+}
+
 impl Constructor for PyByteArray {
     type Args = FuncArgs;
 
@@ -796,7 +781,7 @@ impl AsMapping for PyByteArray {
             ass_subscript: atomic_func!(|mapping, needle, value, vm| {
                 let zelf = PyByteArray::mapping_downcast(mapping);
                 if let Some(value) = value {
-                    PyByteArray::setitem(zelf.to_owned(), needle.to_owned(), value, vm)
+                    Py::setitem(zelf, needle.to_owned(), value, vm)
                 } else {
                     zelf.delitem(needle.to_owned(), vm)
                 }
diff --git a/vm/src/builtins/bytes.rs b/vm/src/builtins/bytes.rs
index 1d1cdfabbe..5715d3f3bb 100644
--- a/vm/src/builtins/bytes.rs
+++ b/vm/src/builtins/bytes.rs
@@ -101,23 +101,38 @@ impl PyBytes {
         PyRef::new_ref(Self::from(data), ctx.types.bytes_type.to_owned(), None)
     }
 
-    fn repeat(zelf: PyRef<Self>, count: isize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
-        if count == 1 && zelf.class().is(vm.ctx.types.bytes_type) {
+    fn _getitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult {
+        match SequenceIndex::try_from_borrowed_object(vm, needle, "byte")? {
+            SequenceIndex::Int(i) => self
+                .getitem_by_index(vm, i)
+                .map(|x| vm.ctx.new_int(x).into()),
+            SequenceIndex::Slice(slice) => self
+                .getitem_by_slice(vm, slice)
+                .map(|x| vm.ctx.new_bytes(x).into()),
+        }
+    }
+}
+
+impl PyRef<PyBytes> {
+    fn repeat(self, count: isize, vm: &VirtualMachine) -> PyResult<PyRef<PyBytes>> {
+        if count == 1 && self.class().is(vm.ctx.types.bytes_type) {
             // Special case: when some `bytes` is multiplied by `1`,
             // nothing really happens, we need to return an object itself
             // with the same `id()` to be compatible with CPython.
             // This only works for `bytes` itself, not its subclasses.
-            return Ok(zelf);
+            return Ok(self);
         }
-        zelf.inner
+        self.inner
             .mul(count, vm)
-            .map(|x| Self::from(x).into_ref(vm))
+            .map(|x| PyBytes::from(x).into_ref(vm))
     }
 }
 
 #[pyclass(
     flags(BASETYPE),
     with(
+        Py,
+        PyRef,
         AsMapping,
         AsSequence,
         Hashable,
@@ -146,15 +161,6 @@ impl PyBytes {
         self.inner.as_bytes()
     }
 
-    #[pymethod(magic)]
-    fn bytes(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyRef<Self> {
-        if zelf.is(vm.ctx.types.bytes_type) {
-            zelf
-        } else {
-            PyBytes::from(zelf.inner.clone()).into_ref(vm)
-        }
-    }
-
     #[pymethod(magic)]
     fn sizeof(&self) -> usize {
         size_of::<Self>() + self.len() * size_of::<u8>()
@@ -179,17 +185,6 @@ impl PyBytes {
         PyBytesInner::maketrans(from, to, vm)
     }
 
-    fn _getitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult {
-        match SequenceIndex::try_from_borrowed_object(vm, needle, "byte")? {
-            SequenceIndex::Int(i) => self
-                .getitem_by_index(vm, i)
-                .map(|x| vm.ctx.new_int(x).into()),
-            SequenceIndex::Slice(slice) => self
-                .getitem_by_slice(vm, slice)
-                .map(|x| vm.ctx.new_bytes(x).into()),
-        }
-    }
-
     #[pymethod(magic)]
     fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
         self._getitem(&needle, vm)
@@ -371,53 +366,11 @@ impl PyBytes {
         self.inner.strip(chars).into()
     }
 
-    #[pymethod]
-    fn lstrip(
-        zelf: PyRef<Self>,
-        chars: OptionalOption<PyBytesInner>,
-        vm: &VirtualMachine,
-    ) -> PyRef<Self> {
-        let stripped = zelf.inner.lstrip(chars);
-        if stripped == zelf.as_bytes() {
-            zelf
-        } else {
-            vm.ctx.new_bytes(stripped.to_vec())
-        }
-    }
-
-    #[pymethod]
-    fn rstrip(
-        zelf: PyRef<Self>,
-        chars: OptionalOption<PyBytesInner>,
-        vm: &VirtualMachine,
-    ) -> PyRef<Self> {
-        let stripped = zelf.inner.rstrip(chars);
-        if stripped == zelf.as_bytes() {
-            zelf
-        } else {
-            vm.ctx.new_bytes(stripped.to_vec())
-        }
-    }
-
-    /// removeprefix($self, prefix, /)
-    ///
-    ///
-    /// Return a bytes object with the given prefix string removed if present.
-    ///
-    /// If the bytes starts with the prefix string, return string[len(prefix):]
-    /// Otherwise, return a copy of the original bytes.
     #[pymethod]
     fn removeprefix(&self, prefix: PyBytesInner) -> Self {
         self.inner.removeprefix(prefix).into()
     }
 
-    /// removesuffix(self, prefix, /)
-    ///
-    ///
-    /// Return a bytes object with the given suffix string removed if present.
-    ///
-    /// If the bytes ends with the suffix string, return string[:len(suffix)]
-    /// Otherwise, return a copy of the original bytes.
     #[pymethod]
     fn removesuffix(&self, suffix: PyBytesInner) -> Self {
         self.inner.removesuffix(suffix).into()
@@ -508,7 +461,7 @@ impl PyBytes {
     #[pymethod(name = "__rmul__")]
     #[pymethod(magic)]
     fn mul(zelf: PyRef<Self>, value: ArgIndex, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
-        Self::repeat(zelf, value.try_to_primitive(vm)?, vm)
+        zelf.repeat(value.try_to_primitive(vm)?, vm)
     }
 
     #[pymethod(name = "__mod__")]
@@ -522,47 +475,79 @@ impl PyBytes {
         vm.ctx.not_implemented()
     }
 
-    /// Return a string decoded from the given bytes.
-    /// Default encoding is 'utf-8'.
-    /// Default errors is 'strict', meaning that encoding errors raise a UnicodeError.
-    /// Other possible values are 'ignore', 'replace'
-    /// For a list of possible encodings,
-    /// see https://docs.python.org/3/library/codecs.html#standard-encodings
-    /// currently, only 'utf-8' and 'ascii' emplemented
-    #[pymethod]
-    fn decode(zelf: PyRef<Self>, args: DecodeArgs, vm: &VirtualMachine) -> PyResult<PyStrRef> {
-        bytes_decode(zelf.into(), args, vm)
-    }
-
     #[pymethod(magic)]
     fn getnewargs(&self, vm: &VirtualMachine) -> PyTupleRef {
         let param: Vec<PyObjectRef> = self.elements().map(|x| x.to_pyobject(vm)).collect();
         PyTuple::new_ref(param, &vm.ctx)
     }
+}
 
+#[pyclass]
+impl Py<PyBytes> {
     #[pymethod(magic)]
     fn reduce_ex(
-        zelf: PyRef<Self>,
+        &self,
         _proto: usize,
         vm: &VirtualMachine,
     ) -> (PyTypeRef, PyTupleRef, Option<PyDictRef>) {
-        Self::reduce(zelf, vm)
+        Self::reduce(self, vm)
     }
 
     #[pymethod(magic)]
-    fn reduce(
-        zelf: PyRef<Self>,
-        vm: &VirtualMachine,
-    ) -> (PyTypeRef, PyTupleRef, Option<PyDictRef>) {
-        let bytes = PyBytes::from(zelf.to_vec()).to_pyobject(vm);
+    fn reduce(&self, vm: &VirtualMachine) -> (PyTypeRef, PyTupleRef, Option<PyDictRef>) {
+        let bytes = PyBytes::from(self.to_vec()).to_pyobject(vm);
         (
-            zelf.class().to_owned(),
+            self.class().to_owned(),
             PyTuple::new_ref(vec![bytes], &vm.ctx),
-            zelf.as_object().dict(),
+            self.as_object().dict(),
         )
     }
 }
 
+#[pyclass]
+impl PyRef<PyBytes> {
+    #[pymethod(magic)]
+    fn bytes(self, vm: &VirtualMachine) -> PyRef<PyBytes> {
+        if self.is(vm.ctx.types.bytes_type) {
+            self
+        } else {
+            PyBytes::from(self.inner.clone()).into_ref(vm)
+        }
+    }
+
+    #[pymethod]
+    fn lstrip(self, chars: OptionalOption<PyBytesInner>, vm: &VirtualMachine) -> PyRef<PyBytes> {
+        let stripped = self.inner.lstrip(chars);
+        if stripped == self.as_bytes() {
+            self
+        } else {
+            vm.ctx.new_bytes(stripped.to_vec())
+        }
+    }
+
+    #[pymethod]
+    fn rstrip(self, chars: OptionalOption<PyBytesInner>, vm: &VirtualMachine) -> PyRef<PyBytes> {
+        let stripped = self.inner.rstrip(chars);
+        if stripped == self.as_bytes() {
+            self
+        } else {
+            vm.ctx.new_bytes(stripped.to_vec())
+        }
+    }
+
+    /// Return a string decoded from the given bytes.
+    /// Default encoding is 'utf-8'.
+    /// Default errors is 'strict', meaning that encoding errors raise a UnicodeError.
+    /// Other possible values are 'ignore', 'replace'
+    /// For a list of possible encodings,
+    /// see https://docs.python.org/3/library/codecs.html#standard-encodings
+    /// currently, only 'utf-8' and 'ascii' emplemented
+    #[pymethod]
+    fn decode(self, args: DecodeArgs, vm: &VirtualMachine) -> PyResult<PyStrRef> {
+        bytes_decode(self.into(), args, vm)
+    }
+}
+
 static BUFFER_METHODS: BufferMethods = BufferMethods {
     obj_bytes: |buffer| buffer.obj_as::<PyBytes>().as_bytes().into(),
     obj_bytes_mut: |_| panic!(),
@@ -605,11 +590,10 @@ impl AsSequence for PyBytes {
                     .map(|x| vm.ctx.new_bytes(x).into())
             }),
             repeat: atomic_func!(|seq, n, vm| {
-                if let Ok(zelf) = seq.obj.to_owned().downcast::<PyBytes>() {
-                    PyBytes::repeat(zelf, n, vm).to_pyresult(vm)
-                } else {
-                    Err(vm.new_type_error("bad argument type for built-in operation".to_owned()))
-                }
+                let zelf = seq.obj.to_owned().downcast::<PyBytes>().map_err(|_| {
+                    vm.new_type_error("bad argument type for built-in operation".to_owned())
+                })?;
+                zelf.repeat(n, vm).to_pyresult(vm)
             }),
             item: atomic_func!(|seq, i, vm| {
                 PyBytes::sequence_downcast(seq)
@@ -742,8 +726,8 @@ impl IterNext for PyBytesIterator {
     }
 }
 
-impl TryFromBorrowedObject for PyBytes {
-    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+impl<'a> TryFromBorrowedObject<'a> for PyBytes {
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
         PyBytesInner::try_from_borrowed_object(vm, obj).map(|x| x.into())
     }
 }
diff --git a/vm/src/builtins/code.rs b/vm/src/builtins/code.rs
index 2d44c2fa33..34ed065b67 100644
--- a/vm/src/builtins/code.rs
+++ b/vm/src/builtins/code.rs
@@ -10,7 +10,7 @@ use crate::{
     convert::ToPyObject,
     function::{FuncArgs, OptionalArg},
     types::Representable,
-    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
+    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
 };
 use num_traits::Zero;
 use std::{borrow::Borrow, fmt, ops::Deref};
@@ -216,9 +216,6 @@ impl PyPayload for PyCode {
     }
 }
 
-#[pyclass(with(PyRef, Representable))]
-impl PyCode {}
-
 impl Representable for PyCode {
     #[inline]
     fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
@@ -233,35 +230,35 @@ impl Representable for PyCode {
     }
 }
 
-#[pyclass]
-impl PyRef<PyCode> {
+#[pyclass(with(Py))]
+impl PyCode {
     #[pyslot]
     fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
         Err(vm.new_type_error("Cannot directly create code object".to_owned()))
     }
 
     #[pygetset]
-    fn co_posonlyargcount(self) -> usize {
+    fn co_posonlyargcount(&self) -> usize {
         self.code.posonlyarg_count as usize
     }
 
     #[pygetset]
-    fn co_argcount(self) -> usize {
+    fn co_argcount(&self) -> usize {
         self.code.arg_count as usize
     }
 
     #[pygetset]
-    fn co_stacksize(self) -> u32 {
+    fn co_stacksize(&self) -> u32 {
         self.code.max_stackdepth
     }
 
     #[pygetset]
-    pub fn co_filename(self) -> PyStrRef {
+    pub fn co_filename(&self) -> PyStrRef {
         self.code.source_path.to_owned()
     }
 
     #[pygetset]
-    pub fn co_cellvars(self, vm: &VirtualMachine) -> PyTupleRef {
+    pub fn co_cellvars(&self, vm: &VirtualMachine) -> PyTupleRef {
         let cellvars = self
             .code
             .cellvars
@@ -273,33 +270,33 @@ impl PyRef<PyCode> {
     }
 
     #[pygetset]
-    fn co_nlocals(self) -> usize {
+    fn co_nlocals(&self) -> usize {
         self.varnames.len()
     }
 
     #[pygetset]
-    fn co_firstlineno(self) -> usize {
+    fn co_firstlineno(&self) -> usize {
         self.code.first_line_number as usize
     }
 
     #[pygetset]
-    fn co_kwonlyargcount(self) -> usize {
+    fn co_kwonlyargcount(&self) -> usize {
         self.code.kwonlyarg_count as usize
     }
 
     #[pygetset]
-    fn co_consts(self, vm: &VirtualMachine) -> PyTupleRef {
+    fn co_consts(&self, vm: &VirtualMachine) -> PyTupleRef {
         let consts = self.code.constants.iter().map(|x| x.0.clone()).collect();
         vm.ctx.new_tuple(consts)
     }
 
     #[pygetset]
-    fn co_name(self) -> PyStrRef {
+    fn co_name(&self) -> PyStrRef {
         self.code.obj_name.to_owned()
     }
 
     #[pygetset]
-    fn co_names(self, vm: &VirtualMachine) -> PyTupleRef {
+    fn co_names(&self, vm: &VirtualMachine) -> PyTupleRef {
         let names = self
             .code
             .names
@@ -311,18 +308,18 @@ impl PyRef<PyCode> {
     }
 
     #[pygetset]
-    fn co_flags(self) -> u16 {
+    fn co_flags(&self) -> u16 {
         self.code.flags.bits()
     }
 
     #[pygetset]
-    pub fn co_varnames(self, vm: &VirtualMachine) -> PyTupleRef {
+    pub fn co_varnames(&self, vm: &VirtualMachine) -> PyTupleRef {
         let varnames = self.code.varnames.iter().map(|s| s.to_object()).collect();
         vm.ctx.new_tuple(varnames)
     }
 
     #[pygetset]
-    pub fn co_freevars(self, vm: &VirtualMachine) -> PyTupleRef {
+    pub fn co_freevars(&self, vm: &VirtualMachine) -> PyTupleRef {
         let names = self
             .code
             .freevars
@@ -334,7 +331,7 @@ impl PyRef<PyCode> {
     }
 
     #[pymethod]
-    pub fn replace(self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<PyCode> {
+    pub fn replace(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<PyCode> {
         let posonlyarg_count = match args.co_posonlyargcount {
             OptionalArg::Present(posonlyarg_count) => posonlyarg_count,
             OptionalArg::Missing => self.code.posonlyarg_count,
@@ -421,6 +418,21 @@ impl PyRef<PyCode> {
     }
 }
 
+#[pyclass]
+impl Py<PyCode> {
+    #[pymethod(magic)]
+    fn repr(&self) -> String {
+        let code = &self.code;
+        format!(
+            "<code object {} at {:#x} file {:?}, line {}>",
+            code.obj_name,
+            self.get_id(),
+            code.source_path.as_str(),
+            code.first_line_number
+        )
+    }
+}
+
 impl fmt::Display for PyCode {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         (**self).fmt(f)
@@ -440,5 +452,5 @@ impl ToPyObject for bytecode::CodeObject {
 }
 
 pub fn init(ctx: &Context) {
-    PyRef::<PyCode>::extend_class(ctx, ctx.types.code_type);
+    PyCode::extend_class(ctx, ctx.types.code_type);
 }
diff --git a/vm/src/builtins/coroutine.rs b/vm/src/builtins/coroutine.rs
index 7c400274e8..76d15b3aeb 100644
--- a/vm/src/builtins/coroutine.rs
+++ b/vm/src/builtins/coroutine.rs
@@ -45,13 +45,13 @@ impl PyCoroutine {
     }
 
     #[pymethod]
-    fn send(zelf: PyRef<Self>, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
+    fn send(zelf: &Py<Self>, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
         zelf.inner.send(zelf.as_object(), value, vm)
     }
 
     #[pymethod]
     fn throw(
-        zelf: PyRef<Self>,
+        zelf: &Py<Self>,
         exc_type: PyObjectRef,
         exc_val: OptionalArg,
         exc_tb: OptionalArg,
@@ -67,7 +67,7 @@ impl PyCoroutine {
     }
 
     #[pymethod]
-    fn close(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<()> {
+    fn close(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()> {
         zelf.inner.close(zelf.as_object(), vm)
     }
 
@@ -111,7 +111,7 @@ impl Representable for PyCoroutine {
 impl IterNextIterable for PyCoroutine {}
 impl IterNext for PyCoroutine {
     fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
-        Self::send(zelf.to_owned(), vm.ctx.none(), vm)
+        Self::send(zelf, vm.ctx.none(), vm)
     }
 }
 
@@ -131,8 +131,8 @@ impl PyPayload for PyCoroutineWrapper {
 #[pyclass(with(IterNext))]
 impl PyCoroutineWrapper {
     #[pymethod]
-    fn send(zelf: PyRef<Self>, val: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
-        PyCoroutine::send(zelf.coro.clone(), val, vm)
+    fn send(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
+        PyCoroutine::send(&self.coro, val, vm)
     }
 
     #[pymethod]
@@ -143,14 +143,14 @@ impl PyCoroutineWrapper {
         exc_tb: OptionalArg,
         vm: &VirtualMachine,
     ) -> PyResult<PyIterReturn> {
-        PyCoroutine::throw(self.coro.clone(), exc_type, exc_val, exc_tb, vm)
+        PyCoroutine::throw(&self.coro, exc_type, exc_val, exc_tb, vm)
     }
 }
 
 impl IterNextIterable for PyCoroutineWrapper {}
 impl IterNext for PyCoroutineWrapper {
     fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
-        Self::send(zelf.to_owned(), vm.ctx.none(), vm)
+        Self::send(zelf, vm.ctx.none(), vm)
     }
 }
 
diff --git a/vm/src/builtins/descriptor.rs b/vm/src/builtins/descriptor.rs
index 8d4154f0cb..8149c30658 100644
--- a/vm/src/builtins/descriptor.rs
+++ b/vm/src/builtins/descriptor.rs
@@ -3,7 +3,7 @@ use crate::{
     class::PyClassImpl,
     function::PySetterValue,
     types::{Constructor, GetDescriptor, Representable, Unconstructible},
-    AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
+    AsObject, Context, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
 };
 use rustpython_common::lock::PyRwLock;
 
@@ -103,8 +103,8 @@ fn calculate_qualname(descr: &DescrObject, vm: &VirtualMachine) -> PyResult<Opti
 #[pyclass(with(GetDescriptor, Constructor, Representable), flags(BASETYPE))]
 impl MemberDescrObject {
     #[pygetset(magic)]
-    fn doc(zelf: PyRef<Self>) -> Option<String> {
-        zelf.member.doc.to_owned()
+    fn doc(&self) -> Option<String> {
+        self.member.doc.to_owned()
     }
 
     #[pygetset(magic)]
diff --git a/vm/src/builtins/dict.rs b/vm/src/builtins/dict.rs
index 6b84c403f8..74d71ddac1 100644
--- a/vm/src/builtins/dict.rs
+++ b/vm/src/builtins/dict.rs
@@ -203,6 +203,8 @@ impl PyDict {
 #[allow(clippy::len_without_is_empty)]
 #[pyclass(
     with(
+        Py,
+        PyRef,
         Constructor,
         Initializer,
         AsMapping,
@@ -270,32 +272,11 @@ impl PyDict {
         self.entries.clear()
     }
 
-    #[pymethod]
-    fn keys(zelf: PyRef<Self>) -> PyDictKeys {
-        PyDictKeys::new(zelf)
-    }
-
-    #[pymethod]
-    fn values(zelf: PyRef<Self>) -> PyDictValues {
-        PyDictValues::new(zelf)
-    }
-
-    #[pymethod]
-    fn items(zelf: PyRef<Self>) -> PyDictItems {
-        PyDictItems::new(zelf)
-    }
-
     #[pymethod(magic)]
     fn setitem(&self, key: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
         self.inner_setitem(&*key, value, vm)
     }
 
-    #[pymethod(magic)]
-    #[cfg_attr(feature = "flame-it", flame("PyDictRef"))]
-    fn getitem(zelf: PyRef<Self>, key: PyObjectRef, vm: &VirtualMachine) -> PyResult {
-        zelf.inner_getitem(&*key, vm)
-    }
-
     #[pymethod]
     fn get(
         &self,
@@ -396,17 +377,44 @@ impl PyDict {
         Ok((key, value))
     }
 
-    #[pymethod(magic)]
-    fn reversed(zelf: PyRef<Self>) -> PyDictReverseKeyIterator {
-        PyDictReverseKeyIterator::new(zelf)
-    }
-
     #[pyclassmethod(magic)]
     fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
         PyGenericAlias::new(cls, args, vm)
     }
 }
 
+#[pyclass]
+impl Py<PyDict> {
+    #[pymethod(magic)]
+    #[cfg_attr(feature = "flame-it", flame("PyDictRef"))]
+    fn getitem(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult {
+        self.inner_getitem(&*key, vm)
+    }
+}
+
+#[pyclass]
+impl PyRef<PyDict> {
+    #[pymethod]
+    fn keys(self) -> PyDictKeys {
+        PyDictKeys::new(self)
+    }
+
+    #[pymethod]
+    fn values(self) -> PyDictValues {
+        PyDictValues::new(self)
+    }
+
+    #[pymethod]
+    fn items(self) -> PyDictItems {
+        PyDictItems::new(self)
+    }
+
+    #[pymethod(magic)]
+    fn reversed(self) -> PyDictReverseKeyIterator {
+        PyDictReverseKeyIterator::new(self)
+    }
+}
+
 impl Constructor for PyDict {
     type Args = FuncArgs;
 
@@ -847,9 +855,9 @@ macro_rules! dict_view {
 
             #[allow(clippy::redundant_closure_call)]
             #[pymethod(magic)]
-            fn reduce(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyTupleRef {
+            fn reduce(&self, vm: &VirtualMachine) -> PyTupleRef {
                 let iter = builtins_iter(vm).to_owned();
-                let internal = zelf.internal.lock();
+                let internal = self.internal.lock();
                 let entries = match &internal.status {
                     IterStatus::Active(dict) => dict
                         .into_iter()
@@ -917,9 +925,9 @@ macro_rules! dict_view {
 
             #[allow(clippy::redundant_closure_call)]
             #[pymethod(magic)]
-            fn reduce(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyTupleRef {
+            fn reduce(&self, vm: &VirtualMachine) -> PyTupleRef {
                 let iter = builtins_reversed(vm).to_owned();
-                let internal = zelf.internal.lock();
+                let internal = self.internal.lock();
                 // TODO: entries must be reversed too
                 let entries = match &internal.status {
                     IterStatus::Active(dict) => dict
@@ -1240,7 +1248,7 @@ impl PyDictItems {
             return Ok(false);
         }
         let value = needle.fast_getitem(1);
-        let found = PyDict::getitem(zelf.dict().clone(), key, vm)?;
+        let found = zelf.dict().getitem(key, vm)?;
         vm.identical_or_equal(&found, &value)
     }
     #[pygetset]
diff --git a/vm/src/builtins/enumerate.rs b/vm/src/builtins/enumerate.rs
index f07f7d6125..2905d4353e 100644
--- a/vm/src/builtins/enumerate.rs
+++ b/vm/src/builtins/enumerate.rs
@@ -8,7 +8,7 @@ use crate::{
     function::OptionalArg,
     protocol::{PyIter, PyIterReturn},
     types::{Constructor, IterNext, IterNextIterable},
-    AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
+    AsObject, Context, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
 };
 use num_bigint::BigInt;
 use num_traits::Zero;
@@ -51,17 +51,21 @@ impl Constructor for PyEnumerate {
     }
 }
 
-#[pyclass(with(IterNext, Constructor), flags(BASETYPE))]
+#[pyclass(with(Py, IterNext, Constructor), flags(BASETYPE))]
 impl PyEnumerate {
     #[pyclassmethod(magic)]
     fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
         PyGenericAlias::new(cls, args, vm)
     }
+}
+
+#[pyclass]
+impl Py<PyEnumerate> {
     #[pymethod(magic)]
-    fn reduce(zelf: PyRef<Self>) -> (PyTypeRef, (PyIter, BigInt)) {
+    fn reduce(&self) -> (PyTypeRef, (PyIter, BigInt)) {
         (
-            zelf.class().to_owned(),
-            (zelf.iterator.clone(), zelf.counter.read().clone()),
+            self.class().to_owned(),
+            (self.iterator.clone(), self.counter.read().clone()),
         )
     }
 }
diff --git a/vm/src/builtins/frame.rs b/vm/src/builtins/frame.rs
index e1f6f51058..f08767af9b 100644
--- a/vm/src/builtins/frame.rs
+++ b/vm/src/builtins/frame.rs
@@ -13,12 +13,9 @@ use crate::{
 use num_traits::Zero;
 
 pub fn init(context: &Context) {
-    FrameRef::extend_class(context, context.types.frame_type);
+    Frame::extend_class(context, context.types.frame_type);
 }
 
-#[pyclass(with(Constructor, PyRef, Representable))]
-impl Frame {}
-
 impl Unconstructible for Frame {}
 
 impl Representable for Frame {
@@ -34,61 +31,46 @@ impl Representable for Frame {
     }
 }
 
-#[pyclass]
-impl FrameRef {
+#[pyclass(with(Constructor, Py))]
+impl Frame {
     #[pymethod]
-    fn clear(self) {
+    fn clear(&self) {
         // TODO
     }
 
     #[pygetset]
-    fn f_globals(self) -> PyDictRef {
+    fn f_globals(&self) -> PyDictRef {
         self.globals.clone()
     }
 
     #[pygetset]
-    fn f_locals(self, vm: &VirtualMachine) -> PyResult {
+    fn f_locals(&self, vm: &VirtualMachine) -> PyResult {
         self.locals(vm).map(Into::into)
     }
 
     #[pygetset]
-    pub fn f_code(self) -> PyRef<PyCode> {
+    pub fn f_code(&self) -> PyRef<PyCode> {
         self.code.clone()
     }
 
     #[pygetset]
-    pub fn f_back(self, vm: &VirtualMachine) -> Option<Self> {
-        // TODO: actually store f_back inside Frame struct
-
-        // get the frame in the frame stack that appears before this one.
-        // won't work if  this frame isn't in the frame stack, hence the todo above
-        vm.frames
-            .borrow()
-            .iter()
-            .rev()
-            .skip_while(|p| !p.is(&self))
-            .nth(1)
-            .cloned()
-    }
-
-    #[pygetset]
-    fn f_lasti(self) -> u32 {
+    fn f_lasti(&self) -> u32 {
         self.lasti()
     }
 
     #[pygetset]
-    pub fn f_lineno(self) -> usize {
+    pub fn f_lineno(&self) -> usize {
         self.current_location().row()
     }
 
     #[pygetset]
-    fn f_trace(self) -> PyObjectRef {
+    fn f_trace(&self) -> PyObjectRef {
         let boxed = self.trace.lock();
         boxed.clone()
     }
 
     #[pygetset(setter)]
-    fn set_f_trace(self, value: PySetterValue, vm: &VirtualMachine) {
+    fn set_f_trace(&self, value: PySetterValue, vm: &VirtualMachine) {
         let mut storage = self.trace.lock();
         *storage = value.unwrap_or_none(vm);
     }
@@ -126,3 +108,21 @@ impl FrameRef {
         }
     }
 }
+
+#[pyclass]
+impl Py<Frame> {
+    #[pygetset]
+    pub fn f_back(&self, vm: &VirtualMachine) -> Option<PyRef<Frame>> {
+        // TODO: actually store f_back inside Frame struct
+
+        // get the frame in the frame stack that appears before this one.
+        // won't work if  this frame isn't in the frame stack, hence the todo above
+        vm.frames
+            .borrow()
+            .iter()
+            .rev()
+            .skip_while(|p| !p.is(self.as_object()))
+            .nth(1)
+            .cloned()
+    }
+}
diff --git a/vm/src/builtins/generator.rs b/vm/src/builtins/generator.rs
index 24ae1994a0..36fd488af5 100644
--- a/vm/src/builtins/generator.rs
+++ b/vm/src/builtins/generator.rs
@@ -25,7 +25,7 @@ impl PyPayload for PyGenerator {
     }
 }
 
-#[pyclass(with(Constructor, IterNext))]
+#[pyclass(with(Py, Constructor, IterNext))]
 impl PyGenerator {
     pub fn as_coro(&self) -> &Coro {
         &self.inner
@@ -47,21 +47,41 @@ impl PyGenerator {
         self.inner.set_name(name)
     }
 
+    #[pygetset]
+    fn gi_frame(&self, _vm: &VirtualMachine) -> FrameRef {
+        self.inner.frame()
+    }
+    #[pygetset]
+    fn gi_running(&self, _vm: &VirtualMachine) -> bool {
+        self.inner.running()
+    }
+    #[pygetset]
+    fn gi_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
+        self.inner.frame().code.clone()
+    }
+    #[pygetset]
+    fn gi_yieldfrom(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
+        self.inner.frame().yield_from_target()
+    }
+}
+
+#[pyclass]
+impl Py<PyGenerator> {
     #[pymethod]
-    fn send(zelf: PyRef<Self>, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
-        zelf.inner.send(zelf.as_object(), value, vm)
+    fn send(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
+        self.inner.send(self.as_object(), value, vm)
     }
 
     #[pymethod]
     fn throw(
-        zelf: PyRef<Self>,
+        &self,
         exc_type: PyObjectRef,
         exc_val: OptionalArg,
         exc_tb: OptionalArg,
         vm: &VirtualMachine,
     ) -> PyResult<PyIterReturn> {
-        zelf.inner.throw(
-            zelf.as_object(),
+        self.inner.throw(
+            self.as_object(),
             exc_type,
             exc_val.unwrap_or_none(vm),
             exc_tb.unwrap_or_none(vm),
@@ -70,25 +90,8 @@ impl PyGenerator {
     }
 
     #[pymethod]
-    fn close(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<()> {
-        zelf.inner.close(zelf.as_object(), vm)
-    }
-
-    #[pygetset]
-    fn gi_frame(&self, _vm: &VirtualMachine) -> FrameRef {
-        self.inner.frame()
-    }
-    #[pygetset]
-    fn gi_running(&self, _vm: &VirtualMachine) -> bool {
-        self.inner.running()
-    }
-    #[pygetset]
-    fn gi_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
-        self.inner.frame().code.clone()
-    }
-    #[pygetset]
-    fn gi_yieldfrom(&self, _vm: &VirtualMachine) -> Option<PyObjectRef> {
-        self.inner.frame().yield_from_target()
+    fn close(&self, vm: &VirtualMachine) -> PyResult<()> {
+        self.inner.close(self.as_object(), vm)
     }
 }
 
@@ -104,7 +107,7 @@ impl Representable for PyGenerator {
 impl IterNextIterable for PyGenerator {}
 impl IterNext for PyGenerator {
     fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
-        Self::send(zelf.to_owned(), vm.ctx.none(), vm)
+        zelf.send(vm.ctx.none(), vm)
     }
 }
 
diff --git a/vm/src/builtins/genericalias.rs b/vm/src/builtins/genericalias.rs
index 8d8b4d92f3..fa745cdb1b 100644
--- a/vm/src/builtins/genericalias.rs
+++ b/vm/src/builtins/genericalias.rs
@@ -179,7 +179,7 @@ impl PyGenericAlias {
     }
 
     #[pymethod(magic)]
-    fn reduce(zelf: PyRef<Self>, vm: &VirtualMachine) -> (PyTypeRef, (PyTypeRef, PyTupleRef)) {
+    fn reduce(zelf: &Py<Self>, vm: &VirtualMachine) -> (PyTypeRef, (PyTypeRef, PyTupleRef)) {
         (
             vm.ctx.types.generic_alias_type.to_owned(),
             (zelf.origin.clone(), zelf.args.clone()),
diff --git a/vm/src/builtins/int.rs b/vm/src/builtins/int.rs
index 9193bb7936..46b81de56b 100644
--- a/vm/src/builtins/int.rs
+++ b/vm/src/builtins/int.rs
@@ -72,8 +72,8 @@ impl_into_pyobject_int!(isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128 BigI
 
 macro_rules! impl_try_from_object_int {
     ($(($t:ty, $to_prim:ident),)*) => {$(
-        impl TryFromBorrowedObject for $t {
-            fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+        impl<'a> TryFromBorrowedObject<'a> for $t {
+            fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
                 obj.try_value_with(|int: &PyInt| {
                     int.try_to_primitive(vm)
                 }, vm)
diff --git a/vm/src/builtins/memory.rs b/vm/src/builtins/memory.rs
index 81576b5985..0b944eb4ca 100644
--- a/vm/src/builtins/memory.rs
+++ b/vm/src/builtins/memory.rs
@@ -68,16 +68,6 @@ impl Constructor for PyMemoryView {
     }
 }
 
-#[pyclass(with(
-    Hashable,
-    Comparable,
-    AsBuffer,
-    AsMapping,
-    AsSequence,
-    Constructor,
-    Iterable,
-    Representable
-))]
 impl PyMemoryView {
     fn parse_format(format: &str, vm: &VirtualMachine) -> PyResult<FormatSpec> {
         FormatSpec::parse(format.as_bytes(), vm)
@@ -143,13 +133,6 @@ impl PyMemoryView {
         zelf
     }
 
-    #[pymethod]
-    pub fn release(&self) {
-        if self.released.compare_exchange(false, true).is_ok() {
-            self.buffer.release();
-        }
-    }
-
     fn try_not_released(&self, vm: &VirtualMachine) -> PyResult<()> {
         if self.released.load() {
             Err(vm.new_value_error("operation forbidden on released memoryview object".to_owned()))
@@ -158,100 +141,6 @@ impl PyMemoryView {
         }
     }
 
-    #[pygetset]
-    fn obj(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
-        self.try_not_released(vm).map(|_| self.buffer.obj.clone())
-    }
-
-    #[pygetset]
-    fn nbytes(&self, vm: &VirtualMachine) -> PyResult<usize> {
-        self.try_not_released(vm).map(|_| self.desc.len)
-    }
-
-    #[pygetset]
-    fn readonly(&self, vm: &VirtualMachine) -> PyResult<bool> {
-        self.try_not_released(vm).map(|_| self.desc.readonly)
-    }
-
-    #[pygetset]
-    fn itemsize(&self, vm: &VirtualMachine) -> PyResult<usize> {
-        self.try_not_released(vm).map(|_| self.desc.itemsize)
-    }
-
-    #[pygetset]
-    fn ndim(&self, vm: &VirtualMachine) -> PyResult<usize> {
-        self.try_not_released(vm).map(|_| self.desc.ndim())
-    }
-
-    #[pygetset]
-    fn shape(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
-        self.try_not_released(vm)?;
-        Ok(vm.ctx.new_tuple(
-            self.desc
-                .dim_desc
-                .iter()
-                .map(|(shape, _, _)| shape.to_pyobject(vm))
-                .collect(),
-        ))
-    }
-
-    #[pygetset]
-    fn strides(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
-        self.try_not_released(vm)?;
-        Ok(vm.ctx.new_tuple(
-            self.desc
-                .dim_desc
-                .iter()
-                .map(|(_, stride, _)| stride.to_pyobject(vm))
-                .collect(),
-        ))
-    }
-
-    #[pygetset]
-    fn suboffsets(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
-        self.try_not_released(vm)?;
-        Ok(vm.ctx.new_tuple(
-            self.desc
-                .dim_desc
-                .iter()
-                .map(|(_, _, suboffset)| suboffset.to_pyobject(vm))
-                .collect(),
-        ))
-    }
-
-    #[pygetset]
-    fn format(&self, vm: &VirtualMachine) -> PyResult<PyStr> {
-        self.try_not_released(vm)
-            .map(|_| PyStr::from(self.desc.format.clone()))
-    }
-
-    #[pygetset]
-    fn contiguous(&self, vm: &VirtualMachine) -> PyResult<bool> {
-        self.try_not_released(vm).map(|_| self.desc.is_contiguous())
-    }
-
-    #[pygetset]
-    fn c_contiguous(&self, vm: &VirtualMachine) -> PyResult<bool> {
-        self.try_not_released(vm).map(|_| self.desc.is_contiguous())
-    }
-
-    #[pygetset]
-    fn f_contiguous(&self, vm: &VirtualMachine) -> PyResult<bool> {
-        // TODO: fortain order
-        self.try_not_released(vm)
-            .map(|_| self.desc.ndim() <= 1 && self.desc.is_contiguous())
-    }
-
-    #[pymethod(magic)]
-    fn enter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
-        zelf.try_not_released(vm).map(|_| zelf)
-    }
-
-    #[pymethod(magic)]
-    fn exit(&self, _args: FuncArgs) {
-        self.release();
-    }
-
     fn getitem_by_idx(&self, i: isize, vm: &VirtualMachine) -> PyResult {
         if self.desc.ndim() != 1 {
             return Err(vm.new_not_implemented_error(
@@ -281,29 +170,6 @@ impl PyMemoryView {
         format_unpack(&self.format_spec, &bytes[pos..pos + self.desc.itemsize], vm)
     }
 
-    #[pymethod(magic)]
-    fn getitem(zelf: PyRef<Self>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
-        zelf.try_not_released(vm)?;
-        if zelf.desc.ndim() == 0 {
-            // 0-d memoryview can be referenced using mv[...] or mv[()] only
-            if needle.is(&vm.ctx.ellipsis) {
-                return Ok(zelf.into());
-            }
-            if let Some(tuple) = needle.payload::<PyTuple>() {
-                if tuple.is_empty() {
-                    return zelf.unpack_single(0, vm);
-                }
-            }
-            return Err(vm.new_type_error("invalid indexing of 0-dim memory".to_owned()));
-        }
-
-        match SubscriptNeedle::try_from_object(vm, needle)? {
-            SubscriptNeedle::Index(i) => zelf.getitem_by_idx(i, vm),
-            SubscriptNeedle::Slice(slice) => zelf.getitem_by_slice(&slice, vm),
-            SubscriptNeedle::MultiIndex(indices) => zelf.getitem_by_multi_idx(&indices, vm),
-        }
-    }
-
     fn setitem_by_idx(&self, i: isize, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
         if self.desc.ndim() != 1 {
             return Err(vm.new_not_implemented_error("sub-views are not implemented".to_owned()));
@@ -317,60 +183,6 @@ impl PyMemoryView {
         self.pack_single(pos, value, vm)
     }
 
-    fn setitem_by_slice(
-        zelf: PyRef<Self>,
-        slice: &PySlice,
-        src: PyObjectRef,
-        vm: &VirtualMachine,
-    ) -> PyResult<()> {
-        if zelf.desc.ndim() != 1 {
-            return Err(vm.new_not_implemented_error("sub-view are not implemented".to_owned()));
-        }
-
-        let mut dest = zelf.new_view();
-        dest.init_slice(slice, 0, vm)?;
-        dest.init_len();
-
-        if zelf.is(&src) {
-            return if !is_equiv_structure(&zelf.desc, &dest.desc) {
-                Err(vm.new_value_error(
-                    "memoryview assignment: lvalue and rvalue have different structures".to_owned(),
-                ))
-            } else {
-                // assign self[:] to self
-                Ok(())
-            };
-        };
-
-        let src = if let Some(src) = src.downcast_ref::<PyMemoryView>() {
-            if zelf.buffer.obj.is(&src.buffer.obj) {
-                src.to_contiguous(vm)
-            } else {
-                AsBuffer::as_buffer(src, vm)?
-            }
-        } else {
-            PyBuffer::try_from_object(vm, src)?
-        };
-
-        if !is_equiv_structure(&src.desc, &dest.desc) {
-            return Err(vm.new_value_error(
-                "memoryview assignment: lvalue and rvalue have different structures".to_owned(),
-            ));
-        }
-
-        let mut bytes_mut = dest.buffer.obj_bytes_mut();
-        let src_bytes = src.obj_bytes();
-        dest.desc.zip_eq(&src.desc, true, |a_range, b_range| {
-            let a_range = (a_range.start + dest.start as isize) as usize
-                ..(a_range.end + dest.start as isize) as usize;
-            let b_range = b_range.start as usize..b_range.end as usize;
-            bytes_mut[a_range].copy_from_slice(&src_bytes[b_range]);
-            false
-        });
-
-        Ok(())
-    }
-
     fn setitem_by_multi_idx(
         &self,
         indexes: &[isize],
@@ -381,47 +193,6 @@ impl PyMemoryView {
         self.pack_single(pos, value, vm)
     }
 
-    #[pymethod(magic)]
-    fn delitem(&self, _needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
-        if self.desc.readonly {
-            return Err(vm.new_type_error("cannot modify read-only memory".to_owned()));
-        }
-        Err(vm.new_type_error("cannot delete memory".to_owned()))
-    }
-
-    #[pymethod(magic)]
-    fn setitem(
-        zelf: PyRef<Self>,
-        needle: PyObjectRef,
-        value: PyObjectRef,
-        vm: &VirtualMachine,
-    ) -> PyResult<()> {
-        zelf.try_not_released(vm)?;
-        if zelf.desc.readonly {
-            return Err(vm.new_type_error("cannot modify read-only memory".to_owned()));
-        }
-        if value.is(&vm.ctx.none) {
-            return Err(vm.new_type_error("cannot delete memory".to_owned()));
-        }
-
-        if zelf.desc.ndim() == 0 {
-            // TODO: merge branches when we got conditional if let
-            if needle.is(&vm.ctx.ellipsis) {
-                return zelf.pack_single(0, value, vm);
-            } else if let Some(tuple) = needle.payload::<PyTuple>() {
-                if tuple.is_empty() {
-                    return zelf.pack_single(0, value, vm);
-                }
-            }
-            return Err(vm.new_type_error("invalid indexing of 0-dim memory".to_owned()));
-        }
-        match SubscriptNeedle::try_from_object(vm, needle)? {
-            SubscriptNeedle::Index(i) => zelf.setitem_by_idx(i, value, vm),
-            SubscriptNeedle::Slice(slice) => Self::setitem_by_slice(zelf, &slice, value, vm),
-            SubscriptNeedle::MultiIndex(indices) => zelf.setitem_by_multi_idx(&indices, value, vm),
-        }
-    }
-
     fn pack_single(&self, pos: usize, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
         let mut bytes = self.buffer.obj_bytes_mut();
         // TODO: Optimize
@@ -522,26 +293,6 @@ impl PyMemoryView {
         Ok(())
     }
 
-    /// return the length of the first dimension
-    #[pymethod(magic)]
-    fn len(&self, vm: &VirtualMachine) -> PyResult<usize> {
-        self.try_not_released(vm)?;
-        Ok(if self.desc.ndim() == 0 {
-            1
-        } else {
-            // shape for dim[0]
-            self.desc.dim_desc[0].0
-        })
-    }
-
-    #[pymethod]
-    fn tobytes(&self, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
-        self.try_not_released(vm)?;
-        let mut v = vec![];
-        self.append_to(&mut v);
-        Ok(PyBytes::from(v).into_ref(vm))
-    }
-
     fn _to_list(
         &self,
         bytes: &[u8],
@@ -572,155 +323,12 @@ impl PyMemoryView {
         Ok(vm.ctx.new_list(v))
     }
 
-    #[pymethod]
-    fn tolist(&self, vm: &VirtualMachine) -> PyResult<PyListRef> {
-        self.try_not_released(vm)?;
-        let bytes = self.buffer.obj_bytes();
-        if self.desc.ndim() == 0 {
-            return Ok(vm.ctx.new_list(vec![format_unpack(
-                &self.format_spec,
-                &bytes[..self.desc.itemsize],
-                vm,
-            )?]));
-        }
-        self._to_list(&bytes, 0, 0, vm)
-    }
-
-    #[pymethod]
-    fn toreadonly(&self, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
-        self.try_not_released(vm)?;
-        let mut other = self.new_view();
-        other.desc.readonly = true;
-        Ok(other.into_ref(vm))
-    }
-
-    #[pymethod]
-    fn hex(
-        &self,
-        sep: OptionalArg<Either<PyStrRef, PyBytesRef>>,
-        bytes_per_sep: OptionalArg<isize>,
-        vm: &VirtualMachine,
-    ) -> PyResult<String> {
-        self.try_not_released(vm)?;
-        self.contiguous_or_collect(|x| bytes_to_hex(x, sep, bytes_per_sep, vm))
-    }
-
-    fn cast_to_1d(&self, format: PyStrRef, vm: &VirtualMachine) -> PyResult<Self> {
-        let format_spec = Self::parse_format(format.as_str(), vm)?;
-        let itemsize = format_spec.size();
-        if self.desc.len % itemsize != 0 {
-            return Err(
-                vm.new_type_error("memoryview: length is not a multiple of itemsize".to_owned())
-            );
-        }
-
-        Ok(Self {
-            buffer: self.buffer.clone(),
-            released: AtomicCell::new(false),
-            start: self.start,
-            format_spec,
-            desc: BufferDescriptor {
-                len: self.desc.len,
-                readonly: self.desc.readonly,
-                itemsize,
-                format: format.to_string().into(),
-                dim_desc: vec![(self.desc.len / itemsize, itemsize as isize, 0)],
-            },
-            hash: OnceCell::new(),
-        })
-    }
-
-    #[pymethod]
-    fn cast(&self, args: CastArgs, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
-        self.try_not_released(vm)?;
-        if !self.desc.is_contiguous() {
-            return Err(vm.new_type_error(
-                "memoryview: casts are restricted to C-contiguous views".to_owned(),
-            ));
-        }
-
-        let CastArgs { format, shape } = args;
-
-        if let OptionalArg::Present(shape) = shape {
-            if self.desc.is_zero_in_shape() {
-                return Err(vm.new_type_error(
-                    "memoryview: cannot cast view with zeros in shape or strides".to_owned(),
-                ));
-            }
-
-            let tup;
-            let list;
-            let list_borrow;
-            let shape = match shape {
-                Either::A(shape) => {
-                    tup = shape;
-                    tup.as_slice()
-                }
-                Either::B(shape) => {
-                    list = shape;
-                    list_borrow = list.borrow_vec();
-                    &list_borrow
-                }
-            };
-
-            let shape_ndim = shape.len();
-            // TODO: MAX_NDIM
-            if self.desc.ndim() != 1 && shape_ndim != 1 {
-                return Err(
-                    vm.new_type_error("memoryview: cast must be 1D -> ND or ND -> 1D".to_owned())
-                );
-            }
-
-            let mut other = self.cast_to_1d(format, vm)?;
-            let itemsize = other.desc.itemsize;
-
-            // 0 ndim is single item
-            if shape_ndim == 0 {
-                other.desc.dim_desc = vec![];
-                other.desc.len = itemsize;
-                return Ok(other.into_ref(vm));
-            }
-
-            let mut product_shape = itemsize;
-            let mut dim_descriptor = Vec::with_capacity(shape_ndim);
-
-            for x in shape {
-                let x = usize::try_from_borrowed_object(vm, x)?;
-
-                if x > isize::MAX as usize / product_shape {
-                    return Err(vm.new_value_error(
-                        "memoryview.cast(): product(shape) > SSIZE_MAX".to_owned(),
-                    ));
-                }
-                product_shape *= x;
-                dim_descriptor.push((x, 0, 0));
-            }
-
-            dim_descriptor.last_mut().unwrap().1 = itemsize as isize;
-            for i in (0..dim_descriptor.len() - 1).rev() {
-                dim_descriptor[i].1 = dim_descriptor[i + 1].1 * dim_descriptor[i + 1].0 as isize;
-            }
-
-            if product_shape != other.desc.len {
-                return Err(vm.new_type_error(
-                    "memoryview: product(shape) * itemsize != buffer size".to_owned(),
-                ));
-            }
-
-            other.desc.dim_desc = dim_descriptor;
-
-            Ok(other.into_ref(vm))
-        } else {
-            Ok(self.cast_to_1d(format, vm)?.into_ref(vm))
-        }
-    }
-
-    fn eq(zelf: &Py<Self>, other: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
-        if zelf.is(other) {
-            return Ok(true);
-        }
-        if zelf.released.load() {
-            return Ok(false);
+    fn eq(zelf: &Py<Self>, other: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
+        if zelf.is(other) {
+            return Ok(true);
+        }
+        if zelf.released.load() {
+            return Ok(false);
         }
 
         if let Some(other) = other.payload::<Self>() {
@@ -781,16 +389,6 @@ impl PyMemoryView {
         ret
     }
 
-    #[pymethod(magic)]
-    fn reduce_ex(zelf: PyRef<Self>, _proto: usize, vm: &VirtualMachine) -> PyResult {
-        Self::reduce(zelf, vm)
-    }
-
-    #[pymethod(magic)]
-    fn reduce(_zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
-        Err(vm.new_type_error("cannot pickle 'memoryview' object".to_owned()))
-    }
-
     fn obj_bytes(&self) -> BorrowedValue<[u8]> {
         if self.desc.is_contiguous() {
             BorrowedValue::map(self.buffer.obj_bytes(), |x| {
@@ -889,6 +487,415 @@ impl PyMemoryView {
     }
 }
 
+impl Py<PyMemoryView> {
+    fn setitem_by_slice(
+        &self,
+        slice: &PySlice,
+        src: PyObjectRef,
+        vm: &VirtualMachine,
+    ) -> PyResult<()> {
+        if self.desc.ndim() != 1 {
+            return Err(vm.new_not_implemented_error("sub-view are not implemented".to_owned()));
+        }
+
+        let mut dest = self.new_view();
+        dest.init_slice(slice, 0, vm)?;
+        dest.init_len();
+
+        if self.is(&src) {
+            return if !is_equiv_structure(&self.desc, &dest.desc) {
+                Err(vm.new_value_error(
+                    "memoryview assignment: lvalue and rvalue have different structures".to_owned(),
+                ))
+            } else {
+                // assign self[:] to self
+                Ok(())
+            };
+        };
+
+        let src = if let Some(src) = src.downcast_ref::<PyMemoryView>() {
+            if self.buffer.obj.is(&src.buffer.obj) {
+                src.to_contiguous(vm)
+            } else {
+                AsBuffer::as_buffer(src, vm)?
+            }
+        } else {
+            PyBuffer::try_from_object(vm, src)?
+        };
+
+        if !is_equiv_structure(&src.desc, &dest.desc) {
+            return Err(vm.new_value_error(
+                "memoryview assignment: lvalue and rvalue have different structures".to_owned(),
+            ));
+        }
+
+        let mut bytes_mut = dest.buffer.obj_bytes_mut();
+        let src_bytes = src.obj_bytes();
+        dest.desc.zip_eq(&src.desc, true, |a_range, b_range| {
+            let a_range = (a_range.start + dest.start as isize) as usize
+                ..(a_range.end + dest.start as isize) as usize;
+            let b_range = b_range.start as usize..b_range.end as usize;
+            bytes_mut[a_range].copy_from_slice(&src_bytes[b_range]);
+            false
+        });
+
+        Ok(())
+    }
+}
+
+#[pyclass(with(
+    Py,
+    Hashable,
+    Comparable,
+    AsBuffer,
+    AsMapping,
+    AsSequence,
+    Constructor,
+    Iterable,
+    Representable
+))]
+impl PyMemoryView {
+    #[pymethod]
+    pub fn release(&self) {
+        if self.released.compare_exchange(false, true).is_ok() {
+            self.buffer.release();
+        }
+    }
+
+    #[pygetset]
+    fn obj(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
+        self.try_not_released(vm).map(|_| self.buffer.obj.clone())
+    }
+
+    #[pygetset]
+    fn nbytes(&self, vm: &VirtualMachine) -> PyResult<usize> {
+        self.try_not_released(vm).map(|_| self.desc.len)
+    }
+
+    #[pygetset]
+    fn readonly(&self, vm: &VirtualMachine) -> PyResult<bool> {
+        self.try_not_released(vm).map(|_| self.desc.readonly)
+    }
+
+    #[pygetset]
+    fn itemsize(&self, vm: &VirtualMachine) -> PyResult<usize> {
+        self.try_not_released(vm).map(|_| self.desc.itemsize)
+    }
+
+    #[pygetset]
+    fn ndim(&self, vm: &VirtualMachine) -> PyResult<usize> {
+        self.try_not_released(vm).map(|_| self.desc.ndim())
+    }
+
+    #[pygetset]
+    fn shape(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
+        self.try_not_released(vm)?;
+        Ok(vm.ctx.new_tuple(
+            self.desc
+                .dim_desc
+                .iter()
+                .map(|(shape, _, _)| shape.to_pyobject(vm))
+                .collect(),
+        ))
+    }
+
+    #[pygetset]
+    fn strides(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
+        self.try_not_released(vm)?;
+        Ok(vm.ctx.new_tuple(
+            self.desc
+                .dim_desc
+                .iter()
+                .map(|(_, stride, _)| stride.to_pyobject(vm))
+                .collect(),
+        ))
+    }
+
+    #[pygetset]
+    fn suboffsets(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
+        self.try_not_released(vm)?;
+        Ok(vm.ctx.new_tuple(
+            self.desc
+                .dim_desc
+                .iter()
+                .map(|(_, _, suboffset)| suboffset.to_pyobject(vm))
+                .collect(),
+        ))
+    }
+
+    #[pygetset]
+    fn format(&self, vm: &VirtualMachine) -> PyResult<PyStr> {
+        self.try_not_released(vm)
+            .map(|_| PyStr::from(self.desc.format.clone()))
+    }
+
+    #[pygetset]
+    fn contiguous(&self, vm: &VirtualMachine) -> PyResult<bool> {
+        self.try_not_released(vm).map(|_| self.desc.is_contiguous())
+    }
+
+    #[pygetset]
+    fn c_contiguous(&self, vm: &VirtualMachine) -> PyResult<bool> {
+        self.try_not_released(vm).map(|_| self.desc.is_contiguous())
+    }
+
+    #[pygetset]
+    fn f_contiguous(&self, vm: &VirtualMachine) -> PyResult<bool> {
+        // TODO: fortain order
+        self.try_not_released(vm)
+            .map(|_| self.desc.ndim() <= 1 && self.desc.is_contiguous())
+    }
+
+    #[pymethod(magic)]
+    fn enter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
+        zelf.try_not_released(vm).map(|_| zelf)
+    }
+
+    #[pymethod(magic)]
+    fn exit(&self, _args: FuncArgs) {
+        self.release();
+    }
+
+    #[pymethod(magic)]
+    fn getitem(zelf: PyRef<Self>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
+        zelf.try_not_released(vm)?;
+        if zelf.desc.ndim() == 0 {
+            // 0-d memoryview can be referenced using mv[...] or mv[()] only
+            if needle.is(&vm.ctx.ellipsis) {
+                return Ok(zelf.into());
+            }
+            if let Some(tuple) = needle.payload::<PyTuple>() {
+                if tuple.is_empty() {
+                    return zelf.unpack_single(0, vm);
+                }
+            }
+            return Err(vm.new_type_error("invalid indexing of 0-dim memory".to_owned()));
+        }
+
+        match SubscriptNeedle::try_from_object(vm, needle)? {
+            SubscriptNeedle::Index(i) => zelf.getitem_by_idx(i, vm),
+            SubscriptNeedle::Slice(slice) => zelf.getitem_by_slice(&slice, vm),
+            SubscriptNeedle::MultiIndex(indices) => zelf.getitem_by_multi_idx(&indices, vm),
+        }
+    }
+
+    #[pymethod(magic)]
+    fn delitem(&self, _needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+        if self.desc.readonly {
+            return Err(vm.new_type_error("cannot modify read-only memory".to_owned()));
+        }
+        Err(vm.new_type_error("cannot delete memory".to_owned()))
+    }
+
+    #[pymethod(magic)]
+    fn len(&self, vm: &VirtualMachine) -> PyResult<usize> {
+        self.try_not_released(vm)?;
+        Ok(if self.desc.ndim() == 0 {
+            1
+        } else {
+            // shape for dim[0]
+            self.desc.dim_desc[0].0
+        })
+    }
+
+    #[pymethod]
+    fn tobytes(&self, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
+        self.try_not_released(vm)?;
+        let mut v = vec![];
+        self.append_to(&mut v);
+        Ok(PyBytes::from(v).into_ref(vm))
+    }
+
+    #[pymethod]
+    fn tolist(&self, vm: &VirtualMachine) -> PyResult<PyListRef> {
+        self.try_not_released(vm)?;
+        let bytes = self.buffer.obj_bytes();
+        if self.desc.ndim() == 0 {
+            return Ok(vm.ctx.new_list(vec![format_unpack(
+                &self.format_spec,
+                &bytes[..self.desc.itemsize],
+                vm,
+            )?]));
+        }
+        self._to_list(&bytes, 0, 0, vm)
+    }
+
+    #[pymethod]
+    fn toreadonly(&self, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
+        self.try_not_released(vm)?;
+        let mut other = self.new_view();
+        other.desc.readonly = true;
+        Ok(other.into_ref(vm))
+    }
+
+    #[pymethod]
+    fn hex(
+        &self,
+        sep: OptionalArg<Either<PyStrRef, PyBytesRef>>,
+        bytes_per_sep: OptionalArg<isize>,
+        vm: &VirtualMachine,
+    ) -> PyResult<String> {
+        self.try_not_released(vm)?;
+        self.contiguous_or_collect(|x| bytes_to_hex(x, sep, bytes_per_sep, vm))
+    }
+
+    fn cast_to_1d(&self, format: PyStrRef, vm: &VirtualMachine) -> PyResult<Self> {
+        let format_spec = Self::parse_format(format.as_str(), vm)?;
+        let itemsize = format_spec.size();
+        if self.desc.len % itemsize != 0 {
+            return Err(
+                vm.new_type_error("memoryview: length is not a multiple of itemsize".to_owned())
+            );
+        }
+
+        Ok(Self {
+            buffer: self.buffer.clone(),
+            released: AtomicCell::new(false),
+            start: self.start,
+            format_spec,
+            desc: BufferDescriptor {
+                len: self.desc.len,
+                readonly: self.desc.readonly,
+                itemsize,
+                format: format.to_string().into(),
+                dim_desc: vec![(self.desc.len / itemsize, itemsize as isize, 0)],
+            },
+            hash: OnceCell::new(),
+        })
+    }
+
+    #[pymethod]
+    fn cast(&self, args: CastArgs, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
+        self.try_not_released(vm)?;
+        if !self.desc.is_contiguous() {
+            return Err(vm.new_type_error(
+                "memoryview: casts are restricted to C-contiguous views".to_owned(),
+            ));
+        }
+
+        let CastArgs { format, shape } = args;
+
+        if let OptionalArg::Present(shape) = shape {
+            if self.desc.is_zero_in_shape() {
+                return Err(vm.new_type_error(
+                    "memoryview: cannot cast view with zeros in shape or strides".to_owned(),
+                ));
+            }
+
+            let tup;
+            let list;
+            let list_borrow;
+            let shape = match shape {
+                Either::A(shape) => {
+                    tup = shape;
+                    tup.as_slice()
+                }
+                Either::B(shape) => {
+                    list = shape;
+                    list_borrow = list.borrow_vec();
+                    &list_borrow
+                }
+            };
+
+            let shape_ndim = shape.len();
+            // TODO: MAX_NDIM
+            if self.desc.ndim() != 1 && shape_ndim != 1 {
+                return Err(
+                    vm.new_type_error("memoryview: cast must be 1D -> ND or ND -> 1D".to_owned())
+                );
+            }
+
+            let mut other = self.cast_to_1d(format, vm)?;
+            let itemsize = other.desc.itemsize;
+
+            // 0 ndim is single item
+            if shape_ndim == 0 {
+                other.desc.dim_desc = vec![];
+                other.desc.len = itemsize;
+                return Ok(other.into_ref(vm));
+            }
+
+            let mut product_shape = itemsize;
+            let mut dim_descriptor = Vec::with_capacity(shape_ndim);
+
+            for x in shape {
+                let x = usize::try_from_borrowed_object(vm, x)?;
+
+                if x > isize::MAX as usize / product_shape {
+                    return Err(vm.new_value_error(
+                        "memoryview.cast(): product(shape) > SSIZE_MAX".to_owned(),
+                    ));
+                }
+                product_shape *= x;
+                dim_descriptor.push((x, 0, 0));
+            }
+
+            dim_descriptor.last_mut().unwrap().1 = itemsize as isize;
+            for i in (0..dim_descriptor.len() - 1).rev() {
+                dim_descriptor[i].1 = dim_descriptor[i + 1].1 * dim_descriptor[i + 1].0 as isize;
+            }
+
+            if product_shape != other.desc.len {
+                return Err(vm.new_type_error(
+                    "memoryview: product(shape) * itemsize != buffer size".to_owned(),
+                ));
+            }
+
+            other.desc.dim_desc = dim_descriptor;
+
+            Ok(other.into_ref(vm))
+        } else {
+            Ok(self.cast_to_1d(format, vm)?.into_ref(vm))
+        }
+    }
+}
+
+#[pyclass]
+impl Py<PyMemoryView> {
+    #[pymethod(magic)]
+    fn setitem(
+        &self,
+        needle: PyObjectRef,
+        value: PyObjectRef,
+        vm: &VirtualMachine,
+    ) -> PyResult<()> {
+        self.try_not_released(vm)?;
+        if self.desc.readonly {
+            return Err(vm.new_type_error("cannot modify read-only memory".to_owned()));
+        }
+        if value.is(&vm.ctx.none) {
+            return Err(vm.new_type_error("cannot delete memory".to_owned()));
+        }
+
+        if self.desc.ndim() == 0 {
+            // TODO: merge branches when we got conditional if let
+            if needle.is(&vm.ctx.ellipsis) {
+                return self.pack_single(0, value, vm);
+            } else if let Some(tuple) = needle.payload::<PyTuple>() {
+                if tuple.is_empty() {
+                    return self.pack_single(0, value, vm);
+                }
+            }
+            return Err(vm.new_type_error("invalid indexing of 0-dim memory".to_owned()));
+        }
+        match SubscriptNeedle::try_from_object(vm, needle)? {
+            SubscriptNeedle::Index(i) => self.setitem_by_idx(i, value, vm),
+            SubscriptNeedle::Slice(slice) => self.setitem_by_slice(&slice, value, vm),
+            SubscriptNeedle::MultiIndex(indices) => self.setitem_by_multi_idx(&indices, value, vm),
+        }
+    }
+
+    #[pymethod(magic)]
+    fn reduce_ex(&self, _proto: usize, vm: &VirtualMachine) -> PyResult {
+        self.reduce(vm)
+    }
+
+    #[pymethod(magic)]
+    fn reduce(&self, vm: &VirtualMachine) -> PyResult {
+        Err(vm.new_type_error("cannot pickle 'memoryview' object".to_owned()))
+    }
+}
+
 #[derive(FromArgs)]
 struct CastArgs {
     #[pyarg(any)]
@@ -977,7 +984,7 @@ impl AsMapping for PyMemoryView {
             ass_subscript: atomic_func!(|mapping, needle, value, vm| {
                 let zelf = PyMemoryView::mapping_downcast(mapping);
                 if let Some(value) = value {
-                    PyMemoryView::setitem(zelf.to_owned(), needle.to_owned(), value, vm)
+                    zelf.setitem(needle.to_owned(), value, vm)
                 } else {
                     Err(vm.new_type_error("cannot delete memory".to_owned()))
                 }
diff --git a/vm/src/builtins/str.rs b/vm/src/builtins/str.rs
index 8b6d61c37b..32b86f0e65 100644
--- a/vm/src/builtins/str.rs
+++ b/vm/src/builtins/str.rs
@@ -42,8 +42,8 @@ use unic_ucd_category::GeneralCategory;
 use unic_ucd_ident::{is_xid_continue, is_xid_start};
 use unicode_casing::CharExt;
 
-impl TryFromBorrowedObject for String {
-    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+impl<'a> TryFromBorrowedObject<'a> for String {
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
         obj.try_value_with(|pystr: &PyStr| Ok(pystr.as_str().to_owned()), vm)
     }
 }
diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs
index f091262a6e..5b9c1a1176 100644
--- a/vm/src/builtins/type.rs
+++ b/vm/src/builtins/type.rs
@@ -355,6 +355,31 @@ impl PyType {
 
         attributes
     }
+
+    // bound method for every type
+    pub(crate) fn __new__(zelf: PyRef<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
+        let (subtype, args): (PyRef<Self>, FuncArgs) = args.bind(vm)?;
+        if !subtype.fast_issubclass(&zelf) {
+            return Err(vm.new_type_error(format!(
+                "{zelf}.__new__({subtype}): {subtype} is not a subtype of {zelf}",
+                zelf = zelf.name(),
+                subtype = subtype.name(),
+            )));
+        }
+        call_slot_new(zelf, subtype, args, vm)
+    }
+
+    pub fn name(&self) -> BorrowedValue<str> {
+        PyRwLockReadGuard::map(self.slots.name.read(), |slot_name| {
+            let name = slot_name.as_ref().unwrap();
+            if self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
+                name.as_str()
+            } else {
+                name.rsplit('.').next().unwrap()
+            }
+        })
+        .into()
+    }
 }
 
 impl Py<PyType> {
@@ -375,30 +400,10 @@ impl Py<PyType> {
 }
 
 #[pyclass(
-    with(GetAttr, SetAttr, Callable, AsNumber, Representable),
+    with(Py, GetAttr, SetAttr, Callable, AsNumber, Representable),
     flags(BASETYPE)
 )]
 impl PyType {
-    // bound method for every type
-    pub(crate) fn __new__(zelf: PyRef<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
-        let (subtype, args): (PyRef<Self>, FuncArgs) = args.bind(vm)?;
-        if !subtype.fast_issubclass(&zelf) {
-            return Err(vm.new_type_error(format!(
-                "{zelf}.__new__({subtype}): {subtype} is not a subtype of {zelf}",
-                zelf = zelf.name(),
-                subtype = subtype.name(),
-            )));
-        }
-        call_slot_new(zelf, subtype, args, vm)
-    }
-
-    #[pygetset(name = "__mro__")]
-    fn get_mro(zelf: PyRef<Self>) -> PyTuple {
-        let elements: Vec<PyObjectRef> =
-            zelf.iter_mro().map(|x| x.as_object().to_owned()).collect();
-        PyTuple::new_unchecked(elements.into_boxed_slice())
-    }
-
     #[pygetset(magic)]
     fn bases(&self, vm: &VirtualMachine) -> PyTupleRef {
         vm.ctx.new_tuple(
@@ -419,48 +424,11 @@ impl PyType {
         self.slots.flags.bits()
     }
 
-    #[pymethod(magic)]
-    fn dir(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyList {
-        let attributes: Vec<PyObjectRef> = zelf
-            .get_attributes()
-            .into_iter()
-            .map(|(k, _)| k.to_object())
-            .collect();
-        PyList::from(attributes)
-    }
-
-    #[pymethod(magic)]
-    fn instancecheck(zelf: PyRef<Self>, obj: PyObjectRef) -> bool {
-        obj.fast_isinstance(&zelf)
-    }
-
-    #[pymethod(magic)]
-    fn subclasscheck(zelf: PyRef<Self>, subclass: PyTypeRef) -> bool {
-        subclass.fast_issubclass(&zelf)
-    }
-
-    #[pyclassmethod(magic)]
-    fn subclasshook(_args: FuncArgs, vm: &VirtualMachine) -> PyObjectRef {
-        vm.ctx.not_implemented()
-    }
-
     #[pygetset]
     fn __name__(&self) -> String {
         self.name().to_string()
     }
 
-    pub fn name(&self) -> BorrowedValue<str> {
-        PyRwLockReadGuard::map(self.slots.name.read(), |slot_name| {
-            let name = slot_name.as_ref().unwrap();
-            if self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
-                name.as_str()
-            } else {
-                name.rsplit('.').next().unwrap()
-            }
-        })
-        .into()
-    }
-
     #[pygetset(magic)]
     pub fn qualname(&self, vm: &VirtualMachine) -> PyObjectRef {
         self.attributes
@@ -607,11 +575,6 @@ impl PyType {
         )
     }
 
-    #[pymethod]
-    fn mro(zelf: PyRef<Self>) -> Vec<PyObjectRef> {
-        zelf.iter_mro().map(|cls| cls.to_owned().into()).collect()
-    }
-
     #[pymethod(magic)]
     pub fn ror(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
         or_(other, zelf, vm)
@@ -906,6 +869,46 @@ impl PyType {
     }
 }
 
+#[pyclass]
+impl Py<PyType> {
+    #[pygetset(name = "__mro__")]
+    fn get_mro(&self) -> PyTuple {
+        let elements: Vec<PyObjectRef> =
+            self.iter_mro().map(|x| x.as_object().to_owned()).collect();
+        PyTuple::new_unchecked(elements.into_boxed_slice())
+    }
+
+    #[pymethod(magic)]
+    fn dir(&self) -> PyList {
+        let attributes: Vec<PyObjectRef> = self
+            .get_attributes()
+            .into_iter()
+            .map(|(k, _)| k.to_object())
+            .collect();
+        PyList::from(attributes)
+    }
+
+    #[pymethod(magic)]
+    fn instancecheck(&self, obj: PyObjectRef) -> bool {
+        obj.fast_isinstance(self)
+    }
+
+    #[pymethod(magic)]
+    fn subclasscheck(&self, subclass: PyTypeRef) -> bool {
+        subclass.fast_issubclass(self)
+    }
+
+    #[pyclassmethod(magic)]
+    fn subclasshook(_args: FuncArgs, vm: &VirtualMachine) -> PyObjectRef {
+        vm.ctx.not_implemented()
+    }
+
+    #[pymethod]
+    fn mro(&self) -> Vec<PyObjectRef> {
+        self.iter_mro().map(|cls| cls.to_owned().into()).collect()
+    }
+}
+
 const SIGNATURE_END_MARKER: &str = ")\n--\n\n";
 fn get_signature(doc: &str) -> Option<&str> {
     doc.find(SIGNATURE_END_MARKER).map(|index| &doc[..=index])
diff --git a/vm/src/bytesinner.rs b/vm/src/bytesinner.rs
index f2a0a7f359..4f07058a55 100644
--- a/vm/src/bytesinner.rs
+++ b/vm/src/bytesinner.rs
@@ -30,8 +30,8 @@ impl From<Vec<u8>> for PyBytesInner {
     }
 }
 
-impl TryFromBorrowedObject for PyBytesInner {
-    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+impl<'a> TryFromBorrowedObject<'a> for PyBytesInner {
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
         bytes_from_object(vm, obj).map(Self::from)
     }
 }
diff --git a/vm/src/class.rs b/vm/src/class.rs
index a99e6874c2..e07c5ae39a 100644
--- a/vm/src/class.rs
+++ b/vm/src/class.rs
@@ -3,7 +3,7 @@
 use crate::{
     builtins::{PyBaseObject, PyBoundMethod, PyType, PyTypeRef},
     identifier,
-    object::{Py, PyObjectPayload, PyObjectRef, PyRef},
+    object::{Py, PyObjectRef},
     types::{hash_not_implemented, PyTypeFlags, PyTypeSlots},
     vm::Context,
 };
@@ -63,18 +63,6 @@ pub trait PyClassDef {
     const UNHASHABLE: bool = false;
 }
 
-impl<T> PyClassDef for PyRef<T>
-where
-    T: PyObjectPayload + PyClassDef,
-{
-    const NAME: &'static str = T::NAME;
-    const MODULE_NAME: Option<&'static str> = T::MODULE_NAME;
-    const TP_NAME: &'static str = T::TP_NAME;
-    const DOC: Option<&'static str> = T::DOC;
-    const BASICSIZE: usize = T::BASICSIZE;
-    const UNHASHABLE: bool = T::UNHASHABLE;
-}
-
 pub trait PyClassImpl: PyClassDef {
     const TP_FLAGS: PyTypeFlags = PyTypeFlags::DEFAULT;
 
diff --git a/vm/src/convert/try_from.rs b/vm/src/convert/try_from.rs
index 42ab0d3200..c1f51ebb53 100644
--- a/vm/src/convert/try_from.rs
+++ b/vm/src/convert/try_from.rs
@@ -1,7 +1,7 @@
 use crate::{
     builtins::PyFloat,
     object::{AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult},
-    vm::VirtualMachine,
+    Py, VirtualMachine,
 };
 use num_traits::ToPrimitive;
 
@@ -15,7 +15,7 @@ pub trait TryFromObject: Sized {
 }
 
 /// Rust-side only version of TryFromObject to reduce unnecessary Rc::clone
-impl<T: TryFromBorrowedObject> TryFromObject for T {
+impl<T: for<'a> TryFromBorrowedObject<'a>> TryFromObject for T {
     fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
         TryFromBorrowedObject::try_from_borrowed_object(vm, &obj)
     }
@@ -31,9 +31,9 @@ impl PyObjectRef {
 }
 
 impl PyObject {
-    pub fn try_to_value<T>(&self, vm: &VirtualMachine) -> PyResult<T>
+    pub fn try_to_value<'a, T: 'a>(&'a self, vm: &VirtualMachine) -> PyResult<T>
     where
-        T: TryFromBorrowedObject,
+        T: TryFromBorrowedObject<'a>,
     {
         T::try_from_borrowed_object(vm, self)
     }
@@ -55,9 +55,12 @@ impl PyObject {
 }
 
 /// Lower-cost variation of `TryFromObject`
-pub trait TryFromBorrowedObject: Sized {
+pub trait TryFromBorrowedObject<'a>: Sized
+where
+    Self: 'a,
+{
     /// Attempt to convert a Python object to a value of this type.
-    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self>;
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self>;
 }
 
 impl<T> TryFromObject for PyRef<T>
@@ -93,12 +96,24 @@ impl<T: TryFromObject> TryFromObject for Option<T> {
     }
 }
 
-impl<T: TryFromObject> TryFromBorrowedObject for Vec<T> {
-    fn try_from_borrowed_object(vm: &VirtualMachine, value: &PyObject) -> PyResult<Self> {
+impl<'a, T: 'a + TryFromObject> TryFromBorrowedObject<'a> for Vec<T> {
+    fn try_from_borrowed_object(vm: &VirtualMachine, value: &'a PyObject) -> PyResult<Self> {
         vm.extract_elements_with(value, |obj| T::try_from_object(vm, obj))
     }
 }
 
+impl<'a, T: PyPayload> TryFromBorrowedObject<'a> for &'a Py<T> {
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
+        let class = T::class(vm);
+        if obj.fast_isinstance(class) {
+            obj.downcast_ref()
+                .ok_or_else(|| vm.new_downcast_runtime_error(class, &obj))
+        } else {
+            Err(vm.new_downcast_type_error(class, &obj))
+        }
+    }
+}
+
 impl TryFromObject for std::time::Duration {
     fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
         use std::time::Duration;
diff --git a/vm/src/frame.rs b/vm/src/frame.rs
index 821a02b3c9..d5c77c0e3c 100644
--- a/vm/src/frame.rs
+++ b/vm/src/frame.rs
@@ -164,24 +164,20 @@ impl Frame {
             temporary_refs: PyMutex::new(vec![]),
         }
     }
-}
 
-impl FrameRef {
-    #[inline(always)]
-    fn with_exec<R>(&self, f: impl FnOnce(ExecutingFrame) -> R) -> R {
-        let mut state = self.state.lock();
-        let exec = ExecutingFrame {
-            code: &self.code,
-            fastlocals: &self.fastlocals,
-            cells_frees: &self.cells_frees,
-            locals: &self.locals,
-            globals: &self.globals,
-            builtins: &self.builtins,
-            lasti: &self.lasti,
-            object: self,
-            state: &mut state,
-        };
-        f(exec)
+    pub fn current_location(&self) -> bytecode::Location {
+        self.code.locations[self.lasti() as usize - 1]
+    }
+
+    pub fn lasti(&self) -> u32 {
+        #[cfg(feature = "threading")]
+        {
+            self.lasti.load(atomic::Ordering::Relaxed)
+        }
+        #[cfg(not(feature = "threading"))]
+        {
+            self.lasti.get()
+        }
     }
 
     pub fn locals(&self, vm: &VirtualMachine) -> PyResult<ArgMapping> {
@@ -221,6 +217,25 @@ impl FrameRef {
         }
         Ok(locals.clone())
     }
+}
+
+impl Py<Frame> {
+    #[inline(always)]
+    fn with_exec<R>(&self, f: impl FnOnce(ExecutingFrame) -> R) -> R {
+        let mut state = self.state.lock();
+        let exec = ExecutingFrame {
+            code: &self.code,
+            fastlocals: &self.fastlocals,
+            cells_frees: &self.cells_frees,
+            locals: &self.locals,
+            globals: &self.globals,
+            builtins: &self.builtins,
+            lasti: &self.lasti,
+            object: self,
+            state: &mut state,
+        };
+        f(exec)
+    }
 
     // #[cfg_attr(feature = "flame-it", flame("Frame"))]
     pub fn run(&self, vm: &VirtualMachine) -> PyResult<ExecutionResult> {
@@ -250,34 +265,19 @@ impl FrameRef {
         self.with_exec(|mut exec| exec.gen_throw(vm, exc_type, exc_val, exc_tb))
     }
 
-    pub fn current_location(&self) -> bytecode::Location {
-        self.code.locations[self.lasti() as usize - 1]
-    }
-
     pub fn yield_from_target(&self) -> Option<PyObjectRef> {
         self.with_exec(|exec| exec.yield_from_target().map(PyObject::to_owned))
     }
 
-    pub fn lasti(&self) -> u32 {
-        #[cfg(feature = "threading")]
-        {
-            self.lasti.load(atomic::Ordering::Relaxed)
-        }
-        #[cfg(not(feature = "threading"))]
-        {
-            self.lasti.get()
-        }
-    }
-
     pub fn is_internal_frame(&self) -> bool {
-        let code = self.clone().f_code();
+        let code = self.f_code();
         let filename = code.co_filename();
 
         filename.as_str().contains("importlib") && filename.as_str().contains("_bootstrap")
     }
 
     pub fn next_external_frame(&self, vm: &VirtualMachine) -> Option<FrameRef> {
-        self.clone().f_back(vm).map(|mut back| loop {
+        self.f_back(vm).map(|mut back| loop {
             back = if let Some(back) = back.to_owned().f_back(vm) {
                 back
             } else {
@@ -300,7 +300,7 @@ struct ExecutingFrame<'a> {
     locals: &'a ArgMapping,
     globals: &'a PyDictRef,
     builtins: &'a PyDictRef,
-    object: &'a FrameRef,
+    object: &'a Py<Frame>,
     lasti: &'a Lasti,
     state: &'a mut FrameState,
 }
@@ -376,8 +376,12 @@ impl ExecutingFrame<'_> {
 
                         let loc = frame.code.locations[idx];
                         let next = exception.traceback();
-                        let new_traceback =
-                            PyTraceback::new(next, frame.object.clone(), frame.lasti(), loc.row());
+                        let new_traceback = PyTraceback::new(
+                            next,
+                            frame.object.to_owned(),
+                            frame.lasti(),
+                            loc.row(),
+                        );
                         vm_trace!("Adding to traceback: {:?} {:?}", new_traceback, loc.row());
                         exception.set_traceback(Some(new_traceback.into_ref(vm)));
 
diff --git a/vm/src/function/buffer.rs b/vm/src/function/buffer.rs
index 81e5e104cb..2938e915e3 100644
--- a/vm/src/function/buffer.rs
+++ b/vm/src/function/buffer.rs
@@ -65,8 +65,8 @@ impl From<ArgBytesLike> for PyBuffer {
     }
 }
 
-impl TryFromBorrowedObject for ArgBytesLike {
-    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+impl<'a> TryFromBorrowedObject<'a> for ArgBytesLike {
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
         let buffer = PyBuffer::try_from_borrowed_object(vm, obj)?;
         if buffer.desc.is_contiguous() {
             Ok(Self(buffer))
@@ -107,8 +107,8 @@ impl From<ArgMemoryBuffer> for PyBuffer {
     }
 }
 
-impl TryFromBorrowedObject for ArgMemoryBuffer {
-    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+impl<'a> TryFromBorrowedObject<'a> for ArgMemoryBuffer {
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
         let buffer = PyBuffer::try_from_borrowed_object(vm, obj)?;
         if !buffer.desc.is_contiguous() {
             Err(vm.new_type_error("non-contiguous buffer is not a bytes-like object".to_owned()))
diff --git a/vm/src/function/builtin.rs b/vm/src/function/builtin.rs
index 3c9a797a58..3a1591cb45 100644
--- a/vm/src/function/builtin.rs
+++ b/vm/src/function/builtin.rs
@@ -1,6 +1,7 @@
 use super::{FromArgs, FuncArgs};
 use crate::{
-    convert::ToPyResult, object::PyThreadingConstraint, PyPayload, PyRef, PyResult, VirtualMachine,
+    convert::ToPyResult, object::PyThreadingConstraint, Py, PyPayload, PyRef, PyResult,
+    VirtualMachine,
 };
 use std::marker::PhantomData;
 
@@ -59,6 +60,8 @@ use sealed::PyNativeFuncInternal;
 #[doc(hidden)]
 pub struct OwnedParam<T>(PhantomData<T>);
 #[doc(hidden)]
+pub struct BorrowedParam<T>(PhantomData<T>);
+#[doc(hidden)]
 pub struct RefParam<T>(PhantomData<T>);
 
 // This is the "magic" that allows rust functions of varying signatures to
@@ -80,6 +83,20 @@ macro_rules! into_py_native_func_tuple {
             }
         }
 
+        impl<F, S, $($T,)* R> PyNativeFuncInternal<(BorrowedParam<S>, $(OwnedParam<$T>,)*), R, VirtualMachine> for F
+        where
+            F: Fn(&Py<S>, $($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
+            S: PyPayload,
+            $($T: FromArgs,)*
+            R: ToPyResult,
+        {
+            fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
+                let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
+
+                (self)(&zelf, $($n,)* vm).to_pyresult(vm)
+            }
+        }
+
         impl<F, S, $($T,)* R> PyNativeFuncInternal<(RefParam<S>, $(OwnedParam<$T>,)*), R, VirtualMachine> for F
         where
             F: Fn(&S, $($T,)* &VirtualMachine) -> R + PyThreadingConstraint + 'static,
@@ -107,6 +124,20 @@ macro_rules! into_py_native_func_tuple {
             }
         }
 
+        impl<F, S, $($T,)* R> PyNativeFuncInternal<(BorrowedParam<S>, $(OwnedParam<$T>,)*), R, ()> for F
+        where
+            F: Fn(&Py<S>, $($T,)*) -> R + PyThreadingConstraint + 'static,
+            S: PyPayload,
+            $($T: FromArgs,)*
+            R: ToPyResult,
+        {
+            fn call_(&self, vm: &VirtualMachine, args: FuncArgs) -> PyResult {
+                let (zelf, $($n,)*) = args.bind::<(PyRef<S>, $($T,)*)>(vm)?;
+
+                (self)(&zelf, $($n,)*).to_pyresult(vm)
+            }
+        }
+
         impl<F, S, $($T,)* R> PyNativeFuncInternal<(RefParam<S>, $(OwnedParam<$T>,)*), R, ()> for F
         where
             F: Fn(&S, $($T,)*) -> R + PyThreadingConstraint + 'static,
diff --git a/vm/src/function/getset.rs b/vm/src/function/getset.rs
index a7731f73c8..827158e834 100644
--- a/vm/src/function/getset.rs
+++ b/vm/src/function/getset.rs
@@ -3,9 +3,9 @@
 */
 use crate::{
     convert::ToPyResult,
-    function::{OwnedParam, RefParam},
+    function::{BorrowedParam, OwnedParam, RefParam},
     object::PyThreadingConstraint,
-    PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
+    Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
 };
 
 #[derive(result_like::OptionLike, is_macro::Is, Debug)]
@@ -74,6 +74,18 @@ where
     }
 }
 
+impl<F, S, R> IntoPyGetterFunc<(BorrowedParam<S>, R, VirtualMachine)> for F
+where
+    F: Fn(&Py<S>, &VirtualMachine) -> R + 'static + Send + Sync,
+    S: PyPayload,
+    R: ToPyResult,
+{
+    fn get(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
+        let zelf = PyRef::<S>::try_from_object(vm, obj)?;
+        (self)(&zelf, vm).to_pyresult(vm)
+    }
+}
+
 impl<F, S, R> IntoPyGetterFunc<(RefParam<S>, R, VirtualMachine)> for F
 where
     F: Fn(&S, &VirtualMachine) -> R + 'static + Send + Sync,
@@ -98,6 +110,18 @@ where
     }
 }
 
+impl<F, S, R> IntoPyGetterFunc<(BorrowedParam<S>, R)> for F
+where
+    F: Fn(&Py<S>) -> R + 'static + Send + Sync,
+    S: PyPayload,
+    R: ToPyResult,
+{
+    fn get(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
+        let zelf = PyRef::<S>::try_from_object(vm, obj)?;
+        (self)(&zelf).to_pyresult(vm)
+    }
+}
+
 impl<F, S, R> IntoPyGetterFunc<(RefParam<S>, R)> for F
 where
     F: Fn(&S) -> R + 'static + Send + Sync,
@@ -149,6 +173,20 @@ where
     }
 }
 
+impl<F, S, V, R> IntoPySetterFunc<(BorrowedParam<S>, V, R, VirtualMachine)> for F
+where
+    F: Fn(&Py<S>, V, &VirtualMachine) -> R + 'static + Send + Sync,
+    S: PyPayload,
+    V: FromPySetterValue,
+    R: IntoPyNoResult,
+{
+    fn set(&self, obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
+        let zelf = PyRef::<S>::try_from_object(vm, obj)?;
+        let value = V::from_setter_value(vm, value)?;
+        (self)(&zelf, value, vm).into_noresult()
+    }
+}
+
 impl<F, S, V, R> IntoPySetterFunc<(RefParam<S>, V, R, VirtualMachine)> for F
 where
     F: Fn(&S, V, &VirtualMachine) -> R + 'static + Send + Sync,
@@ -177,6 +215,20 @@ where
     }
 }
 
+impl<F, S, V, R> IntoPySetterFunc<(BorrowedParam<S>, V, R)> for F
+where
+    F: Fn(&Py<S>, V) -> R + 'static + Send + Sync,
+    S: PyPayload,
+    V: FromPySetterValue,
+    R: IntoPyNoResult,
+{
+    fn set(&self, obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
+        let zelf = PyRef::<S>::try_from_object(vm, obj)?;
+        let value = V::from_setter_value(vm, value)?;
+        (self)(&zelf, value).into_noresult()
+    }
+}
+
 impl<F, S, V, R> IntoPySetterFunc<(RefParam<S>, V, R)> for F
 where
     F: Fn(&S, V) -> R + 'static + Send + Sync,
diff --git a/vm/src/function/mod.rs b/vm/src/function/mod.rs
index 5b4d2bdd78..3a097acf8c 100644
--- a/vm/src/function/mod.rs
+++ b/vm/src/function/mod.rs
@@ -13,7 +13,8 @@ pub use argument::{
 };
 pub use arithmetic::{PyArithmeticValue, PyComparisonValue};
 pub use buffer::{ArgAsciiBuffer, ArgBytesLike, ArgMemoryBuffer, ArgStrOrBytesLike};
-pub use builtin::{IntoPyNativeFunc, OwnedParam, PyNativeFunc, RefParam};
+pub(self) use builtin::{BorrowedParam, OwnedParam, RefParam};
+pub use builtin::{IntoPyNativeFunc, PyNativeFunc};
 pub use either::Either;
 pub use getset::PySetterValue;
 pub(super) use getset::{IntoPyGetterFunc, IntoPySetterFunc, PyGetterFunc, PySetterFunc};
@@ -28,8 +29,8 @@ pub enum ArgByteOrder {
     Little,
 }
 
-impl TryFromBorrowedObject for ArgByteOrder {
-    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+impl<'a> TryFromBorrowedObject<'a> for ArgByteOrder {
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
         obj.try_value_with(
             |s: &PyStr| match s.as_str() {
                 "big" => Ok(Self::Big),
diff --git a/vm/src/object/core.rs b/vm/src/object/core.rs
index 8790302080..1de24bdece 100644
--- a/vm/src/object/core.rs
+++ b/vm/src/object/core.rs
@@ -385,7 +385,7 @@ impl Drop for PyWeak {
     }
 }
 
-impl PyRef<PyWeak> {
+impl Py<PyWeak> {
     #[inline(always)]
     pub fn upgrade(&self) -> Option<PyObjectRef> {
         PyWeak::upgrade(self)
diff --git a/vm/src/protocol/buffer.rs b/vm/src/protocol/buffer.rs
index 9ba0c0160f..1450c837c6 100644
--- a/vm/src/protocol/buffer.rs
+++ b/vm/src/protocol/buffer.rs
@@ -136,8 +136,8 @@ impl PyBuffer {
     }
 }
 
-impl TryFromBorrowedObject for PyBuffer {
-    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+impl<'a> TryFromBorrowedObject<'a> for PyBuffer {
+    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
         let cls = obj.class();
         let as_buffer = cls.mro_find_map(|cls| cls.slots.as_buffer);
         if let Some(f) = as_buffer {
diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs
index 1b02f86eb9..630ad653a4 100644
--- a/vm/src/stdlib/io.rs
+++ b/vm/src/stdlib/io.rs
@@ -3065,8 +3065,6 @@ mod _io {
         closed: AtomicCell<bool>,
     }
 
-    type StringIORef = PyRef<StringIO>;
-
     #[derive(FromArgs)]
     struct StringIONewArgs {
         #[pyarg(positional, optional)]
@@ -3100,7 +3098,6 @@ mod _io {
         }
     }
 
-    #[pyclass(flags(BASETYPE, HAS_DICT), with(PyRef, Constructor))]
     impl StringIO {
         fn buffer(&self, vm: &VirtualMachine) -> PyResult<PyRwLockWriteGuard<'_, BufferedIO>> {
             if !self.closed.load() {
@@ -3109,7 +3106,10 @@ mod _io {
                 Err(io_closed_error(vm))
             }
         }
+    }
 
+    #[pyclass(flags(BASETYPE, HAS_DICT), with(Constructor))]
+    impl StringIO {
         #[pymethod]
         fn readable(&self) -> bool {
             true
@@ -3132,34 +3132,28 @@ mod _io {
         fn close(&self) {
             self.closed.store(true);
         }
-    }
 
-    #[pyclass]
-    impl StringIORef {
-        //write string to underlying vector
+        // write string to underlying vector
         #[pymethod]
-        fn write(self, data: PyStrRef, vm: &VirtualMachine) -> PyResult {
+        fn write(&self, data: PyStrRef, vm: &VirtualMachine) -> PyResult<u64> {
             let bytes = data.as_str().as_bytes();
-
-            match self.buffer(vm)?.write(bytes) {
-                Some(value) => Ok(vm.ctx.new_int(value).into()),
-                None => Err(vm.new_type_error("Error Writing String".to_owned())),
-            }
+            self.buffer(vm)?
+                .write(bytes)
+                .ok_or_else(|| vm.new_type_error("Error Writing String".to_owned()))
         }
 
-        //return the entire contents of the underlying
+        // return the entire contents of the underlying
         #[pymethod]
-        fn getvalue(self, vm: &VirtualMachine) -> PyResult {
-            match String::from_utf8(self.buffer(vm)?.getvalue()) {
-                Ok(result) => Ok(vm.ctx.new_str(result).into()),
-                Err(_) => Err(vm.new_value_error("Error Retrieving Value".to_owned())),
-            }
+        fn getvalue(&self, vm: &VirtualMachine) -> PyResult<String> {
+            let bytes = self.buffer(vm)?.getvalue();
+            String::from_utf8(bytes)
+                .map_err(|_| vm.new_value_error("Error Retrieving Value".to_owned()))
         }
 
-        //skip to the jth position
+        // skip to the jth position
         #[pymethod]
         fn seek(
-            self,
+            &self,
             offset: PyObjectRef,
             how: OptionalArg<i32>,
             vm: &VirtualMachine,
@@ -3169,38 +3163,34 @@ mod _io {
                 .map_err(|err| os_err(vm, err))
         }
 
-        //Read k bytes from the object and return.
-        //If k is undefined || k == -1, then we read all bytes until the end of the file.
-        //This also increments the stream position by the value of k
+        // Read k bytes from the object and return.
+        // If k is undefined || k == -1, then we read all bytes until the end of the file.
+        // This also increments the stream position by the value of k
         #[pymethod]
-        fn read(self, size: OptionalSize, vm: &VirtualMachine) -> PyResult {
-            let data = match self.buffer(vm)?.read(size.to_usize()) {
-                Some(value) => value,
-                None => Vec::new(),
-            };
+        fn read(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<String> {
+            let data = self.buffer(vm)?.read(size.to_usize()).unwrap_or_default();
 
             let value = String::from_utf8(data)
                 .map_err(|_| vm.new_value_error("Error Retrieving Value".to_owned()))?;
-            Ok(vm.ctx.new_str(value).into())
+            Ok(value)
         }
 
         #[pymethod]
-        fn tell(self, vm: &VirtualMachine) -> PyResult<u64> {
+        fn tell(&self, vm: &VirtualMachine) -> PyResult<u64> {
             Ok(self.buffer(vm)?.tell())
         }
 
         #[pymethod]
-        fn readline(self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<String> {
+        fn readline(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<String> {
             // TODO size should correspond to the number of characters, at the moments its the number of
             // bytes.
-            match String::from_utf8(self.buffer(vm)?.readline(size.to_usize(), vm)?) {
-                Ok(value) => Ok(value),
-                Err(_) => Err(vm.new_value_error("Error Retrieving Value".to_owned())),
-            }
+            let input = self.buffer(vm)?.readline(size.to_usize(), vm)?;
+            String::from_utf8(input)
+                .map_err(|_| vm.new_value_error("Error Retrieving Value".to_owned()))
         }
 
         #[pymethod]
-        fn truncate(self, pos: OptionalSize, vm: &VirtualMachine) -> PyResult<usize> {
+        fn truncate(&self, pos: OptionalSize, vm: &VirtualMachine) -> PyResult<usize> {
             let mut buffer = self.buffer(vm)?;
             let pos = pos.try_usize(vm)?;
             Ok(buffer.truncate(pos))
@@ -3216,8 +3206,6 @@ mod _io {
         exports: AtomicCell<usize>,
     }
 
-    type BytesIORef = PyRef<BytesIO>;
-
     impl Constructor for BytesIO {
         type Args = OptionalArg<Option<PyBytesRef>>;
 
@@ -3236,7 +3224,6 @@ mod _io {
         }
     }
 
-    #[pyclass(flags(BASETYPE, HAS_DICT), with(PyRef, Constructor))]
     impl BytesIO {
         fn buffer(&self, vm: &VirtualMachine) -> PyResult<PyRwLockWriteGuard<'_, BufferedIO>> {
             if !self.closed.load() {
@@ -3245,7 +3232,10 @@ mod _io {
                 Err(io_closed_error(vm))
             }
         }
+    }
 
+    #[pyclass(flags(BASETYPE, HAS_DICT), with(PyRef, Constructor))]
+    impl BytesIO {
         #[pymethod]
         fn readable(&self) -> bool {
             true
@@ -3258,37 +3248,33 @@ mod _io {
         fn seekable(&self) -> bool {
             true
         }
-    }
 
-    #[pyclass]
-    impl BytesIORef {
         #[pymethod]
-        fn write(self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<u64> {
+        fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<u64> {
             let mut buffer = self.try_resizable(vm)?;
-            match data.with_ref(|b| buffer.write(b)) {
-                Some(value) => Ok(value),
-                None => Err(vm.new_type_error("Error Writing Bytes".to_owned())),
-            }
+            data.with_ref(|b| buffer.write(b))
+                .ok_or_else(|| vm.new_type_error("Error Writing Bytes".to_owned()))
         }
 
-        //Retrieves the entire bytes object value from the underlying buffer
+        // Retrieves the entire bytes object value from the underlying buffer
         #[pymethod]
-        fn getvalue(self, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
-            Ok(vm.ctx.new_bytes(self.buffer(vm)?.getvalue()))
+        fn getvalue(&self, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
+            let bytes = self.buffer(vm)?.getvalue();
+            Ok(vm.ctx.new_bytes(bytes))
         }
 
-        //Takes an integer k (bytes) and returns them from the underlying buffer
-        //If k is undefined || k == -1, then we read all bytes until the end of the file.
-        //This also increments the stream position by the value of k
+        // Takes an integer k (bytes) and returns them from the underlying buffer
+        // If k is undefined || k == -1, then we read all bytes until the end of the file.
+        // This also increments the stream position by the value of k
         #[pymethod]
         #[pymethod(name = "read1")]
-        fn read(self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
+        fn read(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
             let buf = self.buffer(vm)?.read(size.to_usize()).unwrap_or_default();
             Ok(buf)
         }
 
         #[pymethod]
-        fn readinto(self, obj: ArgMemoryBuffer, vm: &VirtualMachine) -> PyResult<usize> {
+        fn readinto(&self, obj: ArgMemoryBuffer, vm: &VirtualMachine) -> PyResult<usize> {
             let mut buf = self.buffer(vm)?;
             let ret = buf
                 .cursor
@@ -3301,7 +3287,7 @@ mod _io {
         //skip to the jth position
         #[pymethod]
         fn seek(
-            self,
+            &self,
             offset: PyObjectRef,
             how: OptionalArg<i32>,
             vm: &VirtualMachine,
@@ -3312,22 +3298,17 @@ mod _io {
         }
 
         #[pymethod]
-        fn seekable(self) -> bool {
-            true
-        }
-
-        #[pymethod]
-        fn tell(self, vm: &VirtualMachine) -> PyResult<u64> {
+        fn tell(&self, vm: &VirtualMachine) -> PyResult<u64> {
             Ok(self.buffer(vm)?.tell())
         }
 
         #[pymethod]
-        fn readline(self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
+        fn readline(&self, size: OptionalSize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
             self.buffer(vm)?.readline(size.to_usize(), vm)
         }
 
         #[pymethod]
-        fn truncate(self, pos: OptionalSize, vm: &VirtualMachine) -> PyResult<usize> {
+        fn truncate(&self, pos: OptionalSize, vm: &VirtualMachine) -> PyResult<usize> {
             if self.closed.load() {
                 return Err(io_closed_error(vm));
             }
@@ -3337,17 +3318,20 @@ mod _io {
         }
 
         #[pygetset]
-        fn closed(self) -> bool {
+        fn closed(&self) -> bool {
             self.closed.load()
         }
 
         #[pymethod]
-        fn close(self, vm: &VirtualMachine) -> PyResult<()> {
+        fn close(&self, vm: &VirtualMachine) -> PyResult<()> {
             drop(self.try_resizable(vm)?);
             self.closed.store(true);
             Ok(())
         }
+    }
 
+    #[pyclass]
+    impl PyRef<BytesIO> {
         #[pymethod]
         fn getbuffer(self, vm: &VirtualMachine) -> PyResult<PyMemoryView> {
             let len = self.buffer.read().cursor.get_ref().len();
diff --git a/vm/src/stdlib/time.rs b/vm/src/stdlib/time.rs
index 474d51d75d..793a03c6df 100644
--- a/vm/src/stdlib/time.rs
+++ b/vm/src/stdlib/time.rs
@@ -435,8 +435,8 @@ mod unix {
     #[pyattr]
     use libc::{CLOCK_PROF, CLOCK_UPTIME};
 
-    impl TryFromBorrowedObject for ClockId {
-        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
+    impl<'a> TryFromBorrowedObject<'a> for ClockId {
+        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
             obj.try_to_value(vm).map(ClockId::from_raw)
         }
     }


Test patch: