diff --git a/CHANGELOG.md b/CHANGELOG.md
index 156a578d9581d1a6eb7e55f56dac1fc83826efed..0a38a1c3137b5c4f7627f8bebe3aa91fc77ead37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ### Added
 - Added a deprecation call policy shortcut ([#466](https://github.com/stack-of-tasks/eigenpy/pull/466))
+- Added id() helper to retrieve unique object identifier in Python ([#477](https://github.com/stack-of-tasks/eigenpy/pull/477))
 
 ### Fixed
 - Fix register_symbolic_link_to_registered_type() for multiple successive registrations ([#471](https://github.com/stack-of-tasks/eigenpy/pull/471))
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bcc8f9897f45aeb0ef2b2ebbb720f82ec45cde4f..94bf5d85acf9716dc37d2e407748fc7de0aa9998 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -232,6 +232,7 @@ set(${PROJECT_NAME}_HEADERS
     include/eigenpy/eigen-to-python.hpp
     include/eigenpy/eigen-from-python.hpp
     include/eigenpy/eigen-typedef.hpp
+    include/eigenpy/id.hpp
     include/eigenpy/numpy-map.hpp
     include/eigenpy/geometry.hpp
     include/eigenpy/geometry-conversion.hpp
diff --git a/include/eigenpy/angle-axis.hpp b/include/eigenpy/angle-axis.hpp
index 3f0377c8030177b0ab32da468304b729bd45083d..d0600c79bf907263754157032fd361c2c728728d 100644
--- a/include/eigenpy/angle-axis.hpp
+++ b/include/eigenpy/angle-axis.hpp
@@ -118,7 +118,8 @@ class AngleAxisVisitor : public bp::def_visitor<AngleAxisVisitor<AngleAxis> > {
   static void expose() {
     bp::class_<AngleAxis>(
         "AngleAxis", "AngleAxis representation of a rotation.\n\n", bp::no_init)
-        .def(AngleAxisVisitor<AngleAxis>());
+        .def(AngleAxisVisitor<AngleAxis>())
+        .def(IdVisitor<AngleAxis>());
 
     // Cast to Eigen::RotationBase
     bp::implicitly_convertible<AngleAxis, RotationBase>();
diff --git a/include/eigenpy/decompositions/EigenSolver.hpp b/include/eigenpy/decompositions/EigenSolver.hpp
index 3bc447d6a2d12975b01405db41c57e71e71a9439..14583b2e3e9cade3d0bd2d42f7a9b8a162eaa824 100644
--- a/include/eigenpy/decompositions/EigenSolver.hpp
+++ b/include/eigenpy/decompositions/EigenSolver.hpp
@@ -75,7 +75,9 @@ struct EigenSolverVisitor
   }
 
   static void expose(const std::string& name) {
-    bp::class_<Solver>(name.c_str(), bp::no_init).def(EigenSolverVisitor());
+    bp::class_<Solver>(name.c_str(), bp::no_init)
+        .def(EigenSolverVisitor())
+        .def(IdVisitor<Solver>());
   }
 
  private:
diff --git a/include/eigenpy/decompositions/LDLT.hpp b/include/eigenpy/decompositions/LDLT.hpp
index 2a635a217b028e3d6c10eac97bac814b26d6469a..b148a23a2ddbc652cdb7a75e743c632b33a4931c 100644
--- a/include/eigenpy/decompositions/LDLT.hpp
+++ b/include/eigenpy/decompositions/LDLT.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2021 INRIA
+ * Copyright 2020-2024 INRIA
  */
 
 #ifndef __eigenpy_decomposition_ldlt_hpp__
@@ -119,6 +119,7 @@ struct LDLTSolverVisitor
         "have zeros in the bottom right rank(A) - n submatrix. Avoiding the "
         "square root on D also stabilizes the computation.",
         bp::no_init)
+        .def(IdVisitor<Solver>())
         .def(LDLTSolverVisitor());
   }
 
diff --git a/include/eigenpy/decompositions/LLT.hpp b/include/eigenpy/decompositions/LLT.hpp
index 1996d6ccbe3657a44f6a258b77b9adf75eebe08a..695c37300cc2072fe2ca7f96a26c9ce8aecbf85a 100644
--- a/include/eigenpy/decompositions/LLT.hpp
+++ b/include/eigenpy/decompositions/LLT.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2021 INRIA
+ * Copyright 2020-2024 INRIA
  */
 
 #ifndef __eigenpy_decomposition_llt_hpp__
@@ -115,6 +115,7 @@ struct LLTSolverVisitor
         "remains useful in many other situations like generalised eigen "
         "problems with hermitian matrices.",
         bp::no_init)
+        .def(IdVisitor<Solver>())
         .def(LLTSolverVisitor());
   }
 
diff --git a/include/eigenpy/decompositions/PermutationMatrix.hpp b/include/eigenpy/decompositions/PermutationMatrix.hpp
index 48d8ce7b88cfb2422a1d61aea171f1b0cf3ec86c..094bdbaf1144c0258c9e2ba36aa4910550f4ab72 100644
--- a/include/eigenpy/decompositions/PermutationMatrix.hpp
+++ b/include/eigenpy/decompositions/PermutationMatrix.hpp
@@ -97,6 +97,7 @@ struct PermutationMatrixVisitor
                                   "This class represents a permutation matrix, "
                                   "internally stored as a vector of integers.",
                                   bp::no_init)
+        .def(IdVisitor<PermutationMatrix>())
         .def(PermutationMatrixVisitor());
   }
 };
diff --git a/include/eigenpy/decompositions/SelfAdjointEigenSolver.hpp b/include/eigenpy/decompositions/SelfAdjointEigenSolver.hpp
index cd1ddd42b4160788d1dd53e3b910afb56e531ff7..b2587a802459c192b6358a6abf3d4daf75292828 100644
--- a/include/eigenpy/decompositions/SelfAdjointEigenSolver.hpp
+++ b/include/eigenpy/decompositions/SelfAdjointEigenSolver.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 INRIA
+ * Copyright 2020-2024 INRIA
  */
 
 #ifndef __eigenpy_decomposition_self_adjoint_eigen_solver_hpp__
@@ -84,6 +84,7 @@ struct SelfAdjointEigenSolverVisitor
 
   static void expose(const std::string& name) {
     bp::class_<Solver>(name.c_str(), bp::no_init)
+        .def(IdVisitor<Solver>())
         .def(SelfAdjointEigenSolverVisitor());
   }
 
diff --git a/include/eigenpy/decompositions/minres.hpp b/include/eigenpy/decompositions/minres.hpp
index f2de4e0ca1963bd1cdee18c9e93d80f1fec0c872..7f5019c4b61f708f546c6cdbfc0638305fa6e65c 100644
--- a/include/eigenpy/decompositions/minres.hpp
+++ b/include/eigenpy/decompositions/minres.hpp
@@ -165,7 +165,8 @@ struct MINRESSolverVisitor
         "defaults are the size of the problem for the maximal number of "
         "iterations and NumTraits<Scalar>::epsilon() for the tolerance.\n",
         bp::no_init)
