Repository: pydantic/pydantic
Pull request: Revise models docs to indicate attributes are copied for validation
Closes https://github.com/pydantic/pydantic/issues/1068

## Change Summary

<!-- Please give a short summary of the changes. -->

## Related issue number

<!-- Are there any issues opened that will be resolved by merging this change? -->
<!-- WARNING: please use "fix #123" style references so the issue is closed when this PR is merged. -->

## Checklist

* [ ] Unit tests for the changes exist
* [ ] Tests pass on CI and coverage remains at 100%
* [ ] Documentation reflects the changes where applicable
* [ ] `changes/<pull request or issue id>-<github username>.md` file added describing change
  (see [changes/README.md](https://github.com/samuelcolvin/pydantic/blob/master/changes/README.md) for details)
* [ ] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers**


Selected Reviewer: @Kludex

Problem statement:
Inconsistency with attributes assignment
# Bug

Please complete:
* OS: **macOS Mojave 10.14.6**
* Python version `import sys; print(sys.version)`: **3.7.3**
* Pydantic version `import pydantic; print(pydantic.VERSION)`: **1.2**

On class construction with deep data structs like arrays and dictionaries, I get a copy. It's not how it works for regular class and unexpected (check `class C1` example below). But if I assign array to instance attribute it is just assignment (as expected) and not copy like it is in constructor case.

Could you explain why it works in this way? Or/And help me find the docs that explain it if they exist. 


```py
from typing import List, Dict
from pydantic import BaseModel

class C1():
    arr = []
    def __init__(self, in_arr):
        self.arr = in_arr

class C2(BaseModel):
    arr: List[int]

arr_orig    =  [1,9,10,3,  2,3,11,0, 99,30,40,50]
print("id(arr_orig)", id(arr_orig))

c1 = C1(arr_orig)
print("id(c1.arr)  ", id(c1.arr))

c2 = C2(arr=arr_orig)
print("id(c2.arr)  ", id(c2.arr))

c2.arr = arr_orig
print("id(c2.arr)  ", id(c2.arr))
...
```

Hints from issue discussion:
validation has to iterate over every element of the list to check each item is valid as an int input and perhaps coerce values to ints. e.g. `['1', 2.1, b'3']` will be coerced to `[1, 2, 3]`. This requires creating an new list, hence the new id, that's unavoidable with pydantic.

you don't have `validation_assignment=True` set on the config of `C2`, thus assignment isn't run and standard attribute assignment keeps the list. If you change the code too

```py
...
class C2(BaseModel):
    arr: List[int]

    class Config:
        validate_assignment = True
...
```
You'll see the id does change.

If you see any parts of the docs that need improving to clarify this, feel free to submit a PR.
It would be really nice to stress in docs what arguments passed to constructor of pydantic based class would be copied (because of validation, etc).

Also here in docs https://pydantic-docs.helpmanual.io/usage/dataclasses/:

«Keep in mind that pydantic.dataclasses.dataclass is a drop-in replacement for dataclasses.dataclass with validation» It' also misleading if you don't understand that validation is also means of copying data. 

And «You can use all the standard pydantic field types, and the resulting dataclass will be identical to the one created by the standard library dataclass decorator.» – it's just simple untrue. It's not identical, because pydantic's dataclass is also copies data.
agreed, PR welcome.
Just for my understanding, when the attribute is a nested model and I pass an instance of the nested model, is it also unavoidable to create a copy?

Example:

```py
from pydantic import BaseModel


class Child(BaseModel):

    length: int


class Parent(BaseModel):

    child: Child


if __name__ == "__main__":
    c = Child(length=5)
    p = Parent(child=c)
    print("Original child:", id(c))
    print("Parent's child:", id(p.child))
```
You should assume that Pydantic pretty much always copies the data. There may be situations in which it doesn't, but even for an `int`, where it may seem dubious to copy it, it is more performant to convert it to a Rust int, do all of the validation on that and then return a Python int.

@tpdorsey I'm assigning this to you since all we can do is better document this behavior.

Commit message: update docs to note attributes are copied

Patch to review:
diff --git a/docs/usage/dataclasses.md b/docs/usage/dataclasses.md
index 757095678fe..caba93f5fac 100644
--- a/docs/usage/dataclasses.md
+++ b/docs/usage/dataclasses.md
@@ -20,15 +20,15 @@ print(user)
 ```
 
 !!! note
-    Keep in mind that `pydantic.dataclasses.dataclass` is a drop-in replacement for `dataclasses.dataclass`
-    with validation, **not** a replacement for `pydantic.BaseModel` (with a small difference in how [initialization hooks](#initialize-hooks) work). There are cases where subclassing
-    `pydantic.BaseModel` is the better choice.
+    Keep in mind that `pydantic.dataclasses.dataclass` is **not** a replacement for `pydantic.BaseModel` (with a small
+    difference in how [initialization hooks](#initialize-hooks) work). `pydantic.dataclasses.dataclass` provides a
+    similar functionality to `dataclasses.dataclass` with the addition of Pydantic validation. There are cases where
+    subclassing `pydantic.BaseModel` is the better choice.
 
     For more information and discussion see
     [pydantic/pydantic#710](https://github.com/pydantic/pydantic/issues/710).
 
-You can use all the standard _pydantic_ field types, and the resulting dataclass will be identical to the one
-created by the standard library `dataclass` decorator.
+You can use all the standard _pydantic_ field types. Note, however, that arguments passed to constructor will be copied in order to perform validation and, where necessary coercion.
 
 The underlying model and its schema can be accessed through `__pydantic_model__`.
 Also, fields that require a `default_factory` can be specified by either a `pydantic.Field` or a `dataclasses.field`.
@@ -344,7 +344,7 @@ Note that the `dataclasses.dataclass` from Python stdlib implements only the `__
 
 When substituting usage of `dataclasses.dataclass` with `pydantic.dataclasses.dataclass`, it is recommended to move the code executed in the `__post_init__` method to the `__post_init_post_parse__` method, and only leave behind part of code which needs to be executed before validation.
 
-## JSON Dumping
+## JSON dumping
 
 _Pydantic_ dataclasses do not feature a `.json()` function. To dump them as JSON, you will need to make use of the `pydantic_encoder` as follows:
 
@@ -377,3 +377,34 @@ print(pydantic_core.to_json(user, indent=4).decode())
 }
 """
 ```
+## Attribute copies
+
+As described earlier, when constructing classes with data attributes, Pydantic copies the the attribute in order to efficiently iterate over its elements for validation.
+
+In this example, note that the ID of the list changes after the class is constructed because it has been copied for validation.
+
+```py
+from typing import List
+
+from pydantic import BaseModel
+
+
+class C1:
+    arr = []
+
+    def __init__(self, in_arr):
+        self.arr = in_arr
+
+
+class C2(BaseModel):
+    arr: List[int]
+
+
+arr_orig = [1, 9, 10, 3]
+
+
+c1 = C1(arr_orig)
+c2 = C2(arr=arr_orig)
+print('id(c1.arr) == id(c2.arr)  ', id(c1.arr) == id(c2.arr))
+#> id(c1.arr) == id(c2.arr)   False
+```


Code review comments:
diff_hunk: @@ -377,3 +377,34 @@ print(pydantic_core.to_json(user, indent=4).decode())
 }
 """
 ```
+## Attribute copies
+
+As described earlier, when constructing classes with data attributes, Pydantic copies the the attribute in order to efficiently iterate over its elements for validation.
+
+In this example, note that the ID of the list changes after the class is constructed because it has been copied for validation.
+
+```py
+from typing import List
+
+from pydantic import BaseModel
+
+
+class C1:
path: docs/usage/dataclasses.md
text: @user1:
Was this supposed to be a dataclass? There's no reference to dataclasses in this section. 🤔

@author:
You are totally correct. Moving this to models