api.rst (22686B)
1 API Reference 2 ============= 3 4 .. module:: attrs 5 6 *attrs* works by decorating a class using `attrs.define` or `attr.s` and then defining attributes on the class using `attrs.field`, `attr.ib`, or type annotations. 7 8 What follows is the API explanation, if you'd like a more hands-on tutorial, have a look at `examples`. 9 10 If you're confused by the many names, please check out `names` for clarification, but the `TL;DR <https://en.wikipedia.org/wiki/TL;DR>`_ is that as of version 21.3.0, *attrs* consists of **two** top-level package names: 11 12 - The classic ``attr`` that powers the venerable `attr.s` and `attr.ib`. 13 - The newer ``attrs`` that only contains most modern APIs and relies on `attrs.define` and `attrs.field` to define your classes. 14 Additionally it offers some ``attr`` APIs with nicer defaults (e.g. `attrs.asdict`). 15 16 The ``attrs`` namespace is built *on top of* ``attr`` -- which will *never* go away -- and is just as stable, since it doesn't constitute a rewrite. 17 To keep repetition low and this document at a reasonable size, the ``attr`` namespace is `documented on a separate page <api-attr>`, though. 18 19 20 Core 21 ---- 22 23 .. autodata:: attrs.NOTHING 24 :no-value: 25 26 .. autofunction:: attrs.define 27 28 .. function:: mutable(same_as_define) 29 30 Same as `attrs.define`. 31 32 .. versionadded:: 20.1.0 33 34 .. function:: frozen(same_as_define) 35 36 Behaves the same as `attrs.define` but sets *frozen=True* and *on_setattr=None*. 37 38 .. versionadded:: 20.1.0 39 40 .. autofunction:: field 41 42 .. autoclass:: Attribute 43 :members: evolve 44 45 For example: 46 47 .. doctest:: 48 49 >>> import attrs 50 >>> from attrs import define, field 51 52 >>> @define 53 ... class C: 54 ... x = field() 55 >>> attrs.fields(C).x 56 Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x') 57 58 59 .. autofunction:: make_class 60 61 This is handy if you want to programmatically create classes. 62 63 For example: 64 65 .. doctest:: 66 67 >>> C1 = attrs.make_class("C1", ["x", "y"]) 68 >>> C1(1, 2) 69 C1(x=1, y=2) 70 >>> C2 = attrs.make_class("C2", { 71 ... "x": field(default=42), 72 ... "y": field(factory=list) 73 ... }) 74 >>> C2() 75 C2(x=42, y=[]) 76 77 78 .. autoclass:: Factory 79 80 For example: 81 82 .. doctest:: 83 84 >>> @define 85 ... class C: 86 ... x = field(default=attrs.Factory(list)) 87 ... y = field(default=attrs.Factory( 88 ... lambda self: set(self.x), 89 ... takes_self=True) 90 ... ) 91 >>> C() 92 C(x=[], y=set()) 93 >>> C([1, 2, 3]) 94 C(x=[1, 2, 3], y={1, 2, 3}) 95 96 97 Exceptions 98 ---------- 99 100 .. module:: attrs.exceptions 101 102 All exceptions are available from both ``attr.exceptions`` and ``attrs.exceptions`` and are the same thing. 103 That means that it doesn't matter from from which namespace they've been raised and/or caught: 104 105 .. doctest:: 106 107 >>> import attrs, attr 108 >>> try: 109 ... raise attrs.exceptions.FrozenError() 110 ... except attr.exceptions.FrozenError: 111 ... print("this works!") 112 this works! 113 114 .. autoexception:: PythonTooOldError 115 .. autoexception:: FrozenError 116 .. autoexception:: FrozenInstanceError 117 .. autoexception:: FrozenAttributeError 118 .. autoexception:: AttrsAttributeNotFoundError 119 .. autoexception:: NotAnAttrsClassError 120 .. autoexception:: DefaultAlreadySetError 121 .. autoexception:: NotCallableError 122 .. autoexception:: UnannotatedAttributeError 123 124 For example:: 125 126 @attr.s(auto_attribs=True) 127 class C: 128 x: int 129 y = attr.ib() # <- ERROR! 130 131 132 .. _helpers: 133 134 Helpers 135 ------- 136 137 *attrs* comes with a bunch of helper methods that make working with it easier: 138 139 .. currentmodule:: attrs 140 141 .. autofunction:: attrs.cmp_using 142 143 .. autofunction:: attrs.fields 144 145 For example: 146 147 .. doctest:: 148 149 >>> @define 150 ... class C: 151 ... x = field() 152 ... y = field() 153 >>> attrs.fields(C) 154 (Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x'), Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y')) 155 >>> attrs.fields(C)[1] 156 Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y') 157 >>> attrs.fields(C).y is attrs.fields(C)[1] 158 True 159 160 .. autofunction:: attrs.fields_dict 161 162 For example: 163 164 .. doctest:: 165 166 >>> @attr.s 167 ... class C: 168 ... x = attr.ib() 169 ... y = attr.ib() 170 >>> attrs.fields_dict(C) 171 {'x': Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x'), 'y': Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y')} 172 >>> attr.fields_dict(C)['y'] 173 Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y') 174 >>> attrs.fields_dict(C)['y'] is attrs.fields(C).y 175 True 176 177 .. autofunction:: attrs.has 178 179 For example: 180 181 .. doctest:: 182 183 >>> @attr.s 184 ... class C: 185 ... pass 186 >>> attr.has(C) 187 True 188 >>> attr.has(object) 189 False 190 191 .. autofunction:: attrs.resolve_types 192 193 For example: 194 195 .. doctest:: 196 197 >>> import typing 198 >>> @define 199 ... class A: 200 ... a: typing.List['A'] 201 ... b: 'B' 202 ... 203 >>> @define 204 ... class B: 205 ... a: A 206 ... 207 >>> attrs.fields(A).a.type 208 typing.List[ForwardRef('A')] 209 >>> attrs.fields(A).b.type 210 'B' 211 >>> attrs.resolve_types(A, globals(), locals()) 212 <class 'A'> 213 >>> attrs.fields(A).a.type 214 typing.List[A] 215 >>> attrs.fields(A).b.type 216 <class 'B'> 217 218 .. autofunction:: attrs.asdict 219 220 For example: 221 222 .. doctest:: 223 224 >>> @define 225 ... class C: 226 ... x: int 227 ... y: int 228 >>> attrs.asdict(C(1, C(2, 3))) 229 {'x': 1, 'y': {'x': 2, 'y': 3}} 230 231 .. autofunction:: attrs.astuple 232 233 For example: 234 235 .. doctest:: 236 237 >>> @define 238 ... class C: 239 ... x = field() 240 ... y = field() 241 >>> attrs.astuple(C(1,2)) 242 (1, 2) 243 244 .. module:: attrs.filters 245 246 *attrs* includes helpers for filtering the attributes in `attrs.asdict` and `attrs.astuple`: 247 248 .. autofunction:: include 249 250 .. autofunction:: exclude 251 252 See :func:`attrs.asdict` for examples. 253 254 All objects from ``attrs.filters`` are also available from ``attr.filters`` (it's the same module in a different namespace). 255 256 ---- 257 258 .. currentmodule:: attrs 259 260 .. autofunction:: attrs.evolve 261 262 For example: 263 264 .. doctest:: 265 266 >>> @define 267 ... class C: 268 ... x: int 269 ... y: int 270 >>> i1 = C(1, 2) 271 >>> i1 272 C(x=1, y=2) 273 >>> i2 = attrs.evolve(i1, y=3) 274 >>> i2 275 C(x=1, y=3) 276 >>> i1 == i2 277 False 278 279 ``evolve`` creates a new instance using ``__init__``. 280 This fact has several implications: 281 282 * private attributes should be specified without the leading underscore, just like in ``__init__``. 283 * attributes with ``init=False`` can't be set with ``evolve``. 284 * the usual ``__init__`` validators will validate the new values. 285 286 .. autofunction:: attrs.validate 287 288 For example: 289 290 .. doctest:: 291 292 >>> @define(on_setattr=attrs.setters.NO_OP) 293 ... class C: 294 ... x = field(validator=attrs.validators.instance_of(int)) 295 >>> i = C(1) 296 >>> i.x = "1" 297 >>> attrs.validate(i) 298 Traceback (most recent call last): 299 ... 300 TypeError: ("'x' must be <class 'int'> (got '1' that is a <class 'str'>).", ...) 301 302 303 .. _api-validators: 304 305 Validators 306 ---------- 307 308 .. module:: attrs.validators 309 310 *attrs* comes with some common validators in the ``attrs.validators`` module. 311 All objects from ``attrs.validators`` are also available from ``attr.validators`` (it's the same module in a different namespace). 312 313 .. autofunction:: attrs.validators.lt 314 315 For example: 316 317 .. doctest:: 318 319 >>> @define 320 ... class C: 321 ... x = field(validator=attrs.validators.lt(42)) 322 >>> C(41) 323 C(x=41) 324 >>> C(42) 325 Traceback (most recent call last): 326 ... 327 ValueError: ("'x' must be < 42: 42") 328 329 .. autofunction:: attrs.validators.le 330 331 For example: 332 333 .. doctest:: 334 335 >>> @define 336 ... class C: 337 ... x = field(validator=attrs.validators.le(42)) 338 >>> C(42) 339 C(x=42) 340 >>> C(43) 341 Traceback (most recent call last): 342 ... 343 ValueError: ("'x' must be <= 42: 43") 344 345 .. autofunction:: attrs.validators.ge 346 347 For example: 348 349 .. doctest:: 350 351 >>> @define 352 ... class C: 353 ... x = attrs.field(validator=attrs.validators.ge(42)) 354 >>> C(42) 355 C(x=42) 356 >>> C(41) 357 Traceback (most recent call last): 358 ... 359 ValueError: ("'x' must be => 42: 41") 360 361 .. autofunction:: attrs.validators.gt 362 363 For example: 364 365 .. doctest:: 366 367 >>> @define 368 ... class C: 369 ... x = field(validator=attrs.validators.gt(42)) 370 >>> C(43) 371 C(x=43) 372 >>> C(42) 373 Traceback (most recent call last): 374 ... 375 ValueError: ("'x' must be > 42: 42") 376 377 .. autofunction:: attrs.validators.max_len 378 379 For example: 380 381 .. doctest:: 382 383 >>> @define 384 ... class C: 385 ... x = field(validator=attrs.validators.max_len(4)) 386 >>> C("spam") 387 C(x='spam') 388 >>> C("bacon") 389 Traceback (most recent call last): 390 ... 391 ValueError: ("Length of 'x' must be <= 4: 5") 392 393 .. autofunction:: attrs.validators.min_len 394 395 For example: 396 397 .. doctest:: 398 399 >>> @define 400 ... class C: 401 ... x = field(validator=attrs.validators.min_len(1)) 402 >>> C("bacon") 403 C(x='bacon') 404 >>> C("") 405 Traceback (most recent call last): 406 ... 407 ValueError: ("Length of 'x' must be => 1: 0") 408 409 .. autofunction:: attrs.validators.instance_of 410 411 For example: 412 413 .. doctest:: 414 415 >>> @define 416 ... class C: 417 ... x = field(validator=attrs.validators.instance_of(int)) 418 >>> C(42) 419 C(x=42) 420 >>> C("42") 421 Traceback (most recent call last): 422 ... 423 TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None, kw_only=False), <type 'int'>, '42') 424 >>> C(None) 425 Traceback (most recent call last): 426 ... 427 TypeError: ("'x' must be <type 'int'> (got None that is a <type 'NoneType'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, repr=True, cmp=True, hash=None, init=True, type=None, kw_only=False), <type 'int'>, None) 428 429 .. autofunction:: attrs.validators.in_ 430 431 For example: 432 433 .. doctest:: 434 435 >>> import enum 436 >>> class State(enum.Enum): 437 ... ON = "on" 438 ... OFF = "off" 439 >>> @define 440 ... class C: 441 ... state = field(validator=attrs.validators.in_(State)) 442 ... val = field(validator=attrs.validators.in_([1, 2, 3])) 443 >>> C(State.ON, 1) 444 C(state=<State.ON: 'on'>, val=1) 445 >>> C("On", 1) 446 Traceback (most recent call last): 447 ... 448 ValueError: 'state' must be in <enum 'State'> (got 'On'), Attribute(name='state', default=NOTHING, validator=<in_ validator with options <enum 'State'>>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), <enum 'State'>, 'on') 449 >>> C(State.ON, 4) 450 Traceback (most recent call last): 451 ... 452 ValueError: 'val' must be in [1, 2, 3] (got 4), Attribute(name='val', default=NOTHING, validator=<in_ validator with options [1, 2, 3]>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), [1, 2, 3], 4) 453 454 .. autofunction:: attrs.validators.provides 455 456 .. autofunction:: attrs.validators.and_ 457 458 For convenience, it's also possible to pass a list to `attrs.field`'s validator argument. 459 460 Thus the following two statements are equivalent:: 461 462 x = field(validator=attrs.validators.and_(v1, v2, v3)) 463 x = field(validator=[v1, v2, v3]) 464 465 .. autofunction:: attrs.validators.not_ 466 467 For example: 468 469 .. doctest:: 470 471 >>> reserved_names = {"id", "time", "source"} 472 >>> @define 473 ... class Measurement: 474 ... tags = field( 475 ... validator=attrs.validators.deep_mapping( 476 ... key_validator=attrs.validators.not_( 477 ... attrs.validators.in_(reserved_names), 478 ... msg="reserved tag key", 479 ... ), 480 ... value_validator=attrs.validators.instance_of((str, int)), 481 ... ) 482 ... ) 483 >>> Measurement(tags={"source": "universe"}) 484 Traceback (most recent call last): 485 ... 486 ValueError: ("reserved tag key", Attribute(name='tags', default=NOTHING, validator=<not_ validator wrapping <in_ validator with options {'id', 'time', 'source'}>, capturing (<class 'ValueError'>, <class 'TypeError'>)>, type=None, kw_only=False), <in_ validator with options {'id', 'time', 'source'}>, {'source_': 'universe'}, (<class 'ValueError'>, <class 'TypeError'>)) 487 >>> Measurement(tags={"source_": "universe"}) 488 Measurement(tags={'source_': 'universe'}) 489 490 491 .. autofunction:: attrs.validators.optional 492 493 For example: 494 495 .. doctest:: 496 497 >>> @define 498 ... class C: 499 ... x = field( 500 ... validator=attrs.validators.optional( 501 ... attrs.validators.instance_of(int) 502 ... )) 503 >>> C(42) 504 C(x=42) 505 >>> C("42") 506 Traceback (most recent call last): 507 ... 508 TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None, kw_only=False), <type 'int'>, '42') 509 >>> C(None) 510 C(x=None) 511 512 513 .. autofunction:: attrs.validators.is_callable 514 515 For example: 516 517 .. doctest:: 518 519 >>> @define 520 ... class C: 521 ... x = field(validator=attrs.validators.is_callable()) 522 >>> C(isinstance) 523 C(x=<built-in function isinstance>) 524 >>> C("not a callable") 525 Traceback (most recent call last): 526 ... 527 attr.exceptions.NotCallableError: 'x' must be callable (got 'not a callable' that is a <class 'str'>). 528 529 530 .. autofunction:: attrs.validators.matches_re 531 532 For example: 533 534 .. doctest:: 535 536 >>> @define 537 ... class User: 538 ... email = field(validator=attrs.validators.matches_re( 539 ... r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)")) 540 >>> User(email="user@example.com") 541 User(email='user@example.com') 542 >>> User(email="user@example.com@test.com") 543 Traceback (most recent call last): 544 ... 545 ValueError: ("'email' must match regex '(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\\\.[a-zA-Z0-9-.]+$)' ('user@example.com@test.com' doesn't)", Attribute(name='email', default=NOTHING, validator=<matches_re validator for pattern re.compile('(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)')>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), re.compile('(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)'), 'user@example.com@test.com') 546 547 548 .. autofunction:: attrs.validators.deep_iterable 549 550 For example: 551 552 .. doctest:: 553 554 >>> @define 555 ... class C: 556 ... x = field(validator=attrs.validators.deep_iterable( 557 ... member_validator=attrs.validators.instance_of(int), 558 ... iterable_validator=attrs.validators.instance_of(list) 559 ... )) 560 >>> C(x=[1, 2, 3]) 561 C(x=[1, 2, 3]) 562 >>> C(x=set([1, 2, 3])) 563 Traceback (most recent call last): 564 ... 565 TypeError: ("'x' must be <class 'list'> (got {1, 2, 3} that is a <class 'set'>).", Attribute(name='x', default=NOTHING, validator=<deep_iterable validator for <instance_of validator for type <class 'list'>> iterables of <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'list'>, {1, 2, 3}) 566 >>> C(x=[1, 2, "3"]) 567 Traceback (most recent call last): 568 ... 569 TypeError: ("'x' must be <class 'int'> (got '3' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=<deep_iterable validator for <instance_of validator for type <class 'list'>> iterables of <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'int'>, '3') 570 571 572 .. autofunction:: attrs.validators.deep_mapping 573 574 For example: 575 576 .. doctest:: 577 578 >>> @define 579 ... class C: 580 ... x = field(validator=attrs.validators.deep_mapping( 581 ... key_validator=attrs.validators.instance_of(str), 582 ... value_validator=attrs.validators.instance_of(int), 583 ... mapping_validator=attrs.validators.instance_of(dict) 584 ... )) 585 >>> C(x={"a": 1, "b": 2}) 586 C(x={'a': 1, 'b': 2}) 587 >>> C(x=None) 588 Traceback (most recent call last): 589 ... 590 TypeError: ("'x' must be <class 'dict'> (got None that is a <class 'NoneType'>).", Attribute(name='x', default=NOTHING, validator=<deep_mapping validator for objects mapping <instance_of validator for type <class 'str'>> to <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'dict'>, None) 591 >>> C(x={"a": 1.0, "b": 2}) 592 Traceback (most recent call last): 593 ... 594 TypeError: ("'x' must be <class 'int'> (got 1.0 that is a <class 'float'>).", Attribute(name='x', default=NOTHING, validator=<deep_mapping validator for objects mapping <instance_of validator for type <class 'str'>> to <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'int'>, 1.0) 595 >>> C(x={"a": 1, 7: 2}) 596 Traceback (most recent call last): 597 ... 598 TypeError: ("'x' must be <class 'str'> (got 7 that is a <class 'int'>).", Attribute(name='x', default=NOTHING, validator=<deep_mapping validator for objects mapping <instance_of validator for type <class 'str'>> to <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'str'>, 7) 599 600 Validators can be both globally and locally disabled: 601 602 .. autofunction:: attrs.validators.set_disabled 603 604 .. autofunction:: attrs.validators.get_disabled 605 606 .. autofunction:: attrs.validators.disabled 607 608 609 Converters 610 ---------- 611 612 .. module:: attrs.converters 613 614 All objects from ``attrs.converters`` are also available from ``attr.converters`` (it's the same module in a different namespace). 615 616 .. autofunction:: attrs.converters.pipe 617 618 For convenience, it's also possible to pass a list to `attrs.field` / `attr.ib`'s converter arguments. 619 620 Thus the following two statements are equivalent:: 621 622 x = attrs.field(converter=attrs.converter.pipe(c1, c2, c3)) 623 x = attrs.field(converter=[c1, c2, c3]) 624 625 .. autofunction:: attrs.converters.optional 626 627 For example: 628 629 .. doctest:: 630 631 >>> @define 632 ... class C: 633 ... x = field(converter=attrs.converters.optional(int)) 634 >>> C(None) 635 C(x=None) 636 >>> C(42) 637 C(x=42) 638 639 640 .. autofunction:: attrs.converters.default_if_none 641 642 For example: 643 644 .. doctest:: 645 646 >>> @define 647 ... class C: 648 ... x = field( 649 ... converter=attrs.converters.default_if_none("") 650 ... ) 651 >>> C(None) 652 C(x='') 653 654 655 .. autofunction:: attrs.converters.to_bool 656 657 For example: 658 659 .. doctest:: 660 661 >>> @define 662 ... class C: 663 ... x = field( 664 ... converter=attrs.converters.to_bool 665 ... ) 666 >>> C("yes") 667 C(x=True) 668 >>> C(0) 669 C(x=False) 670 >>> C("foo") 671 Traceback (most recent call last): 672 File "<stdin>", line 1, in <module> 673 ValueError: Cannot convert value to bool: foo 674 675 676 677 .. _api_setters: 678 679 Setters 680 ------- 681 682 .. module:: attrs.setters 683 684 These are helpers that you can use together with `attrs.define`'s and `attrs.fields`'s ``on_setattr`` arguments. 685 All setters in ``attrs.setters`` are also available from ``attr.setters`` (it's the same module in a different namespace). 686 687 .. autofunction:: frozen 688 .. autofunction:: validate 689 .. autofunction:: convert 690 .. autofunction:: pipe 691 692 .. data:: NO_OP 693 694 Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. 695 696 Does not work in `attrs.setters.pipe` or within lists. 697 698 .. versionadded:: 20.1.0 699 700 For example, only ``x`` is frozen here: 701 702 .. doctest:: 703 704 >>> @define(on_setattr=attr.setters.frozen) 705 ... class C: 706 ... x = field() 707 ... y = field(on_setattr=attr.setters.NO_OP) 708 >>> c = C(1, 2) 709 >>> c.y = 3 710 >>> c.y 711 3 712 >>> c.x = 4 713 Traceback (most recent call last): 714 ... 715 attrs.exceptions.FrozenAttributeError: () 716 717 N.B. Please use `attrs.define`'s *frozen* argument (or `attrs.frozen`) to freeze whole classes; it is more efficient.