-        .def(MINRESSolverVisitor());
+        .def(MINRESSolverVisitor())
+        .def(IdVisitor<Solver>());
   }
 
  private:
diff --git a/include/eigenpy/decompositions/sparse/LDLT.hpp b/include/eigenpy/decompositions/sparse/LDLT.hpp
index d282442cb717fe9885e3678a489e725f52e6f66e..0d552e4e86cfb2f7c2b342d6e0ddbd34a826dac6 100644
--- a/include/eigenpy/decompositions/sparse/LDLT.hpp
+++ b/include/eigenpy/decompositions/sparse/LDLT.hpp
@@ -59,7 +59,8 @@ struct SimplicialLDLTVisitor
         "prior to the factorization such that the factorized matrix is P A "
         "P^-1.",
         bp::no_init)
-        .def(SimplicialLDLTVisitor());
+        .def(SimplicialLDLTVisitor())
+        .def(IdVisitor<Solver>());
   }
 
  private:
diff --git a/include/eigenpy/decompositions/sparse/LLT.hpp b/include/eigenpy/decompositions/sparse/LLT.hpp
index 199c2573480dbd2a824c78ee8600d74a388e4527..3d3fa0d562e99fc468bfb976cd19dfe0e4fd8d4e 100644
--- a/include/eigenpy/decompositions/sparse/LLT.hpp
+++ b/include/eigenpy/decompositions/sparse/LLT.hpp
@@ -57,7 +57,8 @@ struct SimplicialLLTVisitor
         "prior to the factorization such that the factorized matrix is P A "
         "P^-1.",
         bp::no_init)
