diff --git a/aidge_core/unit_tests/test_operator_binding.py b/aidge_core/unit_tests/test_operator_binding.py
index 5b25eb7975d439816dbf91cc95b462f217fd0227..384a82de8d821e45c1be7e61d451ceff0a2bf3c9 100644
--- a/aidge_core/unit_tests/test_operator_binding.py
+++ b/aidge_core/unit_tests/test_operator_binding.py
@@ -73,15 +73,26 @@ class test_operator_binding(unittest.TestCase):
         self.assertEqual(attrs.get_attr("b"), "test")
         self.assertEqual(attrs.has_attr("c"), True)
         self.assertEqual(attrs.get_attr("c"), [True, False, True])
-        self.assertEqual(attrs.dict().keys(), {"a", "b", "c"})
+        self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "mem", "impl"})
         self.assertEqual(attrs.has_attr("d"), False)
+        self.assertEqual(attrs.has_attr("mem.a"), True)
+        self.assertEqual(attrs.get_attr("mem.a"), 1)
+        self.assertEqual(attrs.has_attr("mem.data.b"), True)
+        self.assertEqual(attrs.get_attr("mem.data.b"), 1.0)
+        self.assertEqual(attrs.get_attr("mem").get_attr("data").get_attr("b"), 1.0)
+        self.assertEqual(attrs.has_attr("impl.c"), True)
+        self.assertEqual(attrs.get_attr("impl.c"), "test")
 
         # Add Python attributes
         attrs.add_attr("d", 18.56)
         self.assertEqual(attrs.get_attr("d"), 18.56)
         self.assertEqual(attrs.has_attr("d"), True)
-        self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "d"})
+        self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "d", "mem", "impl"})
         self.assertEqual(attrs.has_attr("e"), False)
+        attrs.add_attr("mem.data.c", 19.36)
+        self.assertEqual(attrs.get_attr("mem.data.c"), 19.36)
+        self.assertEqual(attrs.has_attr("mem.data.c"), True)
+        self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "d", "mem", "impl"})
 
         # Check that added Python attribute is accessible in C++
         # Return the value of an attribute named "d" of type float64 (double in C++)
diff --git a/include/aidge/utils/DynamicAttributes.hpp b/include/aidge/utils/DynamicAttributes.hpp
index 9c4c197e99406b0d4b4e9afdc5e4fb0c5479da62..5218def658acd164a8ce1c4a28b2a916d6cd930d 100644
--- a/include/aidge/utils/DynamicAttributes.hpp
+++ b/include/aidge/utils/DynamicAttributes.hpp
@@ -67,19 +67,8 @@ public:
         }
         else {
             const auto ns = name.substr(0, dot);
+            AIDGE_ASSERT(isPascalCase(ns), "Aidge standard requires PascalCase for C++ Attributes namespace for \"{}\".", ns);
             const auto nsName = name.substr(dot + 1);
-
-#ifdef PYBIND
-            // If attribute does not exist in C++, it might have been created or modified in Python
-            auto it = mAttrs.find(ns);
-            if (it == mAttrs.end()) {
-                auto itPy = mAttrsPy.find(ns);
-                if (itPy != mAttrsPy.end()) {
-                    // Insert the attribute back in C++
-                    mAttrs.emplace(std::make_pair(ns, future_std::any(itPy->second.cast<DynamicAttributes>())));
-                }
-            }
-#endif
             return future_std::any_cast<const DynamicAttributes&>(mAttrs.at(ns)).getAttr<T>(nsName);
         }
     }
@@ -106,23 +95,16 @@ public:
             // We cannot handle Python object if the Python interpreter is not running
             if (Py_IsInitialized()) {
                 // Keep a copy of the attribute in py::object that is updated everytime
-                mAttrsPy.emplace(std::make_pair(pascalToSnake(name), py::cast(value)));
+                const auto& resPy = mAttrsPy.emplace(std::make_pair(pascalToSnake(name), py::cast(value)));
+                AIDGE_ASSERT(resPy.second, "attribute \"{}\" already exists (added in Python)", name);
             }
 #endif
         }
         else {
             const auto ns = name.substr(0, dot);
+            AIDGE_ASSERT(isPascalCase(ns), "Aidge standard requires PascalCase for C++ Attributes namespace for \"{}\".", ns);
             const auto nsName = name.substr(dot + 1);
             const auto& res = mAttrs.emplace(std::make_pair(ns, future_std::any(DynamicAttributes())));
-
-#ifdef PYBIND
-            // We cannot handle Python object if the Python interpreter is not running
-            if (Py_IsInitialized()) {
-                // Keep a copy of the attribute in py::object that is updated everytime
-                mAttrsPy.emplace(std::make_pair(ns, py::cast(DynamicAttributes())));
-            }
-#endif
-
             future_std::any_cast<DynamicAttributes&>(res.first->second).addAttr(nsName, value);
         }
     }
