diff --git a/README.md b/README.md index 456ce8e..9afed4c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/). For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter). -_1766 TILs and counting..._ +_1767 TILs and counting..._ See some of the other learning resources I work on: @@ -1044,6 +1044,7 @@ If you've learned something here, support my efforts writing daily TILs by - [Access Instance Variables](python/access-instance-variables.md) - [Access Most Recent Return Value In REPL](python/access-most-recent-return-value-in-repl.md) +- [Avoid Modification With Frozen Dataclass](python/avoid-modification-with-frozen-dataclass.md) - [Break Debugger On First Line Of Program](python/break-debugger-on-first-line-of-program.md) - [Check If Package Is Installed With Pip](python/check-if-package-is-installed-with-pip.md) - [Control Passing Of Time In Tests](python/control-passing-of-time-in-tests.md) diff --git a/python/avoid-modification-with-frozen-dataclass.md b/python/avoid-modification-with-frozen-dataclass.md new file mode 100644 index 0000000..52cda1e --- /dev/null +++ b/python/avoid-modification-with-frozen-dataclass.md @@ -0,0 +1,47 @@ +# Avoid Modification With Frozen Dataclass + +The `@dataclass` decorator can be set as _frozen_ to prevent modification of +values on instances of that `dataclass`. + +Without making it frozen, I can easily subvert validations by changing the value +of attributes after the `__post_init__` validations are called. + +```python +>>> config = BPEConfig(300, []) # passes validations +>>> config.vocab_size = 22 # this is invalid, wish this was prevented +``` + +Here is the updated `@dataclass` declaration with `frozen=True` passed as a +parameter. + +```python +from dataclasses import dataclass +from typing import ClassVar + +@dataclass(frozen=True) +class BPEConfig: + BASE_VOCAB_SIZE: ClassVar[int] = 256 + + vocab_size: int + special_tokens: list[str] + + def __post_init__(self): + if self.vocab_size < self.BASE_VOCAB_SIZE: + msg = f"vocab_size ({self.vocab_size}) must be greater than or equal to BASE_VOCAB_SIZE ({self.BASE_VOCAB_SIZE})" + raise ValueError(msg) +``` + +Now I am prevented from modifying a scalar value like `vocab_size` after the +instance has been created. + +```python +>>> config = BPEConfig(300, []) +>>> config.vocab_size = 22 +Traceback (most recent call last): + File "", line 1, in + File "", line 4, in __setattr__ +dataclasses.FrozenInstanceError: cannot assign to field 'vocab_size' +``` + +This doesn't prevent you from modifying the contents of attributes that are +`list` or `dict` types.