-        .def(SimplicialLLTVisitor());
+        .def(SimplicialLLTVisitor())
+        .def(IdVisitor<Solver>());
   }
 };
 
diff --git a/include/eigenpy/fwd.hpp b/include/eigenpy/fwd.hpp
index c511b569097978a51e659340a5fb13ddacfdac44..acc8edc1100f8d34496eb2426c4590b48452cc25 100644
--- a/include/eigenpy/fwd.hpp
+++ b/include/eigenpy/fwd.hpp
@@ -200,5 +200,6 @@ struct has_operator_equal : internal::has_operator_equal_impl<T1, T2>::type {};
 }  // namespace eigenpy
 
 #include "eigenpy/alignment.hpp"
+#include "eigenpy/id.hpp"
 
 #endif  // ifndef __eigenpy_fwd_hpp__
diff --git a/include/eigenpy/id.hpp b/include/eigenpy/id.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..af56d79f1ee2daf2c02cebf79bd8afb71eb016a9
--- /dev/null
+++ b/include/eigenpy/id.hpp
@@ -0,0 +1,33 @@
+//
+// Copyright (c) 2024 INRIA
+//
+
+#ifndef __eigenpy_id_hpp__
+#define __eigenpy_id_hpp__
+
+#include <boost/python.hpp>
+#include <boost/cstdint.hpp>
+
+namespace eigenpy {
+
+///
+/// \brief Add the Python method id to retrieving a unique id for a given object
+/// exposed with Boost.Python
+///
+template <class C>
+struct IdVisitor : public bp::def_visitor<IdVisitor<C> > {
+  template <class PyClass>
+  void visit(PyClass& cl) const {
+    cl.def("id", &id, bp::arg("self"),
+           "Returns the unique identity of an object.\n"
+           "For object held in C++, it corresponds to its memory address.");
+  }
+
+ private:
+  static boost::int64_t id(const C& self) {
+    return boost::int64_t(reinterpret_cast<const void*>(&self));
+  }
+};
+}  // namespace eigenpy
+
+#endif  // ifndef __eigenpy_id_hpp__
diff --git a/include/eigenpy/quaternion.hpp b/include/eigenpy/quaternion.hpp
index a5051c154317ad9d4ff559a3c7aa813fc1777c96..69a72b9eae2de95286a302edca7e366db7b58f86 100644
--- a/include/eigenpy/quaternion.hpp
+++ b/include/eigenpy/quaternion.hpp
@@ -364,7 +364,8 @@ class QuaternionVisitor
         "'q*v' (rotating 'v' by 'q'), "
         "'q==q', 'q!=q', 'q[0..3]'.",
         bp::no_init)
-        .def(QuaternionVisitor<Quaternion>());
+        .def(QuaternionVisitor<Quaternion>())
+        .def(IdVisitor<Quaternion>());
 
     // Cast to Eigen::QuaternionBase and vice-versa
     bp::implicitly_convertible<Quaternion, QuaternionBase>();