@@ -135,6 +117,7 @@ public:
     {
         const auto dot = name.find('.');
         if (dot == name.npos) {
+            AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes for \"{}\".", name);
             auto res = mAttrs.emplace(std::make_pair(name, future_std::any(value)));
             if (!res.second)
                 res.first->second = future_std::any(value);
@@ -151,17 +134,9 @@ public:
         }
         else {
             const auto ns = name.substr(0, dot);
+            AIDGE_ASSERT(isPascalCase(ns), "Aidge standard requires PascalCase for C++ Attributes namespace for \"{}\".", ns);
             const auto nsName = name.substr(dot + 1);
             auto res = mAttrs.emplace(std::make_pair(ns, future_std::any(DynamicAttributes())));
-
-#ifdef PYBIND
-            // We cannot handle Python object if the Python interpreter is not running
-            if (Py_IsInitialized()) {
-                // Keep a copy of the attribute in py::object that is updated everytime
-                auto resPy = mAttrsPy.emplace(std::make_pair(ns, py::cast(DynamicAttributes())));
-            }
-#endif
-
             future_std::any_cast<DynamicAttributes&>(res.first->second).setAttr<T>(nsName, value);
         }
     }
@@ -169,18 +144,17 @@ public:
     void delAttr(const std::string& name) {
         const auto dot = name.find('.');
         if (dot == name.npos) {
+            AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes for \"{}\".", name);
             mAttrs.erase(name);
 #ifdef PYBIND
-            mAttrsPy.erase(name);
+            mAttrsPy.erase(pascalToSnake(name));
 #endif
         }
         else {
             const auto ns = name.substr(0, dot);
+            AIDGE_ASSERT(isPascalCase(ns), "Aidge standard requires PascalCase for C++ Attributes namespace for \"{}\".", ns);
             const auto nsName = name.substr(dot + 1);
             future_std::any_cast<DynamicAttributes&>(mAttrs.at(ns)).delAttr(nsName);
-#ifdef PYBIND
-            mAttrsPy.erase(name);
-#endif
         }
     }
 
@@ -191,15 +165,16 @@ public:
         if (dot == name.npos) {
             AIDGE_ASSERT(isSnakeCase(name), "Aidge standard requires snake_case for Attributes with Python for \"{}\".", name);
             auto it = mAttrs.find(snakeToPascal(name));
-            AIDGE_ASSERT(it == mAttrs.end(), "attribute \"{}\" already exists", name);
+            AIDGE_ASSERT(it == mAttrs.end(), "attribute \"{}\" already exists (added in C++)", name);
 
             const auto& res = mAttrsPy.emplace(std::make_pair(name, value));
             AIDGE_ASSERT(res.second, "attribute \"{}\" already exists", name);
         }
         else {
             const auto ns = name.substr(0, dot);
+            AIDGE_ASSERT(isSnakeCase(ns), "Aidge standard requires snake_case for Attributes namespace with Python for \"{}\".", ns);
             const auto nsName = name.substr(dot + 1);
-            const auto& res = mAttrs.emplace(std::make_pair(ns, future_std::any(DynamicAttributes())));
+            const auto& res = mAttrs.emplace(std::make_pair(snakeToPascal(ns), DynamicAttributes()));
 
             future_std::any_cast<DynamicAttributes&>(res.first->second).addAttrPy(nsName, std::move(value));
         }
@@ -220,8 +195,9 @@ public:
         }
         else {
             const auto ns = name.substr(0, dot);
+            AIDGE_ASSERT(isSnakeCase(ns), "Aidge standard requires snake_case for Attributes namespace with Python for \"{}\".", ns);
             const auto nsName = name.substr(dot + 1);
-            const auto& res = mAttrs.emplace(std::make_pair(ns, future_std::any(DynamicAttributes())));
+            const auto& res = mAttrs.emplace(std::make_pair(snakeToPascal(ns), DynamicAttributes()));
 
             future_std::any_cast<DynamicAttributes&>(res.first->second).setAttrPy(nsName, std::move(value));
         }
