diff --git a/CHANGELOG.md b/CHANGELOG.md
index e90482636e9..4fe74cb09ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 - Change return type of `PyTuple::as_slice` to `&[&PyAny]`. [#971](https://github.com/PyO3/pyo3/pull/971)
 - Update `num-complex` optional dependendency from `0.2` to `0.3`. [#977](https://github.com/PyO3/pyo3/pull/977)
 - Update `num-bigint` optional dependendency from `0.2` to `0.3`. [#978](https://github.com/PyO3/pyo3/pull/978)
+- `#[pyproto]` is re-implemented without specialization. [#961](https://github.com/PyO3/pyo3/pull/961)
 
 ### Removed
 - Remove `ManagedPyRef` (unused, and needs specialization) [#930](https://github.com/PyO3/pyo3/pull/930)
diff --git a/Cargo.toml b/Cargo.toml
index 3a72b361438..d18cafd6ee4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@ travis-ci = { repository = "PyO3/pyo3", branch = "master" }
 appveyor = { repository = "fafhrd91/pyo3" }
 
 [dependencies]
+ctor = { version = "0.1", optional = true }
 indoc = { version = "0.3.4", optional = true }
 inventory = { version = "0.1.4", optional = true }
 libc = "0.2.62"
@@ -38,7 +39,7 @@ version_check = "0.9.1"
 
 [features]
 default = ["macros"]
-macros = ["indoc", "inventory", "paste", "pyo3cls", "unindent"]
+macros = ["ctor", "indoc", "inventory", "paste", "pyo3cls", "unindent"]
 
 # this is no longer needed internally, but setuptools-rust assumes this feature
 python3 = []
diff --git a/guide/src/class.md b/guide/src/class.md
index 2a05ded9076..391e6e40e4d 100644
--- a/guide/src/class.md
+++ b/guide/src/class.md
@@ -956,6 +956,14 @@ impl pyo3::class::methods::HasMethodsInventory for MyClass {
     type Methods = Pyo3MethodsInventoryForMyClass;
 }
 pyo3::inventory::collect!(Pyo3MethodsInventoryForMyClass);
+
+impl pyo3::class::proto_methods::HasProtoRegistry for MyClass {
+    fn registry() -> &'static pyo3::class::proto_methods::PyProtoRegistry {
+        static REGISTRY: pyo3::class::proto_methods::PyProtoRegistry
+            = pyo3::class::proto_methods::PyProtoRegistry::new();
+        &REGISTRY
+    }
+}
 # let gil = Python::acquire_gil();
 # let py = gil.python();
 # let cls = py.get_type::<MyClass>();
diff --git a/pyo3-derive-backend/src/defs.rs b/pyo3-derive-backend/src/defs.rs
index 01240d44546..d7d73cfc996 100644
--- a/pyo3-derive-backend/src/defs.rs
+++ b/pyo3-derive-backend/src/defs.rs
@@ -1,10 +1,20 @@
 // Copyright (c) 2017-present PyO3 Project and Contributors
 use crate::func::MethodProto;
 
+/// Predicates for `#[pyproto]`.
 pub struct Proto {
+    /// The name of this protocol. E.g., Iter.
     pub name: &'static str,
+    /// The name of slot table. E.g., PyIterMethods.
+    pub slot_table: &'static str,
+    /// The name of the setter used to set the table to `PyProtoRegistry`.
+    pub set_slot_table: &'static str,
+    /// All methods.
     pub methods: &'static [MethodProto],
+    /// All methods registered as normal methods like `#[pymethods]`.
     pub py_methods: &'static [PyMethod],
+    /// All methods registered to the slot table.
+    pub slot_setters: &'static [SlotSetter],
 }
 
 impl Proto {
@@ -22,6 +32,7 @@ impl Proto {
     }
 }
 
+/// Represents a method registered as a normal method like `#[pymethods]`.
 // TODO(kngwyu): Currently only __radd__-like methods use METH_COEXIST to prevent
 // __add__-like methods from overriding them.
 pub struct PyMethod {
@@ -47,8 +58,33 @@ impl PyMethod {
     }
 }
 
+/// Represents a setter used to register a method to the method table.
+pub struct SlotSetter {
+    /// Protocols necessary for invoking this setter.
+    /// E.g., we need `__setattr__` and `__delattr__` for invoking `set_setdelitem`.
+    pub proto_names: &'static [&'static str],
+    /// The name of the setter called to the method table.
+    pub set_function: &'static str,
+    /// Represents a set of setters disabled by this setter.
+    /// E.g., `set_setdelitem` have to disable `set_setitem` and `set_delitem`.
+    pub skipped_setters: &'static [&'static str],
+}
+
+impl SlotSetter {
+    const EMPTY_SETTERS: &'static [&'static str] = &[];
+    const fn new(names: &'static [&'static str], set_function: &'static str) -> Self {
+        SlotSetter {
+            proto_names: names,
+            set_function,
+            skipped_setters: Self::EMPTY_SETTERS,
+        }
+    }
+}
+
 pub const OBJECT: Proto = Proto {
     name: "Object",
+    slot_table: "pyo3::class::basic::PyObjectMethods",
+    set_slot_table: "set_basic_methods",
     methods: &[
         MethodProto::Binary {
             name: "__getattr__",
@@ -95,40 +131,60 @@ pub const OBJECT: Proto = Proto {
             pyres: true,
             proto: "pyo3::class::basic::PyObjectBytesProtocol",
         },
-        MethodProto::Unary {
-            name: "__bool__",
-            pyres: false,
-            proto: "pyo3::class::basic::PyObjectBoolProtocol",
-        },
         MethodProto::Binary {
             name: "__richcmp__",
             arg: "Other",
             pyres: true,
             proto: "pyo3::class::basic::PyObjectRichcmpProtocol",
         },
+        MethodProto::Unary {
+            name: "__bool__",
+            pyres: false,
+            proto: "pyo3::class::basic::PyObjectBoolProtocol",
+        },
     ],
     py_methods: &[
         PyMethod::new("__format__", "pyo3::class::basic::FormatProtocolImpl"),
         PyMethod::new("__bytes__", "pyo3::class::basic::BytesProtocolImpl"),
         PyMethod::new("__unicode__", "pyo3::class::basic::UnicodeProtocolImpl"),
     ],
+    slot_setters: &[
+        SlotSetter::new(&["__str__"], "set_str"),
+        SlotSetter::new(&["__repr__"], "set_repr"),
+        SlotSetter::new(&["__hash__"], "set_hash"),
+        SlotSetter::new(&["__getattr__"], "set_getattr"),
+        SlotSetter::new(&["__richcmp__"], "set_richcompare"),
+        SlotSetter {
+            proto_names: &["__setattr__", "__delattr__"],
+            set_function: "set_setdelattr",
+            skipped_setters: &["set_setattr", "set_delattr"],
+        },
+        SlotSetter::new(&["__setattr__"], "set_setattr"),
+        SlotSetter::new(&["__delattr__"], "set_delattr"),
+        SlotSetter::new(&["__bool__"], "set_bool"),
+    ],
 };
 
 pub const ASYNC: Proto = Proto {
     name: "Async",
+    slot_table: "pyo3::ffi::PyAsyncMethods",
+    set_slot_table: "set_async_methods",
     methods: &[
-        MethodProto::Unary {
+        MethodProto::UnaryS {
             name: "__await__",
+            arg: "Receiver",
             pyres: true,
             proto: "pyo3::class::pyasync::PyAsyncAwaitProtocol",
         },
-        MethodProto::Unary {
+        MethodProto::UnaryS {
             name: "__aiter__",
+            arg: "Receiver",
             pyres: true,
             proto: "pyo3::class::pyasync::PyAsyncAiterProtocol",
         },
-        MethodProto::Unary {
+        MethodProto::UnaryS {
             name: "__anext__",
+            arg: "Receiver",
             pyres: true,
             proto: "pyo3::class::pyasync::PyAsyncAnextProtocol",
         },
@@ -155,10 +211,17 @@ pub const ASYNC: Proto = Proto {
             "pyo3::class::pyasync::PyAsyncAexitProtocolImpl",
         ),
     ],
+    slot_setters: &[
+        SlotSetter::new(&["__await__"], "set_await"),
+        SlotSetter::new(&["__aiter__"], "set_aiter"),
+        SlotSetter::new(&["__anext__"], "set_anext"),
+    ],
 };
 
 pub const BUFFER: Proto = Proto {
     name: "Buffer",
+    slot_table: "pyo3::ffi::PyBufferProcs",
+    set_slot_table: "set_buffer_methods",
     methods: &[
         MethodProto::Unary {
             name: "bf_getbuffer",
@@ -172,10 +235,16 @@ pub const BUFFER: Proto = Proto {
         },
     ],
     py_methods: &[],
+    slot_setters: &[
+        SlotSetter::new(&["bf_getbuffer"], "set_getbuffer"),
+        SlotSetter::new(&["bf_releasebuffer"], "set_releasebuffer"),
+    ],
 };
 
 pub const CONTEXT: Proto = Proto {
     name: "Context",
+    slot_table: "",
+    set_slot_table: "",
     methods: &[
         MethodProto::Unary {
             name: "__enter__",
@@ -200,10 +269,13 @@ pub const CONTEXT: Proto = Proto {
             "pyo3::class::context::PyContextExitProtocolImpl",
         ),
     ],
+    slot_setters: &[],
 };
 
 pub const GC: Proto = Proto {
     name: "GC",
+    slot_table: "pyo3::class::gc::PyGCMethods",
+    set_slot_table: "set_gc_methods",
     methods: &[
         MethodProto::Free {
             name: "__traverse__",
@@ -215,23 +287,31 @@ pub const GC: Proto = Proto {
         },
     ],
     py_methods: &[],
+    slot_setters: &[
+        SlotSetter::new(&["__traverse__"], "set_traverse"),
+        SlotSetter::new(&["__clear__"], "set_clear"),
+    ],
 };
 
 pub const DESCR: Proto = Proto {
     name: "Descriptor",
+    slot_table: "pyo3::class::descr::PyDescrMethods",
+    set_slot_table: "set_descr_methods",
     methods: &[
-        MethodProto::Ternary {
+        MethodProto::TernaryS {
             name: "__get__",
-            arg1: "Inst",
-            arg2: "Owner",
+            arg1: "Receiver",
+            arg2: "Inst",
+            arg3: "Owner",
             pyres: true,
             proto: "pyo3::class::descr::PyDescrGetProtocol",
         },
-        MethodProto::Ternary {
+        MethodProto::TernaryS {
             name: "__set__",
-            arg1: "Inst",
-            arg2: "Value",
-            pyres: true,
+            arg1: "Receiver",
+            arg2: "Inst",
+            arg3: "Value",
+            pyres: false,
             proto: "pyo3::class::descr::PyDescrSetProtocol",
         },
         MethodProto::Binary {
@@ -254,10 +334,16 @@ pub const DESCR: Proto = Proto {
             "pyo3::class::context::PyDescrNameProtocolImpl",
         ),
     ],
+    slot_setters: &[
+        SlotSetter::new(&["__get__"], "set_descr_get"),
+        SlotSetter::new(&["__set__"], "set_descr_set"),
+    ],
 };
 
 pub const ITER: Proto = Proto {
     name: "Iter",
+    slot_table: "pyo3::class::iter::PyIterMethods",
+    set_slot_table: "set_iter_methods",
     py_methods: &[],
     methods: &[
         MethodProto::UnaryS {
@@ -273,10 +359,16 @@ pub const ITER: Proto = Proto {
             proto: "pyo3::class::iter::PyIterNextProtocol",
         },
     ],
+    slot_setters: &[
+        SlotSetter::new(&["__iter__"], "set_iter"),
+        SlotSetter::new(&["__next__"], "set_iternext"),
+    ],
 };
 
 pub const MAPPING: Proto = Proto {
     name: "Mapping",
+    slot_table: "pyo3::ffi::PyMappingMethods",
+    set_slot_table: "set_mapping_methods",
     methods: &[
         MethodProto::Unary {
             name: "__len__",
@@ -312,10 +404,23 @@ pub const MAPPING: Proto = Proto {
         "__reversed__",
         "pyo3::class::mapping::PyMappingReversedProtocolImpl",
     )],
+    slot_setters: &[
+        SlotSetter::new(&["__len__"], "set_length"),
+        SlotSetter::new(&["__getitem__"], "set_getitem"),
+        SlotSetter {
+            proto_names: &["__setitem__", "__delitem__"],
+            set_function: "set_setdelitem",
+            skipped_setters: &["set_setitem", "set_delitem"],
+        },
+        SlotSetter::new(&["__setitem__"], "set_setitem"),
+        SlotSetter::new(&["__delitem__"], "set_delitem"),
+    ],
 };
 
 pub const SEQ: Proto = Proto {
     name: "Sequence",
+    slot_table: "pyo3::ffi::PySequenceMethods",
+    set_slot_table: "set_sequence_methods",
     methods: &[
         MethodProto::Unary {
             name: "__len__",
@@ -373,10 +478,28 @@ pub const SEQ: Proto = Proto {
         },
     ],
     py_methods: &[],
+    slot_setters: &[
+        SlotSetter::new(&["__len__"], "set_len"),
+        SlotSetter::new(&["__concat__"], "set_concat"),
+        SlotSetter::new(&["__repeat__"], "set_repeat"),
+        SlotSetter::new(&["__getitem__"], "set_getitem"),
+        SlotSetter {
+            proto_names: &["__setitem__", "__delitem__"],
+            set_function: "set_setdelitem",
+            skipped_setters: &["set_setitem", "set_delitem"],
+        },
+        SlotSetter::new(&["__setitem__"], "set_setitem"),
+        SlotSetter::new(&["__delitem__"], "set_delitem"),
+        SlotSetter::new(&["__contains__"], "set_contains"),
+        SlotSetter::new(&["__inplace_concat__"], "set_inplace_concat"),
+        SlotSetter::new(&["__inplace_repeat__"], "set_inplace_repeat"),
+    ],
 };
 
 pub const NUM: Proto = Proto {
     name: "Number",
+    slot_table: "pyo3::ffi::PyNumberMethods",
+    set_slot_table: "set_number_methods",
     methods: &[
         MethodProto::BinaryS {
             name: "__add__",
@@ -729,4 +852,106 @@ pub const NUM: Proto = Proto {
             "pyo3::class::number::PyNumberRoundProtocolImpl",
         ),
     ],
+    slot_setters: &[
+        SlotSetter {
+            proto_names: &["__add__"],
+            set_function: "set_add",
+            skipped_setters: &["set_radd"],
+        },
+        SlotSetter::new(&["__radd__"], "set_radd"),
+        SlotSetter {
+            proto_names: &["__sub__"],
+            set_function: "set_sub",
+            skipped_setters: &["set_rsub"],
+        },
+        SlotSetter::new(&["__rsub__"], "set_rsub"),
+        SlotSetter {
+            proto_names: &["__mul__"],
+            set_function: "set_mul",
+            skipped_setters: &["set_rmul"],
+        },
+        SlotSetter::new(&["__rmul__"], "set_rmul"),
+        SlotSetter::new(&["__mod__"], "set_mod"),
+        SlotSetter {
+            proto_names: &["__divmod__"],
+            set_function: "set_divmod",
+            skipped_setters: &["set_rdivmod"],
+        },
+        SlotSetter::new(&["__rdivmod__"], "set_rdivmod"),
+        SlotSetter {
+            proto_names: &["__pow__"],
+            set_function: "set_pow",
+            skipped_setters: &["set_rpow"],
+        },
+        SlotSetter::new(&["__rpow__"], "set_rpow"),
+        SlotSetter::new(&["__neg__"], "set_neg"),
+        SlotSetter::new(&["__pos__"], "set_pos"),
+        SlotSetter::new(&["__abs__"], "set_abs"),
+        SlotSetter::new(&["__invert__"], "set_invert"),
+        SlotSetter::new(&["__rdivmod__"], "set_rdivmod"),
+        SlotSetter {
+            proto_names: &["__lshift__"],
+            set_function: "set_lshift",
+            skipped_setters: &["set_rlshift"],
+        },
+        SlotSetter::new(&["__rlshift__"], "set_rlshift"),
+        SlotSetter {
+            proto_names: &["__rshift__"],
+            set_function: "set_rshift",
+            skipped_setters: &["set_rrshift"],
+        },
+        SlotSetter::new(&["__rrshift__"], "set_rrshift"),
+        SlotSetter {
+            proto_names: &["__and__"],
+            set_function: "set_and",
+            skipped_setters: &["set_rand"],
+        },
+        SlotSetter::new(&["__rand__"], "set_rand"),
+        SlotSetter {
+            proto_names: &["__xor__"],
+            set_function: "set_xor",
+            skipped_setters: &["set_rxor"],
+        },
+        SlotSetter::new(&["__rxor__"], "set_rxor"),
+        SlotSetter {
+            proto_names: &["__or__"],
+            set_function: "set_or",
+            skipped_setters: &["set_ror"],
+        },
+        SlotSetter::new(&["__ror__"], "set_ror"),
+        SlotSetter::new(&["__int__"], "set_int"),
+        SlotSetter::new(&["__float__"], "set_float"),
+        SlotSetter::new(&["__iadd__"], "set_iadd"),
+        SlotSetter::new(&["__isub__"], "set_isub"),
+        SlotSetter::new(&["__imul__"], "set_imul"),
+        SlotSetter::new(&["__imod__"], "set_imod"),
+        SlotSetter::new(&["__ipow__"], "set_ipow"),
+        SlotSetter::new(&["__ilshift__"], "set_ilshift"),
+        SlotSetter::new(&["__irshift__"], "set_irshift"),
+        SlotSetter::new(&["__iand__"], "set_iand"),
+        SlotSetter::new(&["__ixor__"], "set_ixor"),
+        SlotSetter::new(&["__ior__"], "set_ior"),
+        SlotSetter {
+            proto_names: &["__floordiv__"],
+            set_function: "set_floordiv",
+            skipped_setters: &["set_rfloordiv"],
+        },
+        SlotSetter::new(&["__rfloordiv__"], "set_rfloordiv"),
+        SlotSetter {
+            proto_names: &["__truediv__"],
+            set_function: "set_truediv",
+            skipped_setters: &["set_rtruediv"],
+        },
+        SlotSetter::new(&["__rtruediv__"], "set_rtruediv"),
+        SlotSetter::new(&["__ifloordiv__"], "set_ifloordiv"),
+        SlotSetter::new(&["__itruediv__"], "set_itruediv"),
+        SlotSetter::new(&["__index__"], "set_index"),
+        SlotSetter {
+            proto_names: &["__matmul__"],
+            set_function: "set_matmul",
+            skipped_setters: &["set_rmatmul"],
+        },
+        SlotSetter::new(&["__rmatmul__"], "set_rmatmul"),
+        SlotSetter::new(&["__imatmul__"], "set_imatmul"),
+    ],
 };
diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs
index 33a45ba61c7..251c9879bf9 100644
--- a/pyo3-derive-backend/src/pyclass.rs
+++ b/pyo3-derive-backend/src/pyclass.rs
@@ -236,6 +236,19 @@ fn impl_methods_inventory(cls: &syn::Ident) -> TokenStream {
     }
 }
 
+/// Implement `HasProtoRegistry` for the class for lazy protocol initialization.
+fn impl_proto_registry(cls: &syn::Ident) -> TokenStream {
+    quote! {
+        impl pyo3::class::proto_methods::HasProtoRegistry for #cls {
+            fn registry() -> &'static pyo3::class::proto_methods::PyProtoRegistry {
+                static REGISTRY: pyo3::class::proto_methods::PyProtoRegistry
+                    = pyo3::class::proto_methods::PyProtoRegistry::new();
+                &REGISTRY
+            }
+        }
+    }
+}
+
 fn get_class_python_name(cls: &syn::Ident, attr: &PyClassArgs) -> TokenStream {
     match &attr.name {
         Some(name) => quote! { #name },
@@ -340,6 +353,7 @@ fn impl_class(
     };
 
     let impl_inventory = impl_methods_inventory(&cls);
+    let impl_proto_registry = impl_proto_registry(&cls);
 
     let base = &attr.base;
     let flags = &attr.flags;
@@ -414,6 +428,8 @@ fn impl_class(
 
         #impl_inventory
 
+        #impl_proto_registry
+
         #extra
 
         #gc_impl
diff --git a/pyo3-derive-backend/src/pyproto.rs b/pyo3-derive-backend/src/pyproto.rs
index 09459556b74..1cded509c3e 100644
--- a/pyo3-derive-backend/src/pyproto.rs
+++ b/pyo3-derive-backend/src/pyproto.rs
@@ -4,9 +4,10 @@ use crate::defs;
 use crate::func::impl_method_proto;
 use crate::method::FnSpec;
 use crate::pymethod;
-use proc_macro2::TokenStream;
+use proc_macro2::{Span, TokenStream};
 use quote::quote;
 use quote::ToTokens;
+use std::collections::HashSet;
 
 pub fn build_py_proto(ast: &mut syn::ItemImpl) -> syn::Result<TokenStream> {
     if let Some((_, ref mut path, _)) = ast.trait_ {
@@ -60,12 +61,17 @@ fn impl_proto_impl(
 ) -> syn::Result<TokenStream> {
     let mut trait_impls = TokenStream::new();
     let mut py_methods = Vec::new();
+    let mut method_names = HashSet::new();
 
     for iimpl in impls.iter_mut() {
         if let syn::ImplItem::Method(ref mut met) = iimpl {
+            // impl Py~Protocol<'p> { type = ... }
             if let Some(m) = proto.get_proto(&met.sig.ident) {
                 impl_method_proto(ty, &mut met.sig, m).to_tokens(&mut trait_impls);
+                // Insert the method to the HashSet
+                method_names.insert(met.sig.ident.to_string());
             }
+            // Add non-slot methods to inventory like `#[pymethods]`
             if let Some(m) = proto.get_method(&met.sig.ident) {
                 let name = &met.sig.ident;
                 let fn_spec = FnSpec::parse(&met.sig, &mut met.attrs, false)?;
@@ -76,7 +82,7 @@ fn impl_proto_impl(
                 } else {
                     quote!(0)
                 };
-                // TODO(kngwyu): doc
+                // TODO(kngwyu): Set ml_doc
                 py_methods.push(quote! {
                     pyo3::class::PyMethodDefType::Method({
                         #method
@@ -91,20 +97,78 @@ fn impl_proto_impl(
             }
         }
     }
+    let inventory_submission = inventory_submission(py_methods, ty);
+    let slot_initialization = slot_initialization(method_names, ty, proto)?;
+    Ok(quote! {
+        #trait_impls
+        #inventory_submission
+        #slot_initialization
+    })
+}
 
+fn inventory_submission(py_methods: Vec<TokenStream>, ty: &syn::Type) -> TokenStream {
     if py_methods.is_empty() {
-        return Ok(quote! { #trait_impls });
+        return quote! {};
     }
-    let inventory_submission = quote! {
+    quote! {
         pyo3::inventory::submit! {
             #![crate = pyo3] {
                 type Inventory = <#ty as pyo3::class::methods::HasMethodsInventory>::Methods;
                 <Inventory as pyo3::class::methods::PyMethodsInventory>::new(&[#(#py_methods),*])
             }
         }
-    };
+    }
+}
+
+fn slot_initialization(
+    method_names: HashSet<String>,
+    ty: &syn::Type,
+    proto: &defs::Proto,
+) -> syn::Result<TokenStream> {
+    // Some setters cannot coexist.
+    // E.g., if we have `__add__`, we need to skip `set_radd`.
+    let mut skipped_setters = Vec::new();
+    // Collect initializers
+    let mut initializers: Vec<TokenStream> = vec![];
+    'outer_loop: for m in proto.slot_setters {
+        if skipped_setters.contains(&m.set_function) {
+            continue;
+        }
+        for name in m.proto_names {
+            // If this `#[pyproto]` block doesn't provide all required methods,
+            // let's skip implementing this method.
+            if !method_names.contains(*name) {
+                continue 'outer_loop;
+            }
+        }
+        skipped_setters.extend_from_slice(m.skipped_setters);
+        // Add slot methods to PyProtoRegistry
+        let set = syn::Ident::new(m.set_function, Span::call_site());
+        initializers.push(quote! { table.#set::<#ty>(); });
+    }
+    if initializers.is_empty() {
+        return Ok(quote! {});
+    }
+    let table: syn::Path = syn::parse_str(proto.slot_table)?;
+    let set = syn::Ident::new(proto.set_slot_table, Span::call_site());
+    let ty_hash = typename_hash(ty);
+    let init = syn::Ident::new(
+        &format!("__init_{}_{}", proto.name, ty_hash),
+        Span::call_site(),
+    );
     Ok(quote! {
-        #trait_impls
-        #inventory_submission
+        #[pyo3::ctor::ctor]
+        fn #init() {
+            let mut table = #table::default();
+            #(#initializers)*
+            <#ty as pyo3::class::proto_methods::HasProtoRegistry>::registry().#set(table);
+        }
     })
 }
+
+fn typename_hash(ty: &syn::Type) -> u64 {
+    use std::hash::{Hash, Hasher};
+    let mut hasher = std::collections::hash_map::DefaultHasher::new();
+    ty.hash(&mut hasher);
+    hasher.finish()
+}
diff --git a/src/class/basic.rs b/src/class/basic.rs
index 30183cb8135..9af60e51b50 100644
--- a/src/class/basic.rs
+++ b/src/class/basic.rs
@@ -77,13 +77,6 @@ pub trait PyObjectProtocol<'p>: PyClass {
         unimplemented!()
     }
 
-    fn __bool__(&'p self) -> Self::Result
-    where
-        Self: PyObjectBoolProtocol<'p>,
-    {
-        unimplemented!()
-    }
-
     fn __bytes__(&'p self) -> Self::Result
     where
         Self: PyObjectBytesProtocol<'p>,
@@ -97,6 +90,12 @@ pub trait PyObjectProtocol<'p>: PyClass {
     {
         unimplemented!()
     }
+    fn __bool__(&'p self) -> Self::Result
+    where
+        Self: PyObjectBoolProtocol<'p>,
+    {
+        unimplemented!()
+    }
 }
 
 pub trait PyObjectGetAttrProtocol<'p>: PyObjectProtocol<'p> {
@@ -142,311 +141,160 @@ pub trait PyObjectRichcmpProtocol<'p>: PyObjectProtocol<'p> {
     type Result: Into<PyResult<Self::Success>>;
 }
 
-#[doc(hidden)]
-pub trait PyObjectProtocolImpl {
-    fn tp_as_object(_type_object: &mut ffi::PyTypeObject);
-    fn nb_bool_fn() -> Option<ffi::inquiry>;
-}
-
-impl<T> PyObjectProtocolImpl for T {
-    default fn tp_as_object(_type_object: &mut ffi::PyTypeObject) {}
-    default fn nb_bool_fn() -> Option<ffi::inquiry> {
-        None
-    }
+/// All FFI functions for basic protocols.
+#[derive(Default)]
+pub struct PyObjectMethods {
+    pub tp_str: Option<ffi::reprfunc>,
+    pub tp_repr: Option<ffi::reprfunc>,
+    pub tp_hash: Option<ffi::hashfunc>,
+    pub tp_getattro: Option<ffi::getattrofunc>,
+    pub tp_richcompare: Option<ffi::richcmpfunc>,
+    pub tp_setattro: Option<ffi::setattrofunc>,
+    pub nb_bool: Option<ffi::inquiry>,
 }
 
-impl<'p, T> PyObjectProtocolImpl for T
-where
-    T: PyObjectProtocol<'p>,
-{
-    fn tp_as_object(type_object: &mut ffi::PyTypeObject) {
-        type_object.tp_str = Self::tp_str();
-        type_object.tp_repr = Self::tp_repr();
-        type_object.tp_hash = Self::tp_hash();
-        type_object.tp_getattro = Self::tp_getattro();
-        type_object.tp_richcompare = Self::tp_richcompare();
-        type_object.tp_setattro = tp_setattro_impl::tp_setattro::<Self>();
-    }
-    fn nb_bool_fn() -> Option<ffi::inquiry> {
-        Self::nb_bool()
-    }
-}
-
-trait GetAttrProtocolImpl {
-    fn tp_getattro() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> GetAttrProtocolImpl for T
-where
-    T: PyObjectProtocol<'p>,
-{
-    default fn tp_getattro() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> GetAttrProtocolImpl for T
-where
-    T: for<'p> PyObjectGetAttrProtocol<'p>,
-{
-    fn tp_getattro() -> Option<ffi::binaryfunc> {
-        #[allow(unused_mut)]
-        unsafe extern "C" fn wrap<T>(
-            slf: *mut ffi::PyObject,
-            arg: *mut ffi::PyObject,
-        ) -> *mut ffi::PyObject
-        where
-            T: for<'p> PyObjectGetAttrProtocol<'p>,
-        {
-            crate::callback_body!(py, {
-                // Behave like python's __getattr__ (as opposed to __getattribute__) and check
-                // for existing fields and methods first
-                let existing = ffi::PyObject_GenericGetAttr(slf, arg);
-                if existing.is_null() {
-                    // PyObject_HasAttr also tries to get an object and clears the error if it fails
-                    ffi::PyErr_Clear();
-                } else {
-                    return Ok(existing);
-                }
-
-                let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
-                let arg = py.from_borrowed_ptr::<PyAny>(arg);
-                call_ref!(slf, __getattr__, arg)
-            })
-        }
-        Some(wrap::<T>)
-    }
-}
-
-/// An object may support setting attributes (by implementing PyObjectSetAttrProtocol)
-/// and may support deleting attributes (by implementing PyObjectDelAttrProtocol).
-/// We need to generate a single "extern C" function that supports only setting, only deleting
-/// or both, and return None in case none of the two is supported.
-mod tp_setattro_impl {
-    use super::*;
-
-    /// setattrofunc PyTypeObject.tp_setattro
-    ///
-    /// An optional pointer to the function for setting and deleting attributes.
-    ///
-    /// The signature is the same as for PyObject_SetAttr(), but setting v to NULL to delete an
-    /// attribute must be supported. It is usually convenient to set this field to
-    /// PyObject_GenericSetAttr(), which implements the normal way of setting object attributes.
-    pub(super) fn tp_setattro<'p, T: PyObjectProtocol<'p>>() -> Option<ffi::setattrofunc> {
-        if let Some(set_del) = T::set_del_attr() {
-            Some(set_del)
-        } else if let Some(set) = T::set_attr() {
-            Some(set)
-        } else if let Some(del) = T::del_attr() {
-            Some(del)
-        } else {
-            None
-        }
-    }
-
-    trait SetAttr {
-        fn set_attr() -> Option<ffi::setattrofunc>;
-    }
-
-    impl<'p, T: PyObjectProtocol<'p>> SetAttr for T {
-        default fn set_attr() -> Option<ffi::setattrofunc> {
-            None
-        }
+#[doc(hidden)]
+impl PyObjectMethods {
+    pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) {
+        type_object.tp_str = self.tp_str;
+        type_object.tp_repr = self.tp_repr;
+        type_object.tp_hash = self.tp_hash;
+        type_object.tp_getattro = self.tp_getattro;
+        type_object.tp_richcompare = self.tp_richcompare;
+        type_object.tp_setattro = self.tp_setattro;
+    }
+    // Set functions used by `#[pyproto]`.
+    pub fn set_str<T>(&mut self)
+    where
+        T: for<'p> PyObjectStrProtocol<'p>,
+    {
+        self.tp_str = py_unary_func!(PyObjectStrProtocol, T::__str__);
     }
-
-    impl<T> SetAttr for T
+    pub fn set_repr<T>(&mut self)
     where
-        T: for<'p> PyObjectSetAttrProtocol<'p>,
+        T: for<'p> PyObjectReprProtocol<'p>,
     {
-        fn set_attr() -> Option<ffi::setattrofunc> {
-            py_func_set!(PyObjectSetAttrProtocol, T, __setattr__)
-        }
+        self.tp_repr = py_unary_func!(PyObjectReprProtocol, T::__repr__);
     }
-
-    trait DelAttr {
-        fn del_attr() -> Option<ffi::setattrofunc>;
+    pub fn set_hash<T>(&mut self)
+    where
+        T: for<'p> PyObjectHashProtocol<'p>,
+    {
+        self.tp_hash = py_unary_func!(
+            PyObjectHashProtocol,
+            T::__hash__,
+            ffi::Py_hash_t,
+            HashCallbackOutput
+        );
     }
-
-    impl<'p, T> DelAttr for T
+    pub fn set_getattr<T>(&mut self)
     where
-        T: PyObjectProtocol<'p>,
+        T: for<'p> PyObjectGetAttrProtocol<'p>,
     {
-        default fn del_attr() -> Option<ffi::setattrofunc> {
-            None
-        }
+        self.tp_getattro = tp_getattro::<T>();
     }
-
-    impl<T> DelAttr for T
+    pub fn set_richcompare<T>(&mut self)
     where
-        T: for<'p> PyObjectDelAttrProtocol<'p>,
+        T: for<'p> PyObjectRichcmpProtocol<'p>,
     {
-        fn del_attr() -> Option<ffi::setattrofunc> {
-            py_func_del!(PyObjectDelAttrProtocol, T, __delattr__)
-        }
+        self.tp_richcompare = tp_richcompare::<T>();
     }
-
-    trait SetDelAttr {
-        fn set_del_attr() -> Option<ffi::setattrofunc>;
+    pub fn set_setattr<T>(&mut self)
+    where
+        T: for<'p> PyObjectSetAttrProtocol<'p>,
+    {
+        self.tp_setattro = py_func_set!(PyObjectSetAttrProtocol, T, __setattr__);
     }
-
-    impl<'p, T> SetDelAttr for T
+    pub fn set_delattr<T>(&mut self)
     where
-        T: PyObjectProtocol<'p>,
+        T: for<'p> PyObjectDelAttrProtocol<'p>,
     {
-        default fn set_del_attr() -> Option<ffi::setattrofunc> {
-            None
-        }
+        self.tp_setattro = py_func_del!(PyObjectDelAttrProtocol, T, __delattr__);
     }
-
-    impl<T> SetDelAttr for T
+    pub fn set_setdelattr<T>(&mut self)
     where
         T: for<'p> PyObjectSetAttrProtocol<'p> + for<'p> PyObjectDelAttrProtocol<'p>,
     {
-        fn set_del_attr() -> Option<ffi::setattrofunc> {
-            py_func_set_del!(
-                PyObjectSetAttrProtocol,
-                PyObjectDelAttrProtocol,
-                T,
-                __setattr__,
-                __delattr__
-            )
-        }
-    }
-}
-
-trait StrProtocolImpl {
-    fn tp_str() -> Option<ffi::unaryfunc>;
-}
-impl<'p, T> StrProtocolImpl for T
-where
-    T: PyObjectProtocol<'p>,
-{
-    default fn tp_str() -> Option<ffi::unaryfunc> {
-        None
-    }
-}
-impl<T> StrProtocolImpl for T
-where
-    T: for<'p> PyObjectStrProtocol<'p>,
-{
-    fn tp_str() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyObjectStrProtocol, T::__str__)
-    }
-}
-
-trait ReprProtocolImpl {
-    fn tp_repr() -> Option<ffi::unaryfunc>;
-}
-impl<'p, T> ReprProtocolImpl for T
-where
-    T: PyObjectProtocol<'p>,
-{
-    default fn tp_repr() -> Option<ffi::unaryfunc> {
-        None
-    }
-}
-impl<T> ReprProtocolImpl for T
-where
-    T: for<'p> PyObjectReprProtocol<'p>,
-{
-    fn tp_repr() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyObjectReprProtocol, T::__repr__)
-    }
-}
-
-trait HashProtocolImpl {
-    fn tp_hash() -> Option<ffi::hashfunc>;
-}
-impl<'p, T> HashProtocolImpl for T
-where
-    T: PyObjectProtocol<'p>,
-{
-    default fn tp_hash() -> Option<ffi::hashfunc> {
-        None
-    }
-}
-impl<T> HashProtocolImpl for T
-where
-    T: for<'p> PyObjectHashProtocol<'p>,
-{
-    fn tp_hash() -> Option<ffi::hashfunc> {
-        py_unary_func!(
-            PyObjectHashProtocol,
-            T::__hash__,
-            ffi::Py_hash_t,
-            HashCallbackOutput
+        self.tp_setattro = py_func_set_del!(
+            PyObjectSetAttrProtocol,
+            PyObjectDelAttrProtocol,
+            T,
+            __setattr__,
+            __delattr__
         )
     }
-}
-
-trait BoolProtocolImpl {
-    fn nb_bool() -> Option<ffi::inquiry>;
-}
-impl<'p, T> BoolProtocolImpl for T
-where
-    T: PyObjectProtocol<'p>,
-{
-    default fn nb_bool() -> Option<ffi::inquiry> {
-        None
-    }
-}
-impl<T> BoolProtocolImpl for T
-where
-    T: for<'p> PyObjectBoolProtocol<'p>,
-{
-    fn nb_bool() -> Option<ffi::inquiry> {
-        py_unary_func!(PyObjectBoolProtocol, T::__bool__, c_int)
+    pub fn set_bool<T>(&mut self)
+    where
+        T: for<'p> PyObjectBoolProtocol<'p>,
+    {
+        self.nb_bool = py_unary_func!(PyObjectBoolProtocol, T::__bool__, c_int);
     }
 }
 
-trait RichcmpProtocolImpl {
-    fn tp_richcompare() -> Option<ffi::richcmpfunc>;
-}
-impl<'p, T> RichcmpProtocolImpl for T
+fn tp_getattro<T>() -> Option<ffi::binaryfunc>
 where
-    T: PyObjectProtocol<'p>,
+    T: for<'p> PyObjectGetAttrProtocol<'p>,
 {
-    default fn tp_richcompare() -> Option<ffi::richcmpfunc> {
-        None
-    }
-}
-impl<T> RichcmpProtocolImpl for T
+    unsafe extern "C" fn wrap<T>(
+        slf: *mut ffi::PyObject,
+        arg: *mut ffi::PyObject,
+    ) -> *mut ffi::PyObject
+    where
+        T: for<'p> PyObjectGetAttrProtocol<'p>,
+    {
+        crate::callback_body!(py, {
+            // Behave like python's __getattr__ (as opposed to __getattribute__) and check
+            // for existing fields and methods first
+            let existing = ffi::PyObject_GenericGetAttr(slf, arg);
+            if existing.is_null() {
+                // PyObject_HasAttr also tries to get an object and clears the error if it fails
+                ffi::PyErr_Clear();
+            } else {
+                return Ok(existing);
+            }
+
+            let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
+            let arg = py.from_borrowed_ptr::<PyAny>(arg);
+            call_ref!(slf, __getattr__, arg)
+        })
+    }
+    Some(wrap::<T>)
+}
+
+fn tp_richcompare<T>() -> Option<ffi::richcmpfunc>
 where
     T: for<'p> PyObjectRichcmpProtocol<'p>,
 {
-    fn tp_richcompare() -> Option<ffi::richcmpfunc> {
-        unsafe extern "C" fn wrap<T>(
-            slf: *mut ffi::PyObject,
-            arg: *mut ffi::PyObject,
-            op: c_int,
-        ) -> *mut ffi::PyObject
-        where
-            T: for<'p> PyObjectRichcmpProtocol<'p>,
-        {
-            crate::callback_body!(py, {
-                let slf = py.from_borrowed_ptr::<crate::PyCell<T>>(slf);
-                let arg = py.from_borrowed_ptr::<PyAny>(arg);
-
-                let op = extract_op(op)?;
-                let arg = arg.extract()?;
-
-                slf.try_borrow()?.__richcmp__(arg, op).into()
-            })
+    fn extract_op(op: c_int) -> PyResult<CompareOp> {
+        match op {
+            ffi::Py_LT => Ok(CompareOp::Lt),
+            ffi::Py_LE => Ok(CompareOp::Le),
+            ffi::Py_EQ => Ok(CompareOp::Eq),
+            ffi::Py_NE => Ok(CompareOp::Ne),
+            ffi::Py_GT => Ok(CompareOp::Gt),
+            ffi::Py_GE => Ok(CompareOp::Ge),
+            _ => Err(PyErr::new::<exceptions::ValueError, _>(
+                "tp_richcompare called with invalid comparison operator",
+            )),
         }
-        Some(wrap::<T>)
     }
-}
+    unsafe extern "C" fn wrap<T>(
+        slf: *mut ffi::PyObject,
+        arg: *mut ffi::PyObject,
+        op: c_int,
+    ) -> *mut ffi::PyObject
+    where
+        T: for<'p> PyObjectRichcmpProtocol<'p>,
+    {
+        crate::callback_body!(py, {
+            let slf = py.from_borrowed_ptr::<crate::PyCell<T>>(slf);
+            let arg = py.from_borrowed_ptr::<PyAny>(arg);
+
+            let op = extract_op(op)?;
+            let arg = arg.extract()?;
 
-fn extract_op(op: c_int) -> PyResult<CompareOp> {
-    match op {
-        ffi::Py_LT => Ok(CompareOp::Lt),
-        ffi::Py_LE => Ok(CompareOp::Le),
-        ffi::Py_EQ => Ok(CompareOp::Eq),
-        ffi::Py_NE => Ok(CompareOp::Ne),
-        ffi::Py_GT => Ok(CompareOp::Gt),
-        ffi::Py_GE => Ok(CompareOp::Ge),
-        _ => Err(PyErr::new::<exceptions::ValueError, _>(
-            "tp_richcompare called with invalid comparison operator",
-        )),
+            slf.try_borrow()?.__richcmp__(arg, op).into()
+        })
     }
+    Some(wrap::<T>)
 }
diff --git a/src/class/buffer.rs b/src/class/buffer.rs
index 7b27ef15f57..c77668583cd 100644
--- a/src/class/buffer.rs
+++ b/src/class/buffer.rs
@@ -5,7 +5,10 @@
 //! For more information check [buffer protocol](https://docs.python.org/3/c-api/buffer.html)
 //! c-api
 use crate::err::PyResult;
-use crate::{ffi, PyCell, PyClass, PyRefMut};
+use crate::{
+    ffi::{self, PyBufferProcs},
+    PyCell, PyClass, PyRefMut,
+};
 use std::os::raw::c_int;
 
 /// Buffer protocol interface
@@ -37,96 +40,55 @@ pub trait PyBufferReleaseBufferProtocol<'p>: PyBufferProtocol<'p> {
     type Result: Into<PyResult<()>>;
 }
 
+/// Set functions used by `#[pyproto]`.
 #[doc(hidden)]
-pub trait PyBufferProtocolImpl {
-    fn tp_as_buffer() -> Option<ffi::PyBufferProcs>;
-}
-
-impl<T> PyBufferProtocolImpl for T {
-    default fn tp_as_buffer() -> Option<ffi::PyBufferProcs> {
-        None
-    }
-}
-
-impl<'p, T> PyBufferProtocolImpl for T
-where
-    T: PyBufferProtocol<'p>,
-{
-    #[inline]
-    #[allow(clippy::needless_update)] // For python 2 it's not useless
-    fn tp_as_buffer() -> Option<ffi::PyBufferProcs> {
-        Some(ffi::PyBufferProcs {
-            bf_getbuffer: Self::cb_bf_getbuffer(),
-            bf_releasebuffer: Self::cb_bf_releasebuffer(),
-            ..ffi::PyBufferProcs_INIT
-        })
+impl PyBufferProcs {
+    pub fn set_getbuffer<T>(&mut self)
+    where
+        T: for<'p> PyBufferGetBufferProtocol<'p>,
+    {
+        self.bf_getbuffer = bf_getbuffer::<T>();
     }
-}
-
-trait PyBufferGetBufferProtocolImpl {
-    fn cb_bf_getbuffer() -> Option<ffi::getbufferproc>;
-}
-
-impl<'p, T> PyBufferGetBufferProtocolImpl for T
-where
-    T: PyBufferProtocol<'p>,
-{
-    default fn cb_bf_getbuffer() -> Option<ffi::getbufferproc> {
-        None
+    pub fn set_releasebuffer<T>(&mut self)
+    where
+        T: for<'p> PyBufferReleaseBufferProtocol<'p>,
+    {
+        self.bf_releasebuffer = bf_releasebuffer::<T>();
     }
 }
 
-impl<T> PyBufferGetBufferProtocolImpl for T
+fn bf_getbuffer<T>() -> Option<ffi::getbufferproc>
 where
     T: for<'p> PyBufferGetBufferProtocol<'p>,
 {
-    #[inline]
-    fn cb_bf_getbuffer() -> Option<ffi::getbufferproc> {
-        unsafe extern "C" fn wrap<T>(
-            slf: *mut ffi::PyObject,
-            arg1: *mut ffi::Py_buffer,
-            arg2: c_int,
-        ) -> c_int
-        where
-            T: for<'p> PyBufferGetBufferProtocol<'p>,
-        {
-            crate::callback_body!(py, {
-                let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
-                T::bf_getbuffer(slf.try_borrow_mut()?, arg1, arg2).into()
-            })
-        }
-        Some(wrap::<T>)
-    }
-}
-
-trait PyBufferReleaseBufferProtocolImpl {
-    fn cb_bf_releasebuffer() -> Option<ffi::releasebufferproc>;
-}
-
-impl<'p, T> PyBufferReleaseBufferProtocolImpl for T
-where
-    T: PyBufferProtocol<'p>,
-{
-    default fn cb_bf_releasebuffer() -> Option<ffi::releasebufferproc> {
-        None
+    unsafe extern "C" fn wrap<T>(
+        slf: *mut ffi::PyObject,
+        arg1: *mut ffi::Py_buffer,
+        arg2: c_int,
+    ) -> c_int
+    where
+        T: for<'p> PyBufferGetBufferProtocol<'p>,
+    {
+        crate::callback_body!(py, {
+            let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
+            T::bf_getbuffer(slf.try_borrow_mut()?, arg1, arg2).into()
+        })
     }
+    Some(wrap::<T>)
 }
 
-impl<T> PyBufferReleaseBufferProtocolImpl for T
+fn bf_releasebuffer<T>() -> Option<ffi::releasebufferproc>
 where
     T: for<'p> PyBufferReleaseBufferProtocol<'p>,
 {
-    #[inline]
-    fn cb_bf_releasebuffer() -> Option<ffi::releasebufferproc> {
-        unsafe extern "C" fn wrap<T>(slf: *mut ffi::PyObject, arg1: *mut ffi::Py_buffer)
-        where
-            T: for<'p> PyBufferReleaseBufferProtocol<'p>,
-        {
-            crate::callback_body!(py, {
-                let slf = py.from_borrowed_ptr::<crate::PyCell<T>>(slf);
-                T::bf_releasebuffer(slf.try_borrow_mut()?, arg1).into()
-            })
-        }
-        Some(wrap::<T>)
+    unsafe extern "C" fn wrap<T>(slf: *mut ffi::PyObject, arg1: *mut ffi::Py_buffer)
+    where
+        T: for<'p> PyBufferReleaseBufferProtocol<'p>,
+    {
+        crate::callback_body!(py, {
+            let slf = py.from_borrowed_ptr::<crate::PyCell<T>>(slf);
+            T::bf_releasebuffer(slf.try_borrow_mut()?, arg1).into()
+        })
     }
+    Some(wrap::<T>)
 }
diff --git a/src/class/descr.rs b/src/class/descr.rs
index 1349f7a734c..83421b4499b 100644
--- a/src/class/descr.rs
+++ b/src/class/descr.rs
@@ -5,23 +5,26 @@
 //! [Python information](
 //! https://docs.python.org/3/reference/datamodel.html#implementing-descriptors)
 
-use crate::class::methods::PyMethodDef;
 use crate::err::PyResult;
-use crate::types::{PyAny, PyType};
+use crate::types::PyAny;
 use crate::{ffi, FromPyObject, IntoPy, PyClass, PyObject};
 use std::os::raw::c_int;
 
 /// Descriptor interface
 #[allow(unused_variables)]
 pub trait PyDescrProtocol<'p>: PyClass {
-    fn __get__(&'p self, instance: &'p PyAny, owner: Option<&'p PyType>) -> Self::Result
+    fn __get__(
+        slf: Self::Receiver,
+        instance: Self::Inst,
+        owner: Option<Self::Owner>,
+    ) -> Self::Result
     where
         Self: PyDescrGetProtocol<'p>,
     {
         unimplemented!()
     }
 
-    fn __set__(&'p self, instance: &'p PyAny, value: &'p PyAny) -> Self::Result
+    fn __set__(slf: Self::Receiver, instance: Self::Inst, value: Self::Value) -> Self::Result
     where
         Self: PyDescrSetProtocol<'p>,
     {
@@ -44,6 +47,7 @@ pub trait PyDescrProtocol<'p>: PyClass {
 }
 
 pub trait PyDescrGetProtocol<'p>: PyDescrProtocol<'p> {
+    type Receiver: crate::derive_utils::TryFromPyCell<'p, Self>;
     type Inst: FromPyObject<'p>;
     type Owner: FromPyObject<'p>;
     type Success: IntoPy<PyObject>;
@@ -51,6 +55,7 @@ pub trait PyDescrGetProtocol<'p>: PyDescrProtocol<'p> {
 }
 
 pub trait PyDescrSetProtocol<'p>: PyDescrProtocol<'p> {
+    type Receiver: crate::derive_utils::TryFromPyCell<'p, Self>;
     type Inst: FromPyObject<'p>;
     type Value: FromPyObject<'p>;
     type Result: Into<PyResult<()>>;
@@ -66,76 +71,29 @@ pub trait PyDescrSetNameProtocol<'p>: PyDescrProtocol<'p> {
     type Result: Into<PyResult<()>>;
 }
 
-trait PyDescrGetProtocolImpl {
-    fn tp_descr_get() -> Option<ffi::descrgetfunc>;
-}
-impl<'p, T> PyDescrGetProtocolImpl for T
-where
-    T: PyDescrProtocol<'p>,
-{
-    default fn tp_descr_get() -> Option<ffi::descrgetfunc> {
-        None
-    }
+/// All FFI functions for description protocols.
+#[derive(Default)]
+pub struct PyDescrMethods {
+    pub tp_descr_get: Option<ffi::descrgetfunc>,
+    pub tp_descr_set: Option<ffi::descrsetfunc>,
 }
 
-impl<T> PyDescrGetProtocolImpl for T
-where
-    T: for<'p> PyDescrGetProtocol<'p>,
-{
-    fn tp_descr_get() -> Option<ffi::descrgetfunc> {
-        py_ternary_func!(PyDescrGetProtocol, T::__get__)
-    }
-}
-
-trait PyDescrSetProtocolImpl {
-    fn tp_descr_set() -> Option<ffi::descrsetfunc>;
-}
-impl<'p, T> PyDescrSetProtocolImpl for T
-where
-    T: PyDescrProtocol<'p>,
-{
-    default fn tp_descr_set() -> Option<ffi::descrsetfunc> {
-        None
-    }
-}
-impl<T> PyDescrSetProtocolImpl for T
-where
-    T: for<'p> PyDescrSetProtocol<'p>,
-{
-    fn tp_descr_set() -> Option<ffi::descrsetfunc> {
-        py_ternary_func!(PyDescrSetProtocol, T::__set__, c_int)
-    }
-}
-
-trait PyDescrDelProtocolImpl {
-    fn __del__() -> Option<PyMethodDef> {
-        None
+#[doc(hidden)]
+impl PyDescrMethods {
+    pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) {
+        type_object.tp_descr_get = self.tp_descr_get;
+        type_object.tp_descr_set = self.tp_descr_set;
     }
-}
-impl<'p, T> PyDescrDelProtocolImpl for T where T: PyDescrProtocol<'p> {}
-
-trait PyDescrSetNameProtocolImpl {
-    fn __set_name__() -> Option<PyMethodDef> {
-        None
+    pub fn set_descr_get<T>(&mut self)
+    where
+        T: for<'p> PyDescrGetProtocol<'p>,
+    {
+        self.tp_descr_get = py_ternarys_func!(PyDescrGetProtocol, T::__get__);
     }
-}
-impl<'p, T> PyDescrSetNameProtocolImpl for T where T: PyDescrProtocol<'p> {}
-
-#[doc(hidden)]
-pub trait PyDescrProtocolImpl {
-    fn tp_as_descr(_type_object: &mut ffi::PyTypeObject);
-}
-
-impl<T> PyDescrProtocolImpl for T {
-    default fn tp_as_descr(_type_object: &mut ffi::PyTypeObject) {}
-}
-
-impl<'p, T> PyDescrProtocolImpl for T
-where
-    T: PyDescrProtocol<'p>,
-{
-    fn tp_as_descr(type_object: &mut ffi::PyTypeObject) {
-        type_object.tp_descr_get = Self::tp_descr_get();
-        type_object.tp_descr_set = Self::tp_descr_set();
+    pub fn set_descr_set<T>(&mut self)
+    where
+        T: for<'p> PyDescrSetProtocol<'p>,
+    {
+        self.tp_descr_set = py_ternarys_func!(PyDescrSetProtocol, T::__set__, c_int);
     }
 }
diff --git a/src/class/gc.rs b/src/class/gc.rs
index 040a6352410..f639b8dcc35 100644
--- a/src/class/gc.rs
+++ b/src/class/gc.rs
@@ -18,25 +18,36 @@ pub trait PyGCProtocol<'p>: PyClass {
 pub trait PyGCTraverseProtocol<'p>: PyGCProtocol<'p> {}
 pub trait PyGCClearProtocol<'p>: PyGCProtocol<'p> {}
 
-#[doc(hidden)]
-pub trait PyGCProtocolImpl {
-    fn update_type_object(_type_object: &mut ffi::PyTypeObject);
+/// All FFI functions for gc protocols.
+#[derive(Default)]
+pub struct PyGCMethods {
+    pub tp_traverse: Option<ffi::traverseproc>,
+    pub tp_clear: Option<ffi::inquiry>,
 }
 
-impl<'p, T> PyGCProtocolImpl for T {
-    default fn update_type_object(_type_object: &mut ffi::PyTypeObject) {}
-}
+#[doc(hidden)]
+impl PyGCMethods {
+    pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) {
+        type_object.tp_traverse = self.tp_traverse;
+        type_object.tp_clear = self.tp_clear;
+    }
 
-impl<'p, T> PyGCProtocolImpl for T
-where
-    T: PyGCProtocol<'p>,
-{
-    fn update_type_object(type_object: &mut ffi::PyTypeObject) {
-        type_object.tp_traverse = Self::tp_traverse();
-        type_object.tp_clear = Self::tp_clear();
+    pub fn set_traverse<T>(&mut self)
+    where
+        T: for<'p> PyGCTraverseProtocol<'p>,
+    {
+        self.tp_traverse = tp_traverse::<T>();
+    }
+
+    pub fn set_clear<T>(&mut self)
+    where
+        T: for<'p> PyGCClearProtocol<'p>,
+    {
+        self.tp_clear = tp_clear::<T>();
     }
 }
 
+/// Object visitor for GC.
 #[derive(Copy, Clone)]
 pub struct PyVisit<'p> {
     visit: ffi::visitproc,
@@ -48,6 +59,7 @@ pub struct PyVisit<'p> {
 }
 
 impl<'p> PyVisit<'p> {
+    /// Visit `obj`.
     pub fn call<T>(&self, obj: &T) -> Result<(), PyTraverseError>
     where
         T: AsPyPointer,
@@ -61,88 +73,55 @@ impl<'p> PyVisit<'p> {
     }
 }
 
-trait PyGCTraverseProtocolImpl {
-    fn tp_traverse() -> Option<ffi::traverseproc>;
-}
-
-impl<'p, T> PyGCTraverseProtocolImpl for T
-where
-    T: PyGCProtocol<'p>,
-{
-    default fn tp_traverse() -> Option<ffi::traverseproc> {
-        None
-    }
-}
-
-#[doc(hidden)]
-impl<T> PyGCTraverseProtocolImpl for T
+fn tp_traverse<T>() -> Option<ffi::traverseproc>
 where
     T: for<'p> PyGCTraverseProtocol<'p>,
 {
-    #[inline]
-    fn tp_traverse() -> Option<ffi::traverseproc> {
-        unsafe extern "C" fn tp_traverse<T>(
-            slf: *mut ffi::PyObject,
-            visit: ffi::visitproc,
-            arg: *mut c_void,
-        ) -> c_int
-        where
-            T: for<'p> PyGCTraverseProtocol<'p>,
-        {
-            let pool = crate::GILPool::new();
-            let py = pool.python();
-            let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
-
-            let visit = PyVisit {
-                visit,
-                arg,
-                _py: py,
-            };
-            let borrow = slf.try_borrow();
-            if let Ok(borrow) = borrow {
-                match borrow.__traverse__(visit) {
-                    Ok(()) => 0,
-                    Err(PyTraverseError(code)) => code,
-                }
-            } else {
-                0
+    unsafe extern "C" fn tp_traverse<T>(
+        slf: *mut ffi::PyObject,
+        visit: ffi::visitproc,
+        arg: *mut c_void,
+    ) -> c_int
+    where
+        T: for<'p> PyGCTraverseProtocol<'p>,
+    {
+        let pool = crate::GILPool::new();
+        let py = pool.python();
+        let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
+
+        let visit = PyVisit {
+            visit,
+            arg,
+            _py: py,
+        };
+        let borrow = slf.try_borrow();
+        if let Ok(borrow) = borrow {
+            match borrow.__traverse__(visit) {
+                Ok(()) => 0,
+                Err(PyTraverseError(code)) => code,
             }
+        } else {
+            0
         }
-
-        Some(tp_traverse::<T>)
     }
-}
-
-trait PyGCClearProtocolImpl {
-    fn tp_clear() -> Option<ffi::inquiry>;
-}
 
-impl<'p, T> PyGCClearProtocolImpl for T
-where
-    T: PyGCProtocol<'p>,
-{
-    default fn tp_clear() -> Option<ffi::inquiry> {
-        None
-    }
+    Some(tp_traverse::<T>)
 }
 
-impl<T> PyGCClearProtocolImpl for T
+fn tp_clear<T>() -> Option<ffi::inquiry>
 where
     T: for<'p> PyGCClearProtocol<'p>,
 {
-    #[inline]
-    fn tp_clear() -> Option<ffi::inquiry> {
-        unsafe extern "C" fn tp_clear<T>(slf: *mut ffi::PyObject) -> c_int
-        where
-            T: for<'p> PyGCClearProtocol<'p>,
-        {
-            let pool = crate::GILPool::new();
-            let py = pool.python();
-            let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
-
-            slf.borrow_mut().__clear__();
-            0
-        }
-        Some(tp_clear::<T>)
+    unsafe extern "C" fn tp_clear<T>(slf: *mut ffi::PyObject) -> c_int
+    where
+        T: for<'p> PyGCClearProtocol<'p>,
+    {
+        let pool = crate::GILPool::new();
+        let py = pool.python();
+        let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
+
+        slf.borrow_mut().__clear__();
+        0
     }
+    Some(tp_clear::<T>)
 }
diff --git a/src/class/iter.rs b/src/class/iter.rs
index 08c12963df3..04fbd1e953f 100644
--- a/src/class/iter.rs
+++ b/src/class/iter.rs
@@ -40,69 +40,29 @@ pub trait PyIterNextProtocol<'p>: PyIterProtocol<'p> {
     type Result: Into<PyResult<Option<Self::Success>>>;
 }
 
-#[doc(hidden)]
-pub trait PyIterProtocolImpl {
-    fn tp_as_iter(_typeob: &mut ffi::PyTypeObject);
-}
-
-impl<T> PyIterProtocolImpl for T {
-    default fn tp_as_iter(_typeob: &mut ffi::PyTypeObject) {}
-}
-
-impl<'p, T> PyIterProtocolImpl for T
-where
-    T: PyIterProtocol<'p>,
-{
-    #[inline]
-    fn tp_as_iter(typeob: &mut ffi::PyTypeObject) {
-        typeob.tp_iter = Self::tp_iter();
-        typeob.tp_iternext = Self::tp_iternext();
-    }
+#[derive(Default)]
+pub struct PyIterMethods {
+    pub tp_iter: Option<ffi::getiterfunc>,
+    pub tp_iternext: Option<ffi::iternextfunc>,
 }
 
-trait PyIterIterProtocolImpl {
-    fn tp_iter() -> Option<ffi::getiterfunc>;
-}
-
-impl<'p, T> PyIterIterProtocolImpl for T
-where
-    T: PyIterProtocol<'p>,
-{
-    default fn tp_iter() -> Option<ffi::getiterfunc> {
-        None
-    }
-}
-
-impl<T> PyIterIterProtocolImpl for T
-where
-    T: for<'p> PyIterIterProtocol<'p>,
-{
-    #[inline]
-    fn tp_iter() -> Option<ffi::getiterfunc> {
-        py_unarys_func!(PyIterIterProtocol, T::__iter__)
+#[doc(hidden)]
+impl PyIterMethods {
+    pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) {
+        type_object.tp_iter = self.tp_iter;
+        type_object.tp_iternext = self.tp_iternext;
     }
-}
-
-trait PyIterNextProtocolImpl {
-    fn tp_iternext() -> Option<ffi::iternextfunc>;
-}
-
-impl<'p, T> PyIterNextProtocolImpl for T
-where
-    T: PyIterProtocol<'p>,
-{
-    default fn tp_iternext() -> Option<ffi::iternextfunc> {
-        None
+    pub fn set_iter<T>(&mut self)
+    where
+        T: for<'p> PyIterIterProtocol<'p>,
+    {
+        self.tp_iter = py_unarys_func!(PyIterIterProtocol, T::__iter__);
     }
-}
-
-impl<T> PyIterNextProtocolImpl for T
-where
-    T: for<'p> PyIterNextProtocol<'p>,
-{
-    #[inline]
-    fn tp_iternext() -> Option<ffi::iternextfunc> {
-        py_unarys_func!(PyIterNextProtocol, T::__next__, IterNextConverter)
+    pub fn set_iternext<T>(&mut self)
+    where
+        T: for<'p> PyIterNextProtocol<'p>,
+    {
+        self.tp_iternext = py_unarys_func!(PyIterNextProtocol, T::__next__, IterNextConverter);
     }
 }
 
diff --git a/src/class/macros.rs b/src/class/macros.rs
index 54d82779d98..8141aedff20 100644
--- a/src/class/macros.rs
+++ b/src/class/macros.rs
@@ -34,8 +34,9 @@ macro_rules! py_unarys_func {
         {
             $crate::callback_body!(py, {
                 let slf = py.from_borrowed_ptr::<$crate::PyCell<T>>(slf);
-                let borrow = <T::Receiver>::try_from_pycell(slf)
-                    .map_err(|e| e.into())?;
+                let borrow =
+                    <T::Receiver as $crate::derive_utils::TryFromPyCell<_>>::try_from_pycell(slf)
+                        .map_err(|e| e.into())?;
 
                 $class::$f(borrow).into()$(.map($conv))?
             })
@@ -106,7 +107,7 @@ macro_rules! py_binary_num_func {
 
 #[macro_export]
 #[doc(hidden)]
-macro_rules! py_binary_reverse_num_func {
+macro_rules! py_binary_reversed_num_func {
     ($trait:ident, $class:ident :: $f:ident) => {{
         unsafe extern "C" fn wrap<T>(
             lhs: *mut ffi::PyObject,
@@ -177,7 +178,7 @@ macro_rules! py_ssizearg_func {
 
 #[macro_export]
 #[doc(hidden)]
-macro_rules! py_ternary_func {
+macro_rules! py_ternarys_func {
     ($trait:ident, $class:ident :: $f:ident, $return_type:ty) => {{
         unsafe extern "C" fn wrap<T>(
             slf: *mut $crate::ffi::PyObject,
@@ -189,6 +190,9 @@ macro_rules! py_ternary_func {
         {
             $crate::callback_body!(py, {
                 let slf = py.from_borrowed_ptr::<$crate::PyCell<T>>(slf);
+                let slf =
+                    <T::Receiver as $crate::derive_utils::TryFromPyCell<_>>::try_from_pycell(slf)
+                        .map_err(|e| e.into())?;
                 let arg1 = py
                     .from_borrowed_ptr::<$crate::types::PyAny>(arg1)
                     .extract()?;
@@ -196,14 +200,14 @@ macro_rules! py_ternary_func {
                     .from_borrowed_ptr::<$crate::types::PyAny>(arg2)
                     .extract()?;
 
-                slf.try_borrow()?.$f(arg1, arg2).into()
+                $class::$f(slf, arg1, arg2).into()
             })
         }
 
         Some(wrap::<T>)
     }};
     ($trait:ident, $class:ident :: $f:ident) => {
-        py_ternary_func!($trait, $class::$f, *mut $crate::ffi::PyObject);
+        py_ternarys_func!($trait, $class::$f, *mut $crate::ffi::PyObject);
     };
 }
 
@@ -240,7 +244,7 @@ macro_rules! py_ternary_num_func {
 
 #[macro_export]
 #[doc(hidden)]
-macro_rules! py_ternary_reverse_num_func {
+macro_rules! py_ternary_reversed_num_func {
     ($trait:ident, $class:ident :: $f:ident) => {{
         unsafe extern "C" fn wrap<T>(
             arg1: *mut $crate::ffi::PyObject,
diff --git a/src/class/mapping.rs b/src/class/mapping.rs
index 2f5fa8e59c4..4e51ebed0bf 100644
--- a/src/class/mapping.rs
+++ b/src/class/mapping.rs
@@ -75,154 +75,41 @@ pub trait PyMappingReversedProtocol<'p>: PyMappingProtocol<'p> {
 }
 
 #[doc(hidden)]
-pub trait PyMappingProtocolImpl {
-    fn tp_as_mapping() -> Option<ffi::PyMappingMethods>;
-}
-
-impl<T> PyMappingProtocolImpl for T {
-    default fn tp_as_mapping() -> Option<ffi::PyMappingMethods> {
-        None
-    }
-}
-
-impl<'p, T> PyMappingProtocolImpl for T
-where
-    T: PyMappingProtocol<'p>,
-{
-    #[inline]
-    fn tp_as_mapping() -> Option<ffi::PyMappingMethods> {
-        let f = if let Some(df) = Self::mp_del_subscript() {
-            Some(df)
-        } else {
-            Self::mp_ass_subscript()
-        };
-
-        Some(ffi::PyMappingMethods {
-            mp_length: Self::mp_length(),
-            mp_subscript: Self::mp_subscript(),
-            mp_ass_subscript: f,
-        })
-    }
-}
-
-trait PyMappingLenProtocolImpl {
-    fn mp_length() -> Option<ffi::lenfunc>;
-}
-
-impl<'p, T> PyMappingLenProtocolImpl for T
-where
-    T: PyMappingProtocol<'p>,
-{
-    default fn mp_length() -> Option<ffi::lenfunc> {
-        None
-    }
-}
-
-impl<T> PyMappingLenProtocolImpl for T
-where
-    T: for<'p> PyMappingLenProtocol<'p>,
-{
-    #[inline]
-    fn mp_length() -> Option<ffi::lenfunc> {
-        py_len_func!(PyMappingLenProtocol, T::__len__)
-    }
-}
-
-trait PyMappingGetItemProtocolImpl {
-    fn mp_subscript() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyMappingGetItemProtocolImpl for T
-where
-    T: PyMappingProtocol<'p>,
-{
-    default fn mp_subscript() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyMappingGetItemProtocolImpl for T
-where
-    T: for<'p> PyMappingGetItemProtocol<'p>,
-{
-    #[inline]
-    fn mp_subscript() -> Option<ffi::binaryfunc> {
-        py_binary_func!(PyMappingGetItemProtocol, T::__getitem__)
-    }
-}
-
-trait PyMappingSetItemProtocolImpl {
-    fn mp_ass_subscript() -> Option<ffi::objobjargproc>;
-}
-
-impl<'p, T> PyMappingSetItemProtocolImpl for T
-where
-    T: PyMappingProtocol<'p>,
-{
-    default fn mp_ass_subscript() -> Option<ffi::objobjargproc> {
-        None
+impl ffi::PyMappingMethods {
+    pub fn set_length<T>(&mut self)
+    where
+        T: for<'p> PyMappingLenProtocol<'p>,
+    {
+        self.mp_length = py_len_func!(PyMappingLenProtocol, T::__len__);
     }
-}
-
-impl<T> PyMappingSetItemProtocolImpl for T
-where
-    T: for<'p> PyMappingSetItemProtocol<'p>,
-{
-    #[inline]
-    fn mp_ass_subscript() -> Option<ffi::objobjargproc> {
-        py_func_set!(PyMappingSetItemProtocol, T, __setitem__)
+    pub fn set_getitem<T>(&mut self)
+    where
+        T: for<'p> PyMappingGetItemProtocol<'p>,
+    {
+        self.mp_subscript = py_binary_func!(PyMappingGetItemProtocol, T::__getitem__);
     }
-}
-
-/// Returns `None` if PyMappingDelItemProtocol isn't implemented, otherwise dispatches to
-/// `DelSetItemDispatch`
-trait DeplItemDipatch {
-    fn mp_del_subscript() -> Option<ffi::objobjargproc>;
-}
-
-impl<'p, T> DeplItemDipatch for T
-where
-    T: PyMappingProtocol<'p>,
-{
-    default fn mp_del_subscript() -> Option<ffi::objobjargproc> {
-        None
+    pub fn set_setitem<T>(&mut self)
+    where
+        T: for<'p> PyMappingSetItemProtocol<'p>,
+    {
+        self.mp_ass_subscript = py_func_set!(PyMappingSetItemProtocol, T, __setitem__);
     }
-}
-
-/// Returns `py_func_set_del` if PyMappingSetItemProtocol is implemented, otherwise `py_func_del`
-trait DelSetItemDispatch: Sized + for<'p> PyMappingDelItemProtocol<'p> {
-    fn det_set_dispatch() -> Option<ffi::objobjargproc>;
-}
-
-impl<T> DelSetItemDispatch for T
-where
-    T: Sized + for<'p> PyMappingDelItemProtocol<'p>,
-{
-    default fn det_set_dispatch() -> Option<ffi::objobjargproc> {
-        py_func_del!(PyMappingDelItemProtocol, Self, __delitem__)
+    pub fn set_delitem<T>(&mut self)
+    where
+        T: for<'p> PyMappingDelItemProtocol<'p>,
+    {
+        self.mp_ass_subscript = py_func_del!(PyMappingDelItemProtocol, T, __delitem__);
     }
-}
-
-impl<T> DelSetItemDispatch for T
-where
-    T: for<'p> PyMappingSetItemProtocol<'p> + for<'p> PyMappingDelItemProtocol<'p>,
-{
-    fn det_set_dispatch() -> Option<ffi::objobjargproc> {
-        py_func_set_del!(
+    pub fn set_setdelitem<T>(&mut self)
+    where
+        T: for<'p> PyMappingSetItemProtocol<'p> + for<'p> PyMappingDelItemProtocol<'p>,
+    {
+        self.mp_ass_subscript = py_func_set_del!(
             PyMappingSetItemProtocol,
             PyMappingDelItemProtocol,
             T,
             __setitem__,
             __delitem__
-        )
-    }
-}
-
-impl<T> DeplItemDipatch for T
-where
-    T: Sized + for<'p> PyMappingDelItemProtocol<'p>,
-{
-    fn mp_del_subscript() -> Option<ffi::objobjargproc> {
-        <T as DelSetItemDispatch>::det_set_dispatch()
+        );
     }
 }
diff --git a/src/class/mod.rs b/src/class/mod.rs
index df828ecb75c..fd37a51fd17 100644
--- a/src/class/mod.rs
+++ b/src/class/mod.rs
@@ -14,6 +14,7 @@ pub mod iter;
 pub mod mapping;
 pub mod methods;
 pub mod number;
+pub mod proto_methods;
 pub mod pyasync;
 pub mod sequence;
 
diff --git a/src/class/number.rs b/src/class/number.rs
index ab5270b4ddb..7e6e6a107a1 100644
--- a/src/class/number.rs
+++ b/src/class/number.rs
@@ -3,7 +3,6 @@
 //! Python Number Interface
 //! Trait and support implementation for implementing number protocol
 
-use crate::class::basic::PyObjectProtocolImpl;
 use crate::err::PyResult;
 use crate::{ffi, FromPyObject, IntoPy, PyClass, PyObject};
 
@@ -617,1124 +616,298 @@ pub trait PyNumberIndexProtocol<'p>: PyNumberProtocol<'p> {
 }
 
 #[doc(hidden)]
-pub trait PyNumberProtocolImpl: PyObjectProtocolImpl {
-    fn tp_as_number() -> Option<ffi::PyNumberMethods>;
-}
-
-impl<'p, T> PyNumberProtocolImpl for T {
-    default fn tp_as_number() -> Option<ffi::PyNumberMethods> {
-        if let Some(nb_bool) = <Self as PyObjectProtocolImpl>::nb_bool_fn() {
-            let meth = ffi::PyNumberMethods {
-                nb_bool: Some(nb_bool),
-                ..ffi::PyNumberMethods_INIT
-            };
-            Some(meth)
-        } else {
-            None
-        }
-    }
-}
-
-impl<'p, T> PyNumberProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    fn tp_as_number() -> Option<ffi::PyNumberMethods> {
-        Some(ffi::PyNumberMethods {
-            nb_add: Self::nb_add().or_else(Self::nb_add_fallback),
-            nb_subtract: Self::nb_subtract().or_else(Self::nb_sub_fallback),
-            nb_multiply: Self::nb_multiply().or_else(Self::nb_mul_fallback),
-            nb_remainder: Self::nb_remainder(),
-            nb_divmod: Self::nb_divmod().or_else(Self::nb_divmod_fallback),
-            nb_power: Self::nb_power().or_else(Self::nb_pow_fallback),
-            nb_negative: Self::nb_negative(),
-            nb_positive: Self::nb_positive(),
-            nb_absolute: Self::nb_absolute(),
-            nb_bool: <Self as PyObjectProtocolImpl>::nb_bool_fn(),
-            nb_invert: Self::nb_invert(),
-            nb_lshift: Self::nb_lshift().or_else(Self::nb_lshift_fallback),
-            nb_rshift: Self::nb_rshift().or_else(Self::nb_rshift_fallback),
-            nb_and: Self::nb_and().or_else(Self::nb_and_fallback),
-            nb_xor: Self::nb_xor().or_else(Self::nb_xor_fallback),
-            nb_or: Self::nb_or().or_else(Self::nb_or_fallback),
-            nb_int: Self::nb_int(),
-            nb_reserved: ::std::ptr::null_mut(),
-            nb_float: Self::nb_float(),
-            nb_inplace_add: Self::nb_inplace_add(),
-            nb_inplace_subtract: Self::nb_inplace_subtract(),
-            nb_inplace_multiply: Self::nb_inplace_multiply(),
-            nb_inplace_remainder: Self::nb_inplace_remainder(),
-            nb_inplace_power: Self::nb_inplace_power(),
-            nb_inplace_lshift: Self::nb_inplace_lshift(),
-            nb_inplace_rshift: Self::nb_inplace_rshift(),
-            nb_inplace_and: Self::nb_inplace_and(),
-            nb_inplace_xor: Self::nb_inplace_xor(),
-            nb_inplace_or: Self::nb_inplace_or(),
-            nb_floor_divide: Self::nb_floor_divide().or_else(Self::nb_floordiv_fallback),
-            nb_true_divide: Self::nb_true_divide().or_else(Self::nb_truediv_fallback),
-            nb_inplace_floor_divide: Self::nb_inplace_floor_divide(),
-            nb_inplace_true_divide: Self::nb_inplace_true_divide(),
-            nb_index: Self::nb_index(),
-            nb_matrix_multiply: Self::nb_matrix_multiply().or_else(Self::nb_matmul_fallback),
-            nb_inplace_matrix_multiply: Self::nb_inplace_matrix_multiply(),
-        })
-    }
-}
-
-trait PyNumberAddProtocolImpl {
-    fn nb_add() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberAddProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_add() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberAddProtocolImpl for T
-where
-    T: for<'p> PyNumberAddProtocol<'p>,
-{
-    fn nb_add() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberAddProtocol, T::__add__)
-    }
-}
-
-trait PyNumberSubProtocolImpl {
-    fn nb_subtract() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberSubProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_subtract() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberSubProtocolImpl for T
-where
-    T: for<'p> PyNumberSubProtocol<'p>,
-{
-    fn nb_subtract() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberSubProtocol, T::__sub__)
-    }
-}
-
-trait PyNumberMulProtocolImpl {
-    fn nb_multiply() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberMulProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_multiply() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberMulProtocolImpl for T
-where
-    T: for<'p> PyNumberMulProtocol<'p>,
-{
-    fn nb_multiply() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberMulProtocol, T::__mul__)
-    }
-}
-
-trait PyNumberMatmulProtocolImpl {
-    fn nb_matrix_multiply() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberMatmulProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_matrix_multiply() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberMatmulProtocolImpl for T
-where
-    T: for<'p> PyNumberMatmulProtocol<'p>,
-{
-    fn nb_matrix_multiply() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberMatmulProtocol, T::__matmul__)
-    }
-}
-
-trait PyNumberTruedivProtocolImpl {
-    fn nb_true_divide() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberTruedivProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_true_divide() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberTruedivProtocolImpl for T
-where
-    T: for<'p> PyNumberTruedivProtocol<'p>,
-{
-    fn nb_true_divide() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberTruedivProtocol, T::__truediv__)
-    }
-}
-
-trait PyNumberFloordivProtocolImpl {
-    fn nb_floor_divide() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberFloordivProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_floor_divide() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberFloordivProtocolImpl for T
-where
-    T: for<'p> PyNumberFloordivProtocol<'p>,
-{
-    fn nb_floor_divide() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberFloordivProtocol, T::__floordiv__)
-    }
-}
-
-trait PyNumberModProtocolImpl {
-    fn nb_remainder() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberModProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_remainder() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberModProtocolImpl for T
-where
-    T: for<'p> PyNumberModProtocol<'p>,
-{
-    fn nb_remainder() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberModProtocol, T::__mod__)
-    }
-}
-
-trait PyNumberDivmodProtocolImpl {
-    fn nb_divmod() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberDivmodProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_divmod() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberDivmodProtocolImpl for T
-where
-    T: for<'p> PyNumberDivmodProtocol<'p>,
-{
-    fn nb_divmod() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberDivmodProtocol, T::__divmod__)
-    }
-}
-
-trait PyNumberPowProtocolImpl {
-    fn nb_power() -> Option<ffi::ternaryfunc>;
-}
-
-impl<'p, T> PyNumberPowProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_power() -> Option<ffi::ternaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberPowProtocolImpl for T
-where
-    T: for<'p> PyNumberPowProtocol<'p>,
-{
-    fn nb_power() -> Option<ffi::ternaryfunc> {
-        py_ternary_num_func!(PyNumberPowProtocol, T::__pow__)
-    }
-}
-
-trait PyNumberLShiftProtocolImpl {
-    fn nb_lshift() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberLShiftProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_lshift() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberLShiftProtocolImpl for T
-where
-    T: for<'p> PyNumberLShiftProtocol<'p>,
-{
-    fn nb_lshift() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberLShiftProtocol, T::__lshift__)
-    }
-}
-
-trait PyNumberRShiftProtocolImpl {
-    fn nb_rshift() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberRShiftProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_rshift() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberRShiftProtocolImpl for T
-where
-    T: for<'p> PyNumberRShiftProtocol<'p>,
-{
-    fn nb_rshift() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberRShiftProtocol, T::__rshift__)
-    }
-}
-
-trait PyNumberAndProtocolImpl {
-    fn nb_and() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberAndProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_and() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberAndProtocolImpl for T
-where
-    T: for<'p> PyNumberAndProtocol<'p>,
-{
-    fn nb_and() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberAndProtocol, T::__and__)
-    }
-}
-
-trait PyNumberXorProtocolImpl {
-    fn nb_xor() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberXorProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_xor() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberXorProtocolImpl for T
-where
-    T: for<'p> PyNumberXorProtocol<'p>,
-{
-    fn nb_xor() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberXorProtocol, T::__xor__)
+impl ffi::PyNumberMethods {
+    pub(crate) fn from_nb_bool(nb_bool: ffi::inquiry) -> *mut Self {
+        let mut nm = ffi::PyNumberMethods_INIT;
+        nm.nb_bool = Some(nb_bool);
+        Box::into_raw(Box::new(nm))
     }
-}
-
-trait PyNumberOrProtocolImpl {
-    fn nb_or() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberOrProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_or() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberOrProtocolImpl for T
-where
-    T: for<'p> PyNumberOrProtocol<'p>,
-{
-    fn nb_or() -> Option<ffi::binaryfunc> {
-        py_binary_num_func!(PyNumberOrProtocol, T::__or__)
-    }
-}
-
-trait PyNumberIAddProtocolImpl {
-    fn nb_inplace_add() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberIAddProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_add() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberIAddProtocolImpl for T
-where
-    T: for<'p> PyNumberIAddProtocol<'p>,
-{
-    fn nb_inplace_add() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberIAddProtocol, T::__iadd__)
-    }
-}
-
-trait PyNumberISubProtocolImpl {
-    fn nb_inplace_subtract() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberISubProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_subtract() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberISubProtocolImpl for T
-where
-    T: for<'p> PyNumberISubProtocol<'p>,
-{
-    fn nb_inplace_subtract() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberISubProtocol, T::__isub__)
-    }
-}
-
-trait PyNumberIMulProtocolImpl {
-    fn nb_inplace_multiply() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberIMulProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_multiply() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberIMulProtocolImpl for T
-where
-    T: for<'p> PyNumberIMulProtocol<'p>,
-{
-    fn nb_inplace_multiply() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberIMulProtocol, T::__imul__)
-    }
-}
-
-trait PyNumberIMatmulProtocolImpl {
-    fn nb_inplace_matrix_multiply() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberIMatmulProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_matrix_multiply() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberIMatmulProtocolImpl for T
-where
-    T: for<'p> PyNumberIMatmulProtocol<'p>,
-{
-    fn nb_inplace_matrix_multiply() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberIMatmulProtocol, T::__imatmul__)
-    }
-}
-
-trait PyNumberITruedivProtocolImpl {
-    fn nb_inplace_true_divide() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberITruedivProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_true_divide() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberITruedivProtocolImpl for T
-where
-    T: for<'p> PyNumberITruedivProtocol<'p>,
-{
-    fn nb_inplace_true_divide() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberITruedivProtocol, T::__itruediv__)
-    }
-}
-
-trait PyNumberIFloordivProtocolImpl {
-    fn nb_inplace_floor_divide() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberIFloordivProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_floor_divide() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberIFloordivProtocolImpl for T
-where
-    T: for<'p> PyNumberIFloordivProtocol<'p>,
-{
-    fn nb_inplace_floor_divide() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberIFloordivProtocol, T::__ifloordiv__)
-    }
-}
-
-trait PyNumberIModProtocolImpl {
-    fn nb_inplace_remainder() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberIModProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_remainder() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberIModProtocolImpl for T
-where
-    T: for<'p> PyNumberIModProtocol<'p>,
-{
-    fn nb_inplace_remainder() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberIModProtocol, T::__imod__)
-    }
-}
-
-trait PyNumberIPowProtocolImpl {
-    fn nb_inplace_power() -> Option<ffi::ternaryfunc>;
-}
-
-impl<'p, T> PyNumberIPowProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_power() -> Option<ffi::ternaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberIPowProtocolImpl for T
-where
-    T: for<'p> PyNumberIPowProtocol<'p>,
-{
-    fn nb_inplace_power() -> Option<ffi::ternaryfunc> {
-        py_dummy_ternary_self_func!(PyNumberIPowProtocol, T::__ipow__)
-    }
-}
-
-trait PyNumberILShiftProtocolImpl {
-    fn nb_inplace_lshift() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberILShiftProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_lshift() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberILShiftProtocolImpl for T
-where
-    T: for<'p> PyNumberILShiftProtocol<'p>,
-{
-    fn nb_inplace_lshift() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberILShiftProtocol, T::__ilshift__)
-    }
-}
-
-trait PyNumberIRShiftProtocolImpl {
-    fn nb_inplace_rshift() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberIRShiftProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_rshift() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberIRShiftProtocolImpl for T
-where
-    T: for<'p> PyNumberIRShiftProtocol<'p>,
-{
-    fn nb_inplace_rshift() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberIRShiftProtocol, T::__irshift__)
-    }
-}
-
-trait PyNumberIAndProtocolImpl {
-    fn nb_inplace_and() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberIAndProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_and() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PyNumberIAndProtocolImpl for T
-where
-    T: for<'p> PyNumberIAndProtocol<'p>,
-{
-    fn nb_inplace_and() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberIAndProtocol, T::__iand__)
+    pub fn set_add<T>(&mut self)
+    where
+        T: for<'p> PyNumberAddProtocol<'p>,
+    {
+        self.nb_add = py_binary_num_func!(PyNumberAddProtocol, T::__add__);
     }
-}
-
-trait PyNumberIXorProtocolImpl {
-    fn nb_inplace_xor() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberIXorProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_xor() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_radd<T>(&mut self)
+    where
+        T: for<'p> PyNumberRAddProtocol<'p>,
+    {
+        self.nb_add = py_binary_reversed_num_func!(PyNumberRAddProtocol, T::__radd__);
     }
-}
-
-impl<T> PyNumberIXorProtocolImpl for T
-where
-    T: for<'p> PyNumberIXorProtocol<'p>,
-{
-    fn nb_inplace_xor() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberIXorProtocol, T::__ixor__)
+    pub fn set_sub<T>(&mut self)
+    where
+        T: for<'p> PyNumberSubProtocol<'p>,
+    {
+        self.nb_subtract = py_binary_num_func!(PyNumberSubProtocol, T::__sub__);
     }
-}
-
-trait PyNumberIOrProtocolImpl {
-    fn nb_inplace_or() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberIOrProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_inplace_or() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_rsub<T>(&mut self)
+    where
+        T: for<'p> PyNumberRSubProtocol<'p>,
+    {
+        self.nb_subtract = py_binary_reversed_num_func!(PyNumberRSubProtocol, T::__rsub__);
     }
-}
-
-impl<T> PyNumberIOrProtocolImpl for T
-where
-    T: for<'p> PyNumberIOrProtocol<'p>,
-{
-    fn nb_inplace_or() -> Option<ffi::binaryfunc> {
-        py_binary_self_func!(PyNumberIOrProtocol, T::__ior__)
+    pub fn set_mul<T>(&mut self)
+    where
+        T: for<'p> PyNumberMulProtocol<'p>,
+    {
+        self.nb_multiply = py_binary_num_func!(PyNumberMulProtocol, T::__mul__);
     }
-}
-
-// Fallback trait for nb_add
-trait PyNumberAddFallback {
-    fn nb_add_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberAddFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_add_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_rmul<T>(&mut self)
+    where
+        T: for<'p> PyNumberRMulProtocol<'p>,
+    {
+        self.nb_multiply = py_binary_reversed_num_func!(PyNumberRMulProtocol, T::__rmul__);
     }
-}
-
-impl<T> PyNumberAddFallback for T
-where
-    T: for<'p> PyNumberRAddProtocol<'p>,
-{
-    fn nb_add_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRAddProtocol, T::__radd__)
+    pub fn set_mod<T>(&mut self)
+    where
+        T: for<'p> PyNumberModProtocol<'p>,
+    {
+        self.nb_remainder = py_binary_num_func!(PyNumberModProtocol, T::__mod__);
     }
-}
-
-trait PyNumberSubFallback {
-    fn nb_sub_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberSubFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_sub_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_divmod<T>(&mut self)
+    where
+        T: for<'p> PyNumberDivmodProtocol<'p>,
+    {
+        self.nb_divmod = py_binary_num_func!(PyNumberDivmodProtocol, T::__divmod__);
     }
-}
-
-impl<T> PyNumberSubFallback for T
-where
-    T: for<'p> PyNumberRSubProtocol<'p>,
-{
-    fn nb_sub_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRSubProtocol, T::__rsub__)
+    pub fn set_rdivmod<T>(&mut self)
+    where
+        T: for<'p> PyNumberRDivmodProtocol<'p>,
+    {
+        self.nb_divmod = py_binary_reversed_num_func!(PyNumberRDivmodProtocol, T::__rdivmod__);
     }
-}
-
-trait PyNumberMulFallback {
-    fn nb_mul_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberMulFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_mul_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_pow<T>(&mut self)
+    where
+        T: for<'p> PyNumberPowProtocol<'p>,
+    {
+        self.nb_power = py_ternary_num_func!(PyNumberPowProtocol, T::__pow__);
     }
-}
-
-impl<T> PyNumberMulFallback for T
-where
-    T: for<'p> PyNumberRMulProtocol<'p>,
-{
-    fn nb_mul_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRMulProtocol, T::__rmul__)
+    pub fn set_rpow<T>(&mut self)
+    where
+        T: for<'p> PyNumberRPowProtocol<'p>,
+    {
+        self.nb_power = py_ternary_reversed_num_func!(PyNumberRPowProtocol, T::__rpow__);
     }
-}
-
-trait PyNumberMatmulFallback {
-    fn nb_matmul_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberMatmulFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_matmul_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_neg<T>(&mut self)
+    where
+        T: for<'p> PyNumberNegProtocol<'p>,
+    {
+        self.nb_negative = py_unary_func!(PyNumberNegProtocol, T::__neg__);
     }
-}
-
-impl<T> PyNumberMatmulFallback for T
-where
-    T: for<'p> PyNumberRMatmulProtocol<'p>,
-{
-    fn nb_matmul_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRMatmulProtocol, T::__rmatmul__)
+    pub fn set_pos<T>(&mut self)
+    where
+        T: for<'p> PyNumberPosProtocol<'p>,
+    {
+        self.nb_positive = py_unary_func!(PyNumberPosProtocol, T::__pos__);
     }
-}
-
-trait PyNumberTruedivFallback {
-    fn nb_truediv_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberTruedivFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_truediv_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_abs<T>(&mut self)
+    where
+        T: for<'p> PyNumberAbsProtocol<'p>,
+    {
+        self.nb_absolute = py_unary_func!(PyNumberAbsProtocol, T::__abs__);
     }
-}
-
-impl<T> PyNumberTruedivFallback for T
-where
-    T: for<'p> PyNumberRTruedivProtocol<'p>,
-{
-    fn nb_truediv_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRTruedivProtocol, T::__rtruediv__)
+    pub fn set_invert<T>(&mut self)
+    where
+        T: for<'p> PyNumberInvertProtocol<'p>,
+    {
+        self.nb_invert = py_unary_func!(PyNumberInvertProtocol, T::__invert__);
     }
-}
-
-trait PyNumberFloordivFallback {
-    fn nb_floordiv_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberFloordivFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_floordiv_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_lshift<T>(&mut self)
+    where
+        T: for<'p> PyNumberLShiftProtocol<'p>,
+    {
+        self.nb_lshift = py_binary_num_func!(PyNumberLShiftProtocol, T::__lshift__);
     }
-}
-
-impl<T> PyNumberFloordivFallback for T
-where
-    T: for<'p> PyNumberRFloordivProtocol<'p>,
-{
-    fn nb_floordiv_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRFloordivProtocol, T::__rfloordiv__)
+    pub fn set_rlshift<T>(&mut self)
+    where
+        T: for<'p> PyNumberRLShiftProtocol<'p>,
+    {
+        self.nb_lshift = py_binary_reversed_num_func!(PyNumberRLShiftProtocol, T::__rlshift__);
     }
-}
-
-trait PyNumberModFallback {
-    fn nb_mod_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberModFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_mod_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_rshift<T>(&mut self)
+    where
+        T: for<'p> PyNumberRShiftProtocol<'p>,
+    {
+        self.nb_rshift = py_binary_num_func!(PyNumberRShiftProtocol, T::__rshift__);
     }
-}
-
-impl<T> PyNumberModFallback for T
-where
-    T: for<'p> PyNumberRModProtocol<'p>,
-{
-    fn nb_mod_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRModProtocol, T::__rmod__)
+    pub fn set_rrshift<T>(&mut self)
+    where
+        T: for<'p> PyNumberRRShiftProtocol<'p>,
+    {
+        self.nb_rshift = py_binary_reversed_num_func!(PyNumberRRShiftProtocol, T::__rrshift__);
     }
-}
-
-trait PyNumberDivmodFallback {
-    fn nb_divmod_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberDivmodFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_divmod_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_and<T>(&mut self)
+    where
+        T: for<'p> PyNumberAndProtocol<'p>,
+    {
+        self.nb_and = py_binary_num_func!(PyNumberAndProtocol, T::__and__);
     }
-}
-
-impl<T> PyNumberDivmodFallback for T
-where
-    T: for<'p> PyNumberRDivmodProtocol<'p>,
-{
-    fn nb_divmod_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRDivmodProtocol, T::__rdivmod__)
+    pub fn set_rand<T>(&mut self)
+    where
+        T: for<'p> PyNumberRAndProtocol<'p>,
+    {
+        self.nb_and = py_binary_reversed_num_func!(PyNumberRAndProtocol, T::__rand__);
     }
-}
-
-trait PyNumberPowFallback {
-    fn nb_pow_fallback() -> Option<ffi::ternaryfunc>;
-}
-
-impl<'p, T> PyNumberPowFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_pow_fallback() -> Option<ffi::ternaryfunc> {
-        None
+    pub fn set_xor<T>(&mut self)
+    where
+        T: for<'p> PyNumberXorProtocol<'p>,
+    {
+        self.nb_xor = py_binary_num_func!(PyNumberXorProtocol, T::__xor__);
     }
-}
-
-impl<T> PyNumberPowFallback for T
-where
-    T: for<'p> PyNumberRPowProtocol<'p>,
-{
-    fn nb_pow_fallback() -> Option<ffi::ternaryfunc> {
-        py_ternary_reverse_num_func!(PyNumberRPowProtocol, T::__rpow__)
+    pub fn set_rxor<T>(&mut self)
+    where
+        T: for<'p> PyNumberRXorProtocol<'p>,
+    {
+        self.nb_xor = py_binary_reversed_num_func!(PyNumberRXorProtocol, T::__rxor__);
     }
-}
-
-trait PyNumberLShiftFallback {
-    fn nb_lshift_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberLShiftFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_lshift_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_or<T>(&mut self)
+    where
+        T: for<'p> PyNumberOrProtocol<'p>,
+    {
+        self.nb_or = py_binary_num_func!(PyNumberOrProtocol, T::__or__);
     }
-}
-
-impl<T> PyNumberLShiftFallback for T
-where
-    T: for<'p> PyNumberRLShiftProtocol<'p>,
-{
-    fn nb_lshift_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRLShiftProtocol, T::__rlshift__)
+    pub fn set_ror<T>(&mut self)
+    where
+        T: for<'p> PyNumberROrProtocol<'p>,
+    {
+        self.nb_or = py_binary_reversed_num_func!(PyNumberROrProtocol, T::__ror__);
     }
-}
-
-trait PyNumberRRshiftFallback {
-    fn nb_rshift_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberRRshiftFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_rshift_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_int<T>(&mut self)
+    where
+        T: for<'p> PyNumberIntProtocol<'p>,
+    {
+        self.nb_int = py_unary_func!(PyNumberIntProtocol, T::__int__);
     }
-}
-
-impl<T> PyNumberRRshiftFallback for T
-where
-    T: for<'p> PyNumberRRShiftProtocol<'p>,
-{
-    fn nb_rshift_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRRShiftProtocol, T::__rrshift__)
+    pub fn set_float<T>(&mut self)
+    where
+        T: for<'p> PyNumberFloatProtocol<'p>,
+    {
+        self.nb_float = py_unary_func!(PyNumberFloatProtocol, T::__float__);
     }
-}
-
-trait PyNumberAndFallback {
-    fn nb_and_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberAndFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_and_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_iadd<T>(&mut self)
+    where
+        T: for<'p> PyNumberIAddProtocol<'p>,
+    {
+        self.nb_inplace_add = py_binary_self_func!(PyNumberIAddProtocol, T::__iadd__);
     }
-}
-
-impl<T> PyNumberAndFallback for T
-where
-    T: for<'p> PyNumberRAndProtocol<'p>,
-{
-    fn nb_and_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRAndProtocol, T::__rand__)
+    pub fn set_isub<T>(&mut self)
+    where
+        T: for<'p> PyNumberISubProtocol<'p>,
+    {
+        self.nb_inplace_subtract = py_binary_self_func!(PyNumberISubProtocol, T::__isub__);
     }
-}
-
-trait PyNumberXorFallback {
-    fn nb_xor_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberXorFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_xor_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_imul<T>(&mut self)
+    where
+        T: for<'p> PyNumberIMulProtocol<'p>,
+    {
+        self.nb_inplace_multiply = py_binary_self_func!(PyNumberIMulProtocol, T::__imul__);
     }
-}
-
-impl<T> PyNumberXorFallback for T
-where
-    T: for<'p> PyNumberRXorProtocol<'p>,
-{
-    fn nb_xor_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberRXorProtocol, T::__rxor__)
+    pub fn set_imod<T>(&mut self)
+    where
+        T: for<'p> PyNumberIModProtocol<'p>,
+    {
+        self.nb_inplace_remainder = py_binary_self_func!(PyNumberIModProtocol, T::__imod__);
     }
-}
-
-trait PyNumberOrFallback {
-    fn nb_or_fallback() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PyNumberOrFallback for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_or_fallback() -> Option<ffi::binaryfunc> {
-        None
+    pub fn set_ipow<T>(&mut self)
+    where
+        T: for<'p> PyNumberIPowProtocol<'p>,
+    {
+        self.nb_inplace_power = py_dummy_ternary_self_func!(PyNumberIPowProtocol, T::__ipow__)
     }
-}
-
-impl<T> PyNumberOrFallback for T
-where
-    T: for<'p> PyNumberROrProtocol<'p>,
-{
-    fn nb_or_fallback() -> Option<ffi::binaryfunc> {
-        py_binary_reverse_num_func!(PyNumberROrProtocol, T::__ror__)
+    pub fn set_ilshift<T>(&mut self)
+    where
+        T: for<'p> PyNumberILShiftProtocol<'p>,
+    {
+        self.nb_inplace_lshift = py_binary_self_func!(PyNumberILShiftProtocol, T::__ilshift__);
     }
-}
-
-trait PyNumberNegProtocolImpl {
-    fn nb_negative() -> Option<ffi::unaryfunc>;
-}
-
-impl<'p, T> PyNumberNegProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_negative() -> Option<ffi::unaryfunc> {
-        None
+    pub fn set_irshift<T>(&mut self)
+    where
+        T: for<'p> PyNumberIRShiftProtocol<'p>,
+    {
+        self.nb_inplace_rshift = py_binary_self_func!(PyNumberIRShiftProtocol, T::__irshift__);
     }
-}
-
-impl<T> PyNumberNegProtocolImpl for T
-where
-    T: for<'p> PyNumberNegProtocol<'p>,
-{
-    #[inline]
-    fn nb_negative() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyNumberNegProtocol, T::__neg__)
+    pub fn set_iand<T>(&mut self)
+    where
+        T: for<'p> PyNumberIAndProtocol<'p>,
+    {
+        self.nb_inplace_and = py_binary_self_func!(PyNumberIAndProtocol, T::__iand__);
     }
-}
-
-trait PyNumberPosProtocolImpl {
-    fn nb_positive() -> Option<ffi::unaryfunc>;
-}
-
-impl<'p, T> PyNumberPosProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_positive() -> Option<ffi::unaryfunc> {
-        None
+    pub fn set_ixor<T>(&mut self)
+    where
+        T: for<'p> PyNumberIXorProtocol<'p>,
+    {
+        self.nb_inplace_xor = py_binary_self_func!(PyNumberIXorProtocol, T::__ixor__);
     }
-}
-
-impl<T> PyNumberPosProtocolImpl for T
-where
-    T: for<'p> PyNumberPosProtocol<'p>,
-{
-    fn nb_positive() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyNumberPosProtocol, T::__pos__)
+    pub fn set_ior<T>(&mut self)
+    where
+        T: for<'p> PyNumberIOrProtocol<'p>,
+    {
+        self.nb_inplace_or = py_binary_self_func!(PyNumberIOrProtocol, T::__ior__);
     }
-}
-
-trait PyNumberAbsProtocolImpl {
-    fn nb_absolute() -> Option<ffi::unaryfunc>;
-}
-
-impl<'p, T> PyNumberAbsProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_absolute() -> Option<ffi::unaryfunc> {
-        None
+    pub fn set_floordiv<T>(&mut self)
+    where
+        T: for<'p> PyNumberFloordivProtocol<'p>,
+    {
+        self.nb_floor_divide = py_binary_num_func!(PyNumberFloordivProtocol, T::__floordiv__);
     }
-}
-
-impl<T> PyNumberAbsProtocolImpl for T
-where
-    T: for<'p> PyNumberAbsProtocol<'p>,
-{
-    fn nb_absolute() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyNumberAbsProtocol, T::__abs__)
+    pub fn set_rfloordiv<T>(&mut self)
+    where
+        T: for<'p> PyNumberRFloordivProtocol<'p>,
+    {
+        self.nb_floor_divide =
+            py_binary_reversed_num_func!(PyNumberRFloordivProtocol, T::__rfloordiv__);
     }
-}
-
-trait PyNumberInvertProtocolImpl {
-    fn nb_invert() -> Option<ffi::unaryfunc>;
-}
-
-impl<'p, T> PyNumberInvertProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_invert() -> Option<ffi::unaryfunc> {
-        None
+    pub fn set_truediv<T>(&mut self)
+    where
+        T: for<'p> PyNumberTruedivProtocol<'p>,
+    {
+        self.nb_true_divide = py_binary_num_func!(PyNumberTruedivProtocol, T::__truediv__);
     }
-}
-
-impl<T> PyNumberInvertProtocolImpl for T
-where
-    T: for<'p> PyNumberInvertProtocol<'p>,
-{
-    fn nb_invert() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyNumberInvertProtocol, T::__invert__)
+    pub fn set_rtruediv<T>(&mut self)
+    where
+        T: for<'p> PyNumberRTruedivProtocol<'p>,
+    {
+        self.nb_true_divide =
+            py_binary_reversed_num_func!(PyNumberRTruedivProtocol, T::__rtruediv__);
     }
-}
-
-trait PyNumberIntProtocolImpl {
-    fn nb_int() -> Option<ffi::unaryfunc>;
-}
-
-impl<'p, T> PyNumberIntProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_int() -> Option<ffi::unaryfunc> {
-        None
+    pub fn set_ifloordiv<T>(&mut self)
+    where
+        T: for<'p> PyNumberIFloordivProtocol<'p>,
+    {
+        self.nb_inplace_floor_divide =
+            py_binary_self_func!(PyNumberIFloordivProtocol, T::__ifloordiv__);
     }
-}
-
-impl<T> PyNumberIntProtocolImpl for T
-where
-    T: for<'p> PyNumberIntProtocol<'p>,
-{
-    fn nb_int() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyNumberIntProtocol, T::__int__)
+    pub fn set_itruediv<T>(&mut self)
+    where
+        T: for<'p> PyNumberITruedivProtocol<'p>,
+    {
+        self.nb_inplace_true_divide =
+            py_binary_self_func!(PyNumberITruedivProtocol, T::__itruediv__);
     }
-}
-
-trait PyNumberFloatProtocolImpl {
-    fn nb_float() -> Option<ffi::unaryfunc>;
-}
-
-impl<'p, T> PyNumberFloatProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_float() -> Option<ffi::unaryfunc> {
-        None
+    pub fn set_index<T>(&mut self)
+    where
+        T: for<'p> PyNumberIndexProtocol<'p>,
+    {
+        self.nb_index = py_unary_func!(PyNumberIndexProtocol, T::__index__);
     }
-}
-
-impl<T> PyNumberFloatProtocolImpl for T
-where
-    T: for<'p> PyNumberFloatProtocol<'p>,
-{
-    fn nb_float() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyNumberFloatProtocol, T::__float__)
+    pub fn set_matmul<T>(&mut self)
+    where
+        T: for<'p> PyNumberMatmulProtocol<'p>,
+    {
+        self.nb_matrix_multiply = py_binary_num_func!(PyNumberMatmulProtocol, T::__matmul__);
     }
-}
-
-trait PyNumberIndexProtocolImpl {
-    fn nb_index() -> Option<ffi::unaryfunc>;
-}
-
-impl<'p, T> PyNumberIndexProtocolImpl for T
-where
-    T: PyNumberProtocol<'p>,
-{
-    default fn nb_index() -> Option<ffi::unaryfunc> {
-        None
+    pub fn set_rmatmul<T>(&mut self)
+    where
+        T: for<'p> PyNumberRMatmulProtocol<'p>,
+    {
+        self.nb_matrix_multiply =
+            py_binary_reversed_num_func!(PyNumberRMatmulProtocol, T::__rmatmul__);
     }
-}
-
-impl<T> PyNumberIndexProtocolImpl for T
-where
-    T: for<'p> PyNumberIndexProtocol<'p>,
-{
-    fn nb_index() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyNumberIndexProtocol, T::__index__)
+    pub fn set_imatmul<T>(&mut self)
+    where
+        T: for<'p> PyNumberIMatmulProtocol<'p>,
+    {
+        self.nb_inplace_matrix_multiply =
+            py_binary_self_func!(PyNumberIMatmulProtocol, T::__imatmul__);
     }
 }
diff --git a/src/class/proto_methods.rs b/src/class/proto_methods.rs
new file mode 100644
index 00000000000..76e75d9f440
--- /dev/null
+++ b/src/class/proto_methods.rs
@@ -0,0 +1,154 @@
+use crate::class::{
+    basic::PyObjectMethods, descr::PyDescrMethods, gc::PyGCMethods, iter::PyIterMethods,
+};
+use crate::ffi::{
+    PyAsyncMethods, PyBufferProcs, PyMappingMethods, PyNumberMethods, PySequenceMethods,
+};
+use std::{
+    ptr::{self, NonNull},
+    sync::atomic::{AtomicPtr, Ordering},
+};
+
+/// Defines all method tables we need for object protocols.
+// Note(kngwyu): default implementations are for rust-numpy. Please don't remove them.
+pub trait PyProtoMethods {
+    fn async_methods() -> Option<NonNull<PyAsyncMethods>> {
+        None
+    }
+    fn basic_methods() -> Option<NonNull<PyObjectMethods>> {
+        None
+    }
+    fn buffer_methods() -> Option<NonNull<PyBufferProcs>> {
+        None
+    }
+    fn descr_methods() -> Option<NonNull<PyDescrMethods>> {
+        None
+    }
+    fn gc_methods() -> Option<NonNull<PyGCMethods>> {
+        None
+    }
+    fn mapping_methods() -> Option<NonNull<PyMappingMethods>> {
+        None
+    }
+    fn number_methods() -> Option<NonNull<PyNumberMethods>> {
+        None
+    }
+    fn iter_methods() -> Option<NonNull<PyIterMethods>> {
+        None
+    }
+    fn sequence_methods() -> Option<NonNull<PySequenceMethods>> {
+        None
+    }
+}
+
+/// Indicates that a type has a protocol registry. Implemented by `#[pyclass]`.
+#[doc(hidden)]
+pub trait HasProtoRegistry: Sized + 'static {
+    fn registry() -> &'static PyProtoRegistry;
+}
+
+impl<T: HasProtoRegistry> PyProtoMethods for T {
+    fn async_methods() -> Option<NonNull<PyAsyncMethods>> {
+        NonNull::new(Self::registry().async_methods.load(Ordering::Relaxed))
+    }
+    fn basic_methods() -> Option<NonNull<PyObjectMethods>> {
+        NonNull::new(Self::registry().basic_methods.load(Ordering::Relaxed))
+    }
+    fn buffer_methods() -> Option<NonNull<PyBufferProcs>> {
+        NonNull::new(Self::registry().buffer_methods.load(Ordering::Relaxed))
+    }
+    fn descr_methods() -> Option<NonNull<PyDescrMethods>> {
+        NonNull::new(Self::registry().descr_methods.load(Ordering::Relaxed))
+    }
+    fn gc_methods() -> Option<NonNull<PyGCMethods>> {
+        NonNull::new(Self::registry().gc_methods.load(Ordering::Relaxed))
+    }
+    fn mapping_methods() -> Option<NonNull<PyMappingMethods>> {
+        NonNull::new(Self::registry().mapping_methods.load(Ordering::Relaxed))
+    }
+    fn number_methods() -> Option<NonNull<PyNumberMethods>> {
+        NonNull::new(Self::registry().number_methods.load(Ordering::Relaxed))
+    }
+    fn iter_methods() -> Option<NonNull<PyIterMethods>> {
+        NonNull::new(Self::registry().iter_methods.load(Ordering::Relaxed))
+    }
+    fn sequence_methods() -> Option<NonNull<PySequenceMethods>> {
+        NonNull::new(Self::registry().sequence_methods.load(Ordering::Relaxed))
+    }
+}
+
+/// Stores all method protocols.
+/// Used in the proc-macro code as a static variable.
+#[doc(hidden)]
+pub struct PyProtoRegistry {
+    /// Async protocols.
+    async_methods: AtomicPtr<PyAsyncMethods>,
+    /// Basic protocols.
+    basic_methods: AtomicPtr<PyObjectMethods>,
+    /// Buffer protocols.
+    buffer_methods: AtomicPtr<PyBufferProcs>,
+    /// Descr pProtocols.
+    descr_methods: AtomicPtr<PyDescrMethods>,
+    /// GC protocols.
+    gc_methods: AtomicPtr<PyGCMethods>,
+    /// Mapping protocols.
+    mapping_methods: AtomicPtr<PyMappingMethods>,
+    /// Number protocols.
+    number_methods: AtomicPtr<PyNumberMethods>,
+    /// Iterator protocols.
+    iter_methods: AtomicPtr<PyIterMethods>,
+    /// Sequence protocols.
+    sequence_methods: AtomicPtr<PySequenceMethods>,
+}
+
+impl PyProtoRegistry {
+    pub const fn new() -> Self {
+        PyProtoRegistry {
+            async_methods: AtomicPtr::new(ptr::null_mut()),
+            basic_methods: AtomicPtr::new(ptr::null_mut()),
+            buffer_methods: AtomicPtr::new(ptr::null_mut()),
+            descr_methods: AtomicPtr::new(ptr::null_mut()),
+            gc_methods: AtomicPtr::new(ptr::null_mut()),
+            mapping_methods: AtomicPtr::new(ptr::null_mut()),
+            number_methods: AtomicPtr::new(ptr::null_mut()),
+            iter_methods: AtomicPtr::new(ptr::null_mut()),
+            sequence_methods: AtomicPtr::new(ptr::null_mut()),
+        }
+    }
+    pub fn set_async_methods(&self, methods: PyAsyncMethods) {
+        self.async_methods
+            .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed)
+    }
+    pub fn set_basic_methods(&self, methods: PyObjectMethods) {
+        self.basic_methods
+            .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed)
+    }
+    pub fn set_buffer_methods(&self, methods: PyBufferProcs) {
+        self.buffer_methods
+            .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed)
+    }
+    pub fn set_descr_methods(&self, methods: PyDescrMethods) {
+        self.descr_methods
+            .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed)
+    }
+    pub fn set_gc_methods(&self, methods: PyGCMethods) {
+        self.gc_methods
+            .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed)
+    }
+    pub fn set_mapping_methods(&self, methods: PyMappingMethods) {
+        self.mapping_methods
+            .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed)
+    }
+    pub fn set_number_methods(&self, methods: PyNumberMethods) {
+        self.number_methods
+            .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed)
+    }
+    pub fn set_iter_methods(&self, methods: PyIterMethods) {
+        self.iter_methods
+            .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed)
+    }
+    pub fn set_sequence_methods(&self, methods: PySequenceMethods) {
+        self.sequence_methods
+            .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed)
+    }
+}
diff --git a/src/class/pyasync.rs b/src/class/pyasync.rs
index a65a91d6e91..4cb3e102f37 100644
--- a/src/class/pyasync.rs
+++ b/src/class/pyasync.rs
@@ -8,6 +8,7 @@
 //! [PEP-0492](https://www.python.org/dev/peps/pep-0492/)
 //!
 
+use crate::derive_utils::TryFromPyCell;
 use crate::err::PyResult;
 use crate::{ffi, PyClass, PyObject};
 
@@ -16,21 +17,21 @@ use crate::{ffi, PyClass, PyObject};
 /// Each method in this trait corresponds to Python async/await implementation.
 #[allow(unused_variables)]
 pub trait PyAsyncProtocol<'p>: PyClass {
-    fn __await__(&'p self) -> Self::Result
+    fn __await__(slf: Self::Receiver) -> Self::Result
     where
         Self: PyAsyncAwaitProtocol<'p>,
     {
         unimplemented!()
     }
 
-    fn __aiter__(&'p self) -> Self::Result
+    fn __aiter__(slf: Self::Receiver) -> Self::Result
     where
         Self: PyAsyncAiterProtocol<'p>,
     {
         unimplemented!()
     }
 
-    fn __anext__(&'p mut self) -> Self::Result
+    fn __anext__(slf: Self::Receiver) -> Self::Result
     where
         Self: PyAsyncAnextProtocol<'p>,
     {
@@ -58,16 +59,19 @@ pub trait PyAsyncProtocol<'p>: PyClass {
 }
 
 pub trait PyAsyncAwaitProtocol<'p>: PyAsyncProtocol<'p> {
+    type Receiver: TryFromPyCell<'p, Self>;
     type Success: crate::IntoPy<PyObject>;
     type Result: Into<PyResult<Self::Success>>;
 }
 
 pub trait PyAsyncAiterProtocol<'p>: PyAsyncProtocol<'p> {
+    type Receiver: TryFromPyCell<'p, Self>;
     type Success: crate::IntoPy<PyObject>;
     type Result: Into<PyResult<Self::Success>>;
 }
 
 pub trait PyAsyncAnextProtocol<'p>: PyAsyncProtocol<'p> {
+    type Receiver: TryFromPyCell<'p, Self>;
     type Success: crate::IntoPy<PyObject>;
     type Result: Into<PyResult<Option<Self::Success>>>;
 }
@@ -86,91 +90,29 @@ pub trait PyAsyncAexitProtocol<'p>: PyAsyncProtocol<'p> {
 }
 
 #[doc(hidden)]
-pub trait PyAsyncProtocolImpl {
-    fn tp_as_async() -> Option<ffi::PyAsyncMethods>;
-}
-
-impl<T> PyAsyncProtocolImpl for T {
-    default fn tp_as_async() -> Option<ffi::PyAsyncMethods> {
-        None
-    }
-}
-
-impl<'p, T> PyAsyncProtocolImpl for T
-where
-    T: PyAsyncProtocol<'p>,
-{
-    #[inline]
-    fn tp_as_async() -> Option<ffi::PyAsyncMethods> {
-        Some(ffi::PyAsyncMethods {
-            am_await: Self::am_await(),
-            am_aiter: Self::am_aiter(),
-            am_anext: Self::am_anext(),
-        })
-    }
-}
-
-trait PyAsyncAwaitProtocolImpl {
-    fn am_await() -> Option<ffi::unaryfunc>;
-}
-
-impl<'p, T> PyAsyncAwaitProtocolImpl for T
-where
-    T: PyAsyncProtocol<'p>,
-{
-    default fn am_await() -> Option<ffi::unaryfunc> {
-        None
-    }
-}
-
-impl<T> PyAsyncAwaitProtocolImpl for T
-where
-    T: for<'p> PyAsyncAwaitProtocol<'p>,
-{
-    #[inline]
-    fn am_await() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyAsyncAwaitProtocol, T::__await__)
-    }
-}
-
-trait PyAsyncAiterProtocolImpl {
-    fn am_aiter() -> Option<ffi::unaryfunc>;
-}
-
-impl<'p, T> PyAsyncAiterProtocolImpl for T
-where
-    T: PyAsyncProtocol<'p>,
-{
-    default fn am_aiter() -> Option<ffi::unaryfunc> {
-        None
+impl ffi::PyAsyncMethods {
+    pub fn set_await<T>(&mut self)
+    where
+        T: for<'p> PyAsyncAwaitProtocol<'p>,
+    {
+        self.am_await = py_unarys_func!(PyAsyncAwaitProtocol, T::__await__);
     }
-}
-
-impl<T> PyAsyncAiterProtocolImpl for T
-where
-    T: for<'p> PyAsyncAiterProtocol<'p>,
-{
-    #[inline]
-    fn am_aiter() -> Option<ffi::unaryfunc> {
-        py_unary_func!(PyAsyncAiterProtocol, T::__aiter__)
+    pub fn set_aiter<T>(&mut self)
+    where
+        T: for<'p> PyAsyncAiterProtocol<'p>,
+    {
+        self.am_aiter = py_unarys_func!(PyAsyncAiterProtocol, T::__aiter__);
     }
-}
-
-trait PyAsyncAnextProtocolImpl {
-    fn am_anext() -> Option<ffi::unaryfunc>;
-}
-
-impl<'p, T> PyAsyncAnextProtocolImpl for T
-where
-    T: PyAsyncProtocol<'p>,
-{
-    default fn am_anext() -> Option<ffi::unaryfunc> {
-        None
+    pub fn set_anext<T>(&mut self)
+    where
+        T: for<'p> PyAsyncAnextProtocol<'p>,
+    {
+        self.am_anext = anext::am_anext::<T>();
     }
 }
 
 mod anext {
-    use super::{PyAsyncAnextProtocol, PyAsyncAnextProtocolImpl};
+    use super::PyAsyncAnextProtocol;
     use crate::callback::IntoPyCallbackOutput;
     use crate::err::PyResult;
     use crate::IntoPyPointer;
@@ -191,19 +133,11 @@ mod anext {
         }
     }
 
-    impl<T> PyAsyncAnextProtocolImpl for T
+    #[inline]
+    pub(super) fn am_anext<T>() -> Option<ffi::unaryfunc>
     where
         T: for<'p> PyAsyncAnextProtocol<'p>,
     {
-        #[inline]
-        fn am_anext() -> Option<ffi::unaryfunc> {
-            py_unary_func!(
-                PyAsyncAnextProtocol,
-                T::__anext__,
-                call_mut,
-                *mut crate::ffi::PyObject,
-                IterANextOutput
-            )
-        }
+        py_unarys_func!(PyAsyncAnextProtocol, T::__anext__, IterANextOutput)
     }
 }
diff --git a/src/class/sequence.rs b/src/class/sequence.rs
index ad88032c968..3a0f605d19f 100644
--- a/src/class/sequence.rs
+++ b/src/class/sequence.rs
@@ -127,355 +127,168 @@ pub trait PySequenceInplaceRepeatProtocol<'p>: PySequenceProtocol<'p> + IntoPy<P
 }
 
 #[doc(hidden)]
-pub trait PySequenceProtocolImpl {
-    fn tp_as_sequence() -> Option<ffi::PySequenceMethods>;
-}
-
-impl<T> PySequenceProtocolImpl for T {
-    default fn tp_as_sequence() -> Option<ffi::PySequenceMethods> {
-        None
-    }
-}
-
-impl<'p, T> PySequenceProtocolImpl for T
-where
-    T: PySequenceProtocol<'p>,
-{
-    fn tp_as_sequence() -> Option<ffi::PySequenceMethods> {
-        Some(ffi::PySequenceMethods {
-            sq_length: Self::sq_length(),
-            sq_concat: Self::sq_concat(),
-            sq_repeat: Self::sq_repeat(),
-            sq_item: Self::sq_item(),
-            was_sq_slice: ::std::ptr::null_mut(),
-            sq_ass_item: sq_ass_item_impl::sq_ass_item::<Self>(),
-            was_sq_ass_slice: ::std::ptr::null_mut(),
-            sq_contains: Self::sq_contains(),
-            sq_inplace_concat: Self::sq_inplace_concat(),
-            sq_inplace_repeat: Self::sq_inplace_repeat(),
-        })
-    }
-}
-
-trait PySequenceLenProtocolImpl {
-    fn sq_length() -> Option<ffi::lenfunc>;
-}
-
-impl<'p, T> PySequenceLenProtocolImpl for T
-where
-    T: PySequenceProtocol<'p>,
-{
-    default fn sq_length() -> Option<ffi::lenfunc> {
-        None
-    }
-}
-
-impl<T> PySequenceLenProtocolImpl for T
-where
-    T: for<'p> PySequenceLenProtocol<'p>,
-{
-    fn sq_length() -> Option<ffi::lenfunc> {
-        py_len_func!(PySequenceLenProtocol, T::__len__)
-    }
-}
-
-trait PySequenceGetItemProtocolImpl {
-    fn sq_item() -> Option<ffi::ssizeargfunc>;
-}
-
-impl<'p, T> PySequenceGetItemProtocolImpl for T
-where
-    T: PySequenceProtocol<'p>,
-{
-    default fn sq_item() -> Option<ffi::ssizeargfunc> {
-        None
-    }
-}
-
-impl<T> PySequenceGetItemProtocolImpl for T
-where
-    T: for<'p> PySequenceGetItemProtocol<'p>,
-{
-    fn sq_item() -> Option<ffi::ssizeargfunc> {
-        py_ssizearg_func!(PySequenceGetItemProtocol, T::__getitem__)
-    }
-}
-
-/// It can be possible to delete and set items (PySequenceSetItemProtocol and
-/// PySequenceDelItemProtocol implemented), only to delete (PySequenceDelItemProtocol implemented)
-/// or no deleting or setting is possible
-mod sq_ass_item_impl {
-    use super::*;
-
-    /// ssizeobjargproc PySequenceMethods.sq_ass_item
-    ///
-    /// This function is used by PySequence_SetItem() and has the same signature. It is also used
-    /// by PyObject_SetItem() and PyObject_DelItem(), after trying the item assignment and deletion
-    /// via the mp_ass_subscript slot. This slot may be left to NULL if the object does not support
-    /// item assignment and deletion.
-    pub(super) fn sq_ass_item<'p, T>() -> Option<ffi::ssizeobjargproc>
+impl ffi::PySequenceMethods {
+    pub fn set_len<T>(&mut self)
     where
-        T: PySequenceProtocol<'p>,
+        T: for<'p> PySequenceLenProtocol<'p>,
     {
-        if let Some(del_set_item) = T::del_set_item() {
-            Some(del_set_item)
-        } else if let Some(del_item) = T::del_item() {
-            Some(del_item)
-        } else if let Some(set_item) = T::set_item() {
-            Some(set_item)
-        } else {
-            None
-        }
+        self.sq_length = py_len_func!(PySequenceLenProtocol, T::__len__);
     }
-
-    trait SetItem {
-        fn set_item() -> Option<ffi::ssizeobjargproc>;
-    }
-
-    impl<'p, T> SetItem for T
+    pub fn set_concat<T>(&mut self)
     where
-        T: PySequenceProtocol<'p>,
+        T: for<'p> PySequenceConcatProtocol<'p>,
     {
-        default fn set_item() -> Option<ffi::ssizeobjargproc> {
-            None
-        }
+        self.sq_concat = py_binary_func!(PySequenceConcatProtocol, T::__concat__);
     }
-
-    impl<T> SetItem for T
+    pub fn set_repeat<T>(&mut self)
     where
-        T: for<'p> PySequenceSetItemProtocol<'p>,
+        T: for<'p> PySequenceRepeatProtocol<'p>,
     {
-        fn set_item() -> Option<ffi::ssizeobjargproc> {
-            unsafe extern "C" fn wrap<T>(
-                slf: *mut ffi::PyObject,
-                key: ffi::Py_ssize_t,
-                value: *mut ffi::PyObject,
-            ) -> c_int
-            where
-                T: for<'p> PySequenceSetItemProtocol<'p>,
-            {
-                crate::callback_body!(py, {
-                    let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
-
-                    if value.is_null() {
-                        return Err(PyErr::new::<exceptions::NotImplementedError, _>(format!(
-                            "Item deletion is not supported by {:?}",
-                            stringify!(T)
-                        )));
-                    }
-
-                    let mut slf = slf.try_borrow_mut()?;
-                    let value = py.from_borrowed_ptr::<PyAny>(value);
-                    let value = value.extract()?;
-                    slf.__setitem__(key.into(), value).into()
-                })
-            }
-            Some(wrap::<T>)
-        }
+        self.sq_repeat = py_ssizearg_func!(PySequenceRepeatProtocol, T::__repeat__);
     }
-
-    trait DelItem {
-        fn del_item() -> Option<ffi::ssizeobjargproc>;
+    pub fn set_getitem<T>(&mut self)
+    where
+        T: for<'p> PySequenceGetItemProtocol<'p>,
+    {
+        self.sq_item = py_ssizearg_func!(PySequenceGetItemProtocol, T::__getitem__);
     }
-
-    impl<'p, T> DelItem for T
+    pub fn set_setitem<T>(&mut self)
     where
-        T: PySequenceProtocol<'p>,
+        T: for<'p> PySequenceSetItemProtocol<'p>,
     {
-        default fn del_item() -> Option<ffi::ssizeobjargproc> {
-            None
-        }
+        self.sq_ass_item = sq_ass_item_impl::set_item::<T>();
     }
-
-    impl<T> DelItem for T
+    pub fn set_delitem<T>(&mut self)
     where
         T: for<'p> PySequenceDelItemProtocol<'p>,
     {
-        fn del_item() -> Option<ffi::ssizeobjargproc> {
-            unsafe extern "C" fn wrap<T>(
-                slf: *mut ffi::PyObject,
-                key: ffi::Py_ssize_t,
-                value: *mut ffi::PyObject,
-            ) -> c_int
-            where
-                T: for<'p> PySequenceDelItemProtocol<'p>,
-            {
-                crate::callback_body!(py, {
-                    let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
-
-                    if value.is_null() {
-                        slf.borrow_mut().__delitem__(key.into()).into()
-                    } else {
-                        Err(PyErr::new::<exceptions::NotImplementedError, _>(format!(
-                            "Item assignment not supported by {:?}",
-                            stringify!(T)
-                        )))
-                    }
-                })
-            }
-            Some(wrap::<T>)
-        }
-    }
-
-    trait DelSetItem {
-        fn del_set_item() -> Option<ffi::ssizeobjargproc>;
+        self.sq_ass_item = sq_ass_item_impl::del_item::<T>();
     }
-
-    impl<'p, T> DelSetItem for T
+    pub fn set_setdelitem<T>(&mut self)
     where
-        T: PySequenceProtocol<'p>,
+        T: for<'p> PySequenceDelItemProtocol<'p> + for<'p> PySequenceSetItemProtocol<'p>,
     {
-        default fn del_set_item() -> Option<ffi::ssizeobjargproc> {
-            None
-        }
+        self.sq_ass_item = sq_ass_item_impl::set_del_item::<T>();
     }
-
-    impl<T> DelSetItem for T
+    pub fn set_contains<T>(&mut self)
     where
-        T: for<'p> PySequenceSetItemProtocol<'p> + for<'p> PySequenceDelItemProtocol<'p>,
+        T: for<'p> PySequenceContainsProtocol<'p>,
     {
-        fn del_set_item() -> Option<ffi::ssizeobjargproc> {
-            unsafe extern "C" fn wrap<T>(
-                slf: *mut ffi::PyObject,
-                key: ffi::Py_ssize_t,
-                value: *mut ffi::PyObject,
-            ) -> c_int
-            where
-                T: for<'p> PySequenceSetItemProtocol<'p> + for<'p> PySequenceDelItemProtocol<'p>,
-            {
-                crate::callback_body!(py, {
-                    let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
-
-                    if value.is_null() {
-                        call_mut!(slf, __delitem__; key.into())
-                    } else {
-                        let value = py.from_borrowed_ptr::<PyAny>(value);
-                        let mut slf_ = slf.try_borrow_mut()?;
-                        let value = value.extract()?;
-                        slf_.__setitem__(key.into(), value).into()
-                    }
-                })
-            }
-            Some(wrap::<T>)
-        }
-    }
-}
-
-trait PySequenceContainsProtocolImpl {
-    fn sq_contains() -> Option<ffi::objobjproc>;
-}
-
-impl<'p, T> PySequenceContainsProtocolImpl for T
-where
-    T: PySequenceProtocol<'p>,
-{
-    default fn sq_contains() -> Option<ffi::objobjproc> {
-        None
-    }
-}
-
-impl<T> PySequenceContainsProtocolImpl for T
-where
-    T: for<'p> PySequenceContainsProtocol<'p>,
-{
-    fn sq_contains() -> Option<ffi::objobjproc> {
-        py_binary_func!(PySequenceContainsProtocol, T::__contains__, c_int)
-    }
-}
-
-trait PySequenceConcatProtocolImpl {
-    fn sq_concat() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PySequenceConcatProtocolImpl for T
-where
-    T: PySequenceProtocol<'p>,
-{
-    default fn sq_concat() -> Option<ffi::binaryfunc> {
-        None
-    }
-}
-
-impl<T> PySequenceConcatProtocolImpl for T
-where
-    T: for<'p> PySequenceConcatProtocol<'p>,
-{
-    fn sq_concat() -> Option<ffi::binaryfunc> {
-        py_binary_func!(PySequenceConcatProtocol, T::__concat__)
-    }
-}
-
-trait PySequenceRepeatProtocolImpl {
-    fn sq_repeat() -> Option<ffi::ssizeargfunc>;
-}
-
-impl<'p, T> PySequenceRepeatProtocolImpl for T
-where
-    T: PySequenceProtocol<'p>,
-{
-    default fn sq_repeat() -> Option<ffi::ssizeargfunc> {
-        None
-    }
-}
-
-impl<T> PySequenceRepeatProtocolImpl for T
-where
-    T: for<'p> PySequenceRepeatProtocol<'p>,
-{
-    fn sq_repeat() -> Option<ffi::ssizeargfunc> {
-        py_ssizearg_func!(PySequenceRepeatProtocol, T::__repeat__)
-    }
-}
-
-trait PySequenceInplaceConcatProtocolImpl {
-    fn sq_inplace_concat() -> Option<ffi::binaryfunc>;
-}
-
-impl<'p, T> PySequenceInplaceConcatProtocolImpl for T
-where
-    T: PySequenceProtocol<'p>,
-{
-    default fn sq_inplace_concat() -> Option<ffi::binaryfunc> {
-        None
+        self.sq_contains = py_binary_func!(PySequenceContainsProtocol, T::__contains__, c_int);
     }
-}
-
-impl<T> PySequenceInplaceConcatProtocolImpl for T
-where
-    T: for<'p> PySequenceInplaceConcatProtocol<'p>,
-{
-    fn sq_inplace_concat() -> Option<ffi::binaryfunc> {
-        py_binary_func!(
+    pub fn set_inplace_concat<T>(&mut self)
+    where
+        T: for<'p> PySequenceInplaceConcatProtocol<'p>,
+    {
+        self.sq_inplace_concat = py_binary_func!(
             PySequenceInplaceConcatProtocol,
             T::__inplace_concat__,
             *mut ffi::PyObject,
             call_mut
         )
     }
+    pub fn set_inplace_repeat<T>(&mut self)
+    where
+        T: for<'p> PySequenceInplaceRepeatProtocol<'p>,
+    {
+        self.sq_inplace_repeat = py_ssizearg_func!(
+            PySequenceInplaceRepeatProtocol,
+            T::__inplace_repeat__,
+            call_mut
+        )
+    }
 }
 
-trait PySequenceInplaceRepeatProtocolImpl {
-    fn sq_inplace_repeat() -> Option<ffi::ssizeargfunc>;
-}
+/// It can be possible to delete and set items (PySequenceSetItemProtocol and
+/// PySequenceDelItemProtocol implemented), only to delete (PySequenceDelItemProtocol implemented)
+/// or no deleting or setting is possible
+mod sq_ass_item_impl {
+    use super::*;
 
-impl<'p, T> PySequenceInplaceRepeatProtocolImpl for T
-where
-    T: PySequenceProtocol<'p>,
-{
-    default fn sq_inplace_repeat() -> Option<ffi::ssizeargfunc> {
-        None
+    pub(super) fn set_item<T>() -> Option<ffi::ssizeobjargproc>
+    where
+        T: for<'p> PySequenceSetItemProtocol<'p>,
+    {
+        unsafe extern "C" fn wrap<T>(
+            slf: *mut ffi::PyObject,
+            key: ffi::Py_ssize_t,
+            value: *mut ffi::PyObject,
+        ) -> c_int
+        where
+            T: for<'p> PySequenceSetItemProtocol<'p>,
+        {
+            crate::callback_body!(py, {
+                let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
+
+                if value.is_null() {
+                    return Err(PyErr::new::<exceptions::NotImplementedError, _>(format!(
+                        "Item deletion is not supported by {:?}",
+                        stringify!(T)
+                    )));
+                }
+
+                let mut slf = slf.try_borrow_mut()?;
+                let value = py.from_borrowed_ptr::<PyAny>(value);
+                let value = value.extract()?;
+                slf.__setitem__(key.into(), value).into()
+            })
+        }
+        Some(wrap::<T>)
     }
-}
 
-impl<T> PySequenceInplaceRepeatProtocolImpl for T
-where
-    T: for<'p> PySequenceInplaceRepeatProtocol<'p>,
-{
-    fn sq_inplace_repeat() -> Option<ffi::ssizeargfunc> {
-        py_ssizearg_func!(
-            PySequenceInplaceRepeatProtocol,
-            T::__inplace_repeat__,
-            call_mut
-        )
+    pub(super) fn del_item<T>() -> Option<ffi::ssizeobjargproc>
+    where
+        T: for<'p> PySequenceDelItemProtocol<'p>,
+    {
+        unsafe extern "C" fn wrap<T>(
+            slf: *mut ffi::PyObject,
+            key: ffi::Py_ssize_t,
+            value: *mut ffi::PyObject,
+        ) -> c_int
+        where
+            T: for<'p> PySequenceDelItemProtocol<'p>,
+        {
+            crate::callback_body!(py, {
+                let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
+
+                if value.is_null() {
+                    slf.borrow_mut().__delitem__(key.into()).into()
+                } else {
+                    Err(PyErr::new::<exceptions::NotImplementedError, _>(format!(
+                        "Item assignment not supported by {:?}",
+                        stringify!(T)
+                    )))
+                }
+            })
+        }
+        Some(wrap::<T>)
+    }
+
+    pub(super) fn set_del_item<T>() -> Option<ffi::ssizeobjargproc>
+    where
+        T: for<'p> PySequenceSetItemProtocol<'p> + for<'p> PySequenceDelItemProtocol<'p>,
+    {
+        unsafe extern "C" fn wrap<T>(
+            slf: *mut ffi::PyObject,
+            key: ffi::Py_ssize_t,
+            value: *mut ffi::PyObject,
+        ) -> c_int
+        where
+            T: for<'p> PySequenceSetItemProtocol<'p> + for<'p> PySequenceDelItemProtocol<'p>,
+        {
+            crate::callback_body!(py, {
+                let slf = py.from_borrowed_ptr::<PyCell<T>>(slf);
+
+                if value.is_null() {
+                    call_mut!(slf, __delitem__; key.into())
+                } else {
+                    let value = py.from_borrowed_ptr::<PyAny>(value);
+                    let mut slf_ = slf.try_borrow_mut()?;
+                    let value = value.extract()?;
+                    slf_.__setitem__(key.into(), value).into()
+                }
+            })
+        }
+        Some(wrap::<T>)
     }
 }
diff --git a/src/ffi/object.rs b/src/ffi/object.rs
index 175105b8e1f..ef43db5b7bc 100644
--- a/src/ffi/object.rs
+++ b/src/ffi/object.rs
@@ -438,14 +438,16 @@ mod typeobject {
     impl Default for PyAsyncMethods {
         #[inline]
         fn default() -> Self {
-            unsafe { mem::zeroed() }
+            PyAsyncMethods_INIT
         }
     }
+
     pub const PyAsyncMethods_INIT: PyAsyncMethods = PyAsyncMethods {
         am_await: None,
         am_aiter: None,
         am_anext: None,
     };
+
     #[repr(C)]
     #[derive(Copy, Clone, Debug)]
     pub struct PyBufferProcs {
@@ -456,9 +458,10 @@ mod typeobject {
     impl Default for PyBufferProcs {
         #[inline]
         fn default() -> Self {
-            unsafe { mem::zeroed() }
+            PyBufferProcs_INIT
         }
     }
+
     pub const PyBufferProcs_INIT: PyBufferProcs = PyBufferProcs {
         bf_getbuffer: None,
         bf_releasebuffer: None,
diff --git a/src/instance.rs b/src/instance.rs
index ad2fd59b0b4..2dce9efd9f0 100644
--- a/src/instance.rs
+++ b/src/instance.rs
@@ -47,7 +47,6 @@ pub unsafe trait PyNativeType: Sized {
 pub struct Py<T>(NonNull<ffi::PyObject>, PhantomData<T>);
 
 unsafe impl<T> Send for Py<T> {}
-
 unsafe impl<T> Sync for Py<T> {}
 
 impl<T> Py<T>
diff --git a/src/lib.rs b/src/lib.rs
index 0051461347b..92204145722 100755
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -153,6 +153,7 @@ pub use crate::types::PyAny;
 #[cfg(feature = "macros")]
 #[doc(hidden)]
 pub use {
+    ctor,      // Re-exported for pyproto
     indoc,     // Re-exported for py_run
     inventory, // Re-exported for pymethods
     paste,     // Re-exported for wrap_function
diff --git a/src/pyclass.rs b/src/pyclass.rs
index 95351cf45e4..d3b395e45c8 100644
--- a/src/pyclass.rs
+++ b/src/pyclass.rs
@@ -1,5 +1,6 @@
 //! `PyClass` trait
 use crate::class::methods::{PyClassAttributeDef, PyMethodDefType, PyMethods};
+use crate::class::proto_methods::PyProtoMethods;
 use crate::conversion::{IntoPyPointer, ToPyObject};
 use crate::pyclass_slots::{PyClassDict, PyClassWeakRef};
 use crate::type_object::{type_flags, PyLayout};
@@ -77,6 +78,7 @@ pub trait PyClass:
     + Sized
     + PyClassAlloc
     + PyMethods
+    + PyProtoMethods
     + Send
 {
     /// Specify this class has `#[pyclass(dict)]` or not.
@@ -141,35 +143,43 @@ where
     }
 
     // GC support
-    <T as class::gc::PyGCProtocolImpl>::update_type_object(type_object);
+    if let Some(gc) = T::gc_methods() {
+        unsafe { gc.as_ref() }.update_typeobj(type_object);
+    }
 
     // descriptor protocol
-    <T as class::descr::PyDescrProtocolImpl>::tp_as_descr(type_object);
+    if let Some(descr) = T::descr_methods() {
+        unsafe { descr.as_ref() }.update_typeobj(type_object);
+    }
 
     // iterator methods
-    <T as class::iter::PyIterProtocolImpl>::tp_as_iter(type_object);
+    if let Some(iter) = T::iter_methods() {
+        unsafe { iter.as_ref() }.update_typeobj(type_object);
+    }
 
+    // nb_bool is a part of PyObjectProtocol, but should be placed under tp_as_number
+    let mut nb_bool = None;
     // basic methods
-    <T as class::basic::PyObjectProtocolImpl>::tp_as_object(type_object);
-
-    fn to_ptr<T>(value: Option<T>) -> *mut T {
-        value
-            .map(|v| Box::into_raw(Box::new(v)))
-            .unwrap_or_else(ptr::null_mut)
+    if let Some(basic) = T::basic_methods() {
+        unsafe { basic.as_ref() }.update_typeobj(type_object);
+        nb_bool = unsafe { basic.as_ref() }.nb_bool;
     }
 
     // number methods
-    type_object.tp_as_number = to_ptr(<T as class::number::PyNumberProtocolImpl>::tp_as_number());
+    type_object.tp_as_number = T::number_methods()
+        .map(|mut p| {
+            unsafe { p.as_mut() }.nb_bool = nb_bool;
+            p.as_ptr()
+        })
+        .unwrap_or_else(|| nb_bool.map_or_else(ptr::null_mut, ffi::PyNumberMethods::from_nb_bool));
     // mapping methods
-    type_object.tp_as_mapping =
-        to_ptr(<T as class::mapping::PyMappingProtocolImpl>::tp_as_mapping());
+    type_object.tp_as_mapping = T::mapping_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
     // sequence methods
-    type_object.tp_as_sequence =
-        to_ptr(<T as class::sequence::PySequenceProtocolImpl>::tp_as_sequence());
+    type_object.tp_as_sequence = T::sequence_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
     // async methods
-    type_object.tp_as_async = to_ptr(<T as class::pyasync::PyAsyncProtocolImpl>::tp_as_async());
+    type_object.tp_as_async = T::async_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
     // buffer protocol
-    type_object.tp_as_buffer = to_ptr(<T as class::buffer::PyBufferProtocolImpl>::tp_as_buffer());
+    type_object.tp_as_buffer = T::buffer_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
 
     let (new, call, mut methods, attrs) = py_class_method_defs::<T>();
 
diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs
index 421873f0f4b..60b242cc4f4 100644
--- a/tests/test_dunder.rs
+++ b/tests/test_dunder.rs
@@ -1,5 +1,6 @@
 use pyo3::class::{
-    PyContextProtocol, PyIterProtocol, PyMappingProtocol, PyObjectProtocol, PySequenceProtocol,
+    PyAsyncProtocol, PyContextProtocol, PyDescrProtocol, PyIterProtocol, PyMappingProtocol,
+    PyObjectProtocol, PySequenceProtocol,
 };
 use pyo3::exceptions::{IndexError, ValueError};
 use pyo3::prelude::*;
@@ -552,3 +553,131 @@ fn getattr_doesnt_override_member() {
     py_assert!(py, inst, "inst.data == 4");
     py_assert!(py, inst, "inst.a == 8");
 }
+
+/// Wraps a Python future and yield it once.
+#[pyclass]
+struct OnceFuture {
+    future: PyObject,
+    polled: bool,
+}
+
+#[pymethods]
+impl OnceFuture {
+    #[new]
+    fn new(future: PyObject) -> Self {
+        OnceFuture {
+            future,
+            polled: false,
+        }
+    }
+}
+
+#[pyproto]
+impl PyAsyncProtocol for OnceFuture {
+    fn __await__(slf: PyRef<'p, Self>) -> PyResult<PyRef<'p, Self>> {
+        Ok(slf)
+    }
+}
+
+#[pyproto]
+impl PyIterProtocol for OnceFuture {
+    fn __iter__(slf: PyRef<'p, Self>) -> PyResult<PyRef<'p, Self>> {
+        Ok(slf)
+    }
+    fn __next__(mut slf: PyRefMut<Self>) -> PyResult<Option<PyObject>> {
+        if !slf.polled {
+            slf.polled = true;
+            Ok(Some(slf.future.clone()))
+        } else {
+            Ok(None)
+        }
+    }
+}
+
+#[test]
+fn test_await() {
+    let gil = Python::acquire_gil();
+    let py = gil.python();
+    let once = py.get_type::<OnceFuture>();
+    let source = pyo3::indoc::indoc!(
+        r#"
+import asyncio
+import sys
+
+async def main():
+    res = await Once(await asyncio.sleep(0.1))
+    return res
+# For an odd error similar to https://bugs.python.org/issue38563
+if sys.platform == "win32" and sys.version_info >= (3, 8, 0):
+    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
+# get_event_loop can raise an error: https://github.com/PyO3/pyo3/pull/961#issuecomment-645238579
+loop = asyncio.new_event_loop()
+asyncio.set_event_loop(loop)
+assert loop.run_until_complete(main()) is None
+loop.close()
+"#
+    );
+    let globals = PyModule::import(py, "__main__").unwrap().dict();
+    globals.set_item("Once", once).unwrap();
+    py.run(source, Some(globals), None)
+        .map_err(|e| e.print(py))
+        .unwrap();
+}
+
+/// Increment the count when `__get__` is called.
+#[pyclass]
+struct DescrCounter {
+    #[pyo3(get)]
+    count: usize,
+}
+
+#[pymethods]
+impl DescrCounter {
+    #[new]
+    fn new() -> Self {
+        DescrCounter { count: 0 }
+    }
+}
+
+#[pyproto]
+impl PyDescrProtocol for DescrCounter {
+    fn __get__(
+        mut slf: PyRefMut<'p, Self>,
+        _instance: &PyAny,
+        _owner: Option<&'p PyType>,
+    ) -> PyResult<PyRefMut<'p, Self>> {
+        slf.count += 1;
+        Ok(slf)
+    }
+    fn __set__(
+        _slf: PyRef<'p, Self>,
+        _instance: &PyAny,
+        mut new_value: PyRefMut<'p, Self>,
+    ) -> PyResult<()> {
+        new_value.count = _slf.count;
+        Ok(())
+    }
+}
+
+#[test]
+fn descr_getset() {
+    let gil = Python::acquire_gil();
+    let py = gil.python();
+    let counter = py.get_type::<DescrCounter>();
+    let source = pyo3::indoc::indoc!(
+        r#"
+class Class:
+    counter = Counter()
+c = Class()
+c.counter # count += 1
+assert c.counter.count == 2
+c.counter = Counter()
+assert c.counter.count == 3
+"#
+    );
+    let globals = PyModule::import(py, "__main__").unwrap().dict();
+    globals.set_item("Counter", counter).unwrap();
+    py.run(source, Some(globals), None)
+        .map_err(|e| e.print(py))
+        .unwrap();
+}