diff --git a/include/eigenpy/solvers/BFGSPreconditioners.hpp b/include/eigenpy/solvers/BFGSPreconditioners.hpp
index 2fd36833994863299e366aee7e4cd7c731143bc6..53c4ad433a71fae871141514e0c00f9587328b15 100644
--- a/include/eigenpy/solvers/BFGSPreconditioners.hpp
+++ b/include/eigenpy/solvers/BFGSPreconditioners.hpp
@@ -1,5 +1,6 @@
 /*
  * Copyright 2017 CNRS
+ * Copyright 2024 Inria
  */
 
 #ifndef __eigenpy_bfgs_preconditioners_hpp__
@@ -37,6 +38,7 @@ struct BFGSPreconditionerBaseVisitor
 
   static void expose(const std::string& name) {
     bp::class_<Preconditioner>(name, bp::no_init)
+        .def(IdVisitor<Preconditioner>())
         .def(BFGSPreconditionerBaseVisitor<Preconditioner>());
   }
 };
@@ -56,6 +58,7 @@ struct LimitedBFGSPreconditionerBaseVisitor
 
   static void expose(const std::string& name) {
     bp::class_<Preconditioner>(name.c_str(), bp::no_init)
+        .def(IdVisitor<Preconditioner>())
         .def(LimitedBFGSPreconditionerBaseVisitor<Preconditioner>());
   }
 };
diff --git a/include/eigenpy/solvers/BasicPreconditioners.hpp b/include/eigenpy/solvers/BasicPreconditioners.hpp
index d74ee3f225e89ec8b89c611bfa54fa181e3ef25a..fb65f2028e9559df5785666bfb3795d0e37a4aab 100644
--- a/include/eigenpy/solvers/BasicPreconditioners.hpp
+++ b/include/eigenpy/solvers/BasicPreconditioners.hpp
@@ -1,5 +1,6 @@
 /*
  * Copyright 2017 CNRS
+ * Copyright 2024 Inria
  */
 
 #ifndef __eigenpy_basic_preconditioners_hpp__
@@ -69,7 +70,8 @@ struct DiagonalPreconditionerVisitor
         "A preconditioner based on the digonal entrie.\n"
         "This class allows to approximately solve for A.x = b problems "
         "assuming A is a diagonal matrix.",
-        bp::no_init);
+        bp::no_init)
+        .def(IdVisitor<Preconditioner>());
   }
 };
 
@@ -91,7 +93,8 @@ struct LeastSquareDiagonalPreconditionerVisitor
         "his class allows to approximately solve for A' A x  = A' b problems "
         "assuming A' A is a diagonal matrix.",
         bp::no_init)
-        .def(DiagonalPreconditionerVisitor<Scalar>());
+        .def(DiagonalPreconditionerVisitor<Scalar>())
+        .def(IdVisitor<Preconditioner>());
   }
 };
 #endif
@@ -105,7 +108,8 @@ struct IdentityPreconditionerVisitor
 
   static void expose() {
     bp::class_<Preconditioner>("IdentityPreconditioner", bp::no_init)
-        .def(PreconditionerBaseVisitor<Preconditioner>());
+        .def(PreconditionerBaseVisitor<Preconditioner>())
+        .def(IdVisitor<Preconditioner>());
   }
 };
 
diff --git a/include/eigenpy/solvers/ConjugateGradient.hpp b/include/eigenpy/solvers/ConjugateGradient.hpp
index 6f41e9d788c0136452352a8b224a105aa0982ded..1d3d71afff6c65a35f15c8bf5df2e45bf82a468f 100644
--- a/include/eigenpy/solvers/ConjugateGradient.hpp
+++ b/include/eigenpy/solvers/ConjugateGradient.hpp
@@ -31,7 +31,8 @@ struct ConjugateGradientVisitor
 
   static void expose(const std::string& name = "ConjugateGradient") {
     bp::class_<ConjugateGradient, boost::noncopyable>(name.c_str(), bp::no_init)
-        .def(ConjugateGradientVisitor<ConjugateGradient>());
+        .def(ConjugateGradientVisitor<ConjugateGradient>())
+        .def(IdVisitor<ConjugateGradient>());
   }
 };
 