@@ -229,6 +205,12 @@ public:
 
     py::dict dict() const override {
         py::dict attributes;
+        for (const auto& elt : mAttrs) {
+            const std::string snakeName = pascalToSnake(elt.first);
+            if (elt.second.type() == typeid(DynamicAttributes)) {
+                attributes[snakeName.c_str()] = future_std::any_cast<const DynamicAttributes&>(elt.second).dict();
+            }
+        }
         for (const auto& elt : mAttrsPy) {
             const std::string snakeName = pascalToSnake(elt.first);
             attributes[snakeName.c_str()] = elt.second;
@@ -255,10 +237,16 @@ public:
         const auto dot = name.find('.');
         if (dot == name.npos) {
             AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes for \"{}\".", name);
+#ifdef PYBIND
+            return (mAttrs.find(name) != mAttrs.cend() || mAttrsPy.find(pascalToSnake(name)) != mAttrsPy.cend());
+
+#else
             return (mAttrs.find(name) != mAttrs.cend());
+#endif
         }
         else {
             const auto ns = name.substr(0, dot);
+            AIDGE_ASSERT(isPascalCase(ns), "Aidge standard requires PascalCase for C++ Attributes namespace \"{}\".", ns);
             const auto it = mAttrs.find(ns);
             if (it != mAttrs.cend()) {
                 const auto nsName = name.substr(dot + 1);
@@ -280,19 +268,14 @@ public:
         }
         else {
             const auto ns = name.substr(0, dot);
-            const auto nsName = name.substr(dot + 1);
-            const auto it = mAttrs.find(ns);
+            AIDGE_ASSERT(isSnakeCase(ns), "Aidge standard requires snake_case for Attributes namespace with Python for \"{}\".", ns);
+            const auto it = mAttrs.find(snakeToPascal(ns));
             if (it != mAttrs.cend()) {
+                const auto nsName = name.substr(dot + 1);
                 return future_std::any_cast<const DynamicAttributes&>(it->second).hasAttrPy(nsName);
             }
             else {
-                const auto itPy = mAttrsPy.find(ns);
-                if (itPy != mAttrsPy.cend()) {
-                    return itPy->second.cast<DynamicAttributes>().hasAttrPy(nsName);
-                }
-                else {
-                    return false;
-                }
+                return false;
             }
         }
     }
@@ -320,18 +303,6 @@ public:
         else {
             const auto ns = name.substr(0, dot);
             const auto nsName = name.substr(dot + 1);
-
-#ifdef PYBIND
-            // If attribute does not exist in C++, it might have been created in Python
-            auto it = mAttrs.find(ns);
-            if (it == mAttrs.end()) {
-                auto itPy = mAttrsPy.find(ns);
-                if (itPy != mAttrsPy.end()) {
-                    return itPy->second.cast<DynamicAttributes>().getAttrType(nsName);
-                }
-            }
-#endif
-
             return future_std::any_cast<const DynamicAttributes&>(mAttrs.at(ns)).getAttrType(nsName);
         }
     }
@@ -357,12 +328,24 @@ public:
     inline py::object getAttrPy(const std::string& name) const override final {
         const auto dot = name.find('.');
         if (dot == name.npos) {
-            return mAttrsPy.at(name);
+            AIDGE_ASSERT(isSnakeCase(name), "Aidge standard requires snake_case for Attributes with Python for \"{}\".", name);
+            auto itPy = mAttrsPy.find(name);
+            if (itPy == mAttrsPy.end()) {
+                // Attribute may be a namespace
+                auto it = mAttrs.find(snakeToPascal(name));
+                AIDGE_ASSERT(it != mAttrs.end() && it->second.type() == typeid(DynamicAttributes), "attribute \"{}\" not found", name);
+                return py::cast(future_std::any_cast<const DynamicAttributes&>(it->second));
+            }
+            else {
+                return itPy->second;
+            }
         }
         else {
+            // Namespace is stored in mAttrs, so convention should be pascal.
             const auto ns = name.substr(0, dot);
+            AIDGE_ASSERT(isSnakeCase(ns), "Aidge standard requires snake_case for Attributes namespace with Python for \"{}\".", ns);
             const auto nsName = name.substr(dot + 1);
-            return mAttrsPy.at(ns).cast<DynamicAttributes>().getAttrPy(nsName);
+            return future_std::any_cast<const DynamicAttributes&>(mAttrs.at(snakeToPascal(ns))).getAttrPy(nsName);
         }
     };
 #endif
diff --git a/python_binding/utils/pybind_Attributes.cpp b/python_binding/utils/pybind_Attributes.cpp
index 7f5dde63c4835eb694d5fd2d571d7c9c1fd5a9ac..d449feaad9a7234e6f67215737795672fc54e9ac 100644
--- a/python_binding/utils/pybind_Attributes.cpp
+++ b/python_binding/utils/pybind_Attributes.cpp
@@ -24,6 +24,9 @@ DynamicAttributes test_DynamicAttributes_binding() {
     attrs.addAttr<int>("A", 42);
     attrs.addAttr<std::string>("B", "test");
     attrs.addAttr<std::vector<bool>>("C", {true, false, true});
+    attrs.addAttr("Mem.A", 1);
+    attrs.addAttr("Mem.Data.B", 1.0f);
+    attrs.addAttr("Impl.C", std::string("test"));
     return attrs;
 }