Repository: OpenMined/PySyft

Issue:
Allow Object Attributes in AST
## Description
Allow for object attributes to be allowed in the allowlist AST.
The issue being that they do not exist at Class analysis time, and we would want the bound function to have the context of the target data item.

One method could be to provide a flag that these are dynamic and not bind the method but attach generated function to the Pointer so that on the retrieval side we can attempt to locate the attribute at execution time. 

## Definition of Done
Items like the following can be enabled and work as expected:
```
allowlist["torch.nn.Linear.weight"] = "torch.Tensor"
allowlist["torch.nn.Linear.bias"] = "torch.Tensor"
```



Discussion:
Additional use case is `grad` and `data` for Tensors which are used in the `UnionGenerator` for `Plans`.
```
torch.Tensor.grad
torch.Tensor.data
```
Also see `torchvision`: https://github.com/OpenMined/PySyft/issues/5032 and https://github.com/OpenMined/PySyft/issues/5033 and https://github.com/OpenMined/PySyft/issues/5031
Hi, I did create a PR for the same issue a few months ago but it was closed due to some reason. I would like to work on this PR and try out the method that you have suggested. Also, you can refer to the conversation we had previously in this PR #5117 . 

Resolving patch:
diff --git a/packages/syft/src/syft/lib/torch/__init__.py b/packages/syft/src/syft/lib/torch/__init__.py
--- a/packages/syft/src/syft/lib/torch/__init__.py
+++ b/packages/syft/src/syft/lib/torch/__init__.py
@@ -13,9 +13,11 @@
 from . import return_types  # noqa: 401
 from . import size  # noqa: 401
 from . import uppercase_tensor  # noqa: 401
+from ...ast import add_dynamic_objects
 from ...ast.globals import Globals
 from ...logger import info
 from .allowlist import allowlist
+from .allowlist import dynamic_allowlist
 
 TORCH_VERSION = version.parse(torch.__version__.split("+")[0])
 
@@ -68,8 +70,11 @@ def create_torch_ast(client: Any = None) -> Globals:
         else:
             info(f"Skipping {method} not supported in {TORCH_VERSION}")
 
+    add_dynamic_objects(ast, list(dynamic_allowlist.items()))
+
     for klass in ast.classes:
         klass.create_pointer_class()
         klass.create_send_method()
         klass.create_storable_object_attr_convenience_methods()
+
     return ast
diff --git a/packages/syft/src/syft/lib/torch/allowlist.py b/packages/syft/src/syft/lib/torch/allowlist.py
--- a/packages/syft/src/syft/lib/torch/allowlist.py
+++ b/packages/syft/src/syft/lib/torch/allowlist.py
@@ -6,7 +6,7 @@
 from ..misc.union import UnionGenerator
 
 allowlist: Dict[str, Union[str, Dict[str, str]]] = {}  # (path: str, return_type:type)
-
+dynamic_allowlist: Dict[str, str] = {}
 # --------------------------------------------------------------------------------------
 # SECTION - Tensor methods which are intentionally disabled
 # --------------------------------------------------------------------------------------
@@ -1811,6 +1811,7 @@
 allowlist["torch.nn.Linear.load_state_dict"] = "syft.lib.python._SyNone"
 allowlist["torch.nn.Linear.extra_repr"] = "syft.lib.python.String"
 
+
 # DataLoader
 allowlist["torch.utils.data.DataLoader"] = "torch.utils.data.DataLoader"
 allowlist["torch.utils.data.DataLoader.__iter__"] = "syft.lib.python.Iterator"
@@ -3586,3 +3587,6 @@
     "return_type": "torch.Tensor",
     "min_version": "1.8.0",
 }
+
+dynamic_allowlist["torch.nn.Linear.weight"] = "torch.nn.Parameter"
+dynamic_allowlist["torch.nn.Linear.bias"] = "torch.nn.Parameter"


Test patch:
diff --git a/packages/syft/tests/syft/lib/torch/weight_bias_test.py b/packages/syft/tests/syft/lib/torch/weight_bias_test.py
new file mode 100644
--- /dev/null
+++ b/packages/syft/tests/syft/lib/torch/weight_bias_test.py
@@ -0,0 +1,33 @@
+# third party
+import pytest
+import torch
+
+# syft absolute
+import syft as sy
+
+input_list = [torch.nn.Linear(100, 10)]
+
+
+@pytest.mark.parametrize("target_layer", input_list)
+def test_weights_and_bias(
+    root_client: sy.VirtualMachineClient, target_layer: torch.nn.Module
+) -> None:
+    original_bias = target_layer.bias
+    original_weight = target_layer.weight
+
+    remote_target_mod = target_layer.send(root_client)
+
+    remote_original_bias = remote_target_mod.bias
+    remote_original_weight = remote_target_mod.weight
+
+    assert torch.equal(original_bias, remote_original_bias.get())
+    assert torch.equal(original_weight, remote_original_weight.get())
+
+    new_bias = torch.nn.Parameter(torch.zeros_like(original_bias))
+    new_weight = torch.nn.Parameter(torch.zeros_like(original_weight))
+
+    remote_target_mod.weight = new_weight
+    remote_target_mod.bias = new_bias
+
+    assert torch.equal(remote_target_mod.weight.get(), new_weight)
+    assert torch.equal(remote_target_mod.bias.get(), new_bias)