diff --git a/include/eigenpy/solvers/LeastSquaresConjugateGradient.hpp b/include/eigenpy/solvers/LeastSquaresConjugateGradient.hpp
index 3107270e0eb4d127057c46fd37af0a07e8c93511..21464ae6100cc8e980402453bb5b0203aff2d5cd 100644
--- a/include/eigenpy/solvers/LeastSquaresConjugateGradient.hpp
+++ b/include/eigenpy/solvers/LeastSquaresConjugateGradient.hpp
@@ -34,7 +34,8 @@ struct LeastSquaresConjugateGradientVisitor
         "LeastSquaresConjugateGradient", bp::no_init)
         .def(IterativeSolverVisitor<LeastSquaresConjugateGradient>())
         .def(LeastSquaresConjugateGradientVisitor<
-             LeastSquaresConjugateGradient>());
+             LeastSquaresConjugateGradient>())
+        .def(IdVisitor<LeastSquaresConjugateGradient>());
   }
 };
 
diff --git a/include/eigenpy/std-array.hpp b/include/eigenpy/std-array.hpp
index 492e003201f68a9be4e857c99a221ee5c42c27aa..19ea821b495707fa06a8e43a90bf89237ff5a056 100644
--- a/include/eigenpy/std-array.hpp
+++ b/include/eigenpy/std-array.hpp
@@ -135,6 +135,7 @@ struct StdArrayPythonVisitor {
       bp::class_<array_type> cl(class_name.c_str(), doc_string.c_str());
       cl.def(bp::init<const array_type &>(bp::args("self", "other"),
                                           "Copy constructor"));
+      cl.def(IdVisitor<array_type>());
 
       array_indexing_suite<array_type, NoProxy, SliceAllocator> indexing_suite;
       cl.def(indexing_suite)
diff --git a/include/eigenpy/std-vector.hpp b/include/eigenpy/std-vector.hpp
index 0c5e3bf6e7d1e38394747c03b939f4e1d9457070..2dbdabfd2f8e9bd03c642b0e14466bc5d2648b33 100644
--- a/include/eigenpy/std-vector.hpp
+++ b/include/eigenpy/std-vector.hpp
@@ -454,6 +454,7 @@ struct StdVectorPythonVisitor {
     if (!register_symbolic_link_to_registered_type<vector_type>(
             add_std_visitor)) {
       bp::class_<vector_type> cl(class_name.c_str(), doc_string.c_str());
+      cl.def(IdVisitor<vector_type>());
 
       // Standard vector indexing definition
       boost::python::vector_indexing_suite<
diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt
index 72bfb1bb33e19ab97a0476fe265d576059969635..38f75745f9f7672bf953bb8bba2ccd36190c6059 100644
--- a/unittest/CMakeLists.txt
+++ b/unittest/CMakeLists.txt
@@ -134,6 +134,8 @@ add_python_eigenpy_lib_unit_test("py-LLT" "unittest/python/test_LLT.py")
 
 add_python_eigenpy_lib_unit_test("py-LDLT" "unittest/python/test_LDLT.py")
 
+add_python_eigenpy_lib_unit_test("py-id" "unittest/python/test_id.py")
+
 if(NOT WIN32)
   add_python_eigenpy_lib_unit_test("py-MINRES" "unittest/python/test_MINRES.py")
 endif(NOT WIN32)
diff --git a/unittest/python/test_id.py b/unittest/python/test_id.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6d87bb5e611bc248cbd39bbfbf79009bbb80589
--- /dev/null
+++ b/unittest/python/test_id.py
@@ -0,0 +1,11 @@
+import eigenpy
+
+ldlt1 = eigenpy.LDLT()
+ldlt2 = eigenpy.LDLT()
+
+id1 = ldlt1.id()
+id2 = ldlt2.id()
+
+assert id1 != id2
+assert id1 == ldlt1.id()
+assert id2 == ldlt2.id()