@@ -705,7 +705,8 @@ class C(object):
705
705
706
706
assert (
707
707
"Non keyword-only attributes are not allowed after a "
708
- "keyword-only attribute. Attribute in question: Attribute"
708
+ "keyword-only attribute (unless they are init=False). "
709
+ "Attribute in question: Attribute"
709
710
"(name='y', default=NOTHING, validator=None, repr=True, "
710
711
"cmp=True, hash=None, init=True, metadata=mappingproxy({}), "
711
712
"type=None, converter=None, kw_only=False)" ,
@@ -771,6 +772,62 @@ class C(Base):
771
772
assert c .x == 0
772
773
assert c .y == 1
773
774
775
+ def test_init_false_attribute_after_keyword_attribute (self ):
776
+ """
777
+ A positional attribute cannot follow a `kw_only` attribute,
778
+ but an `init=False` attribute can because it won't appear
779
+ in `__init__`
780
+ """
781
+
782
+ @attr .s
783
+ class KwArgBeforeInitFalse :
784
+ kwarg = attr .ib (kw_only = True )
785
+ non_init_function_default = attr .ib (init = False )
786
+ non_init_keyword_default = attr .ib (
787
+ init = False , default = "default-by-keyword"
788
+ )
789
+
790
+ @non_init_function_default .default
791
+ def _init_to_init (self ):
792
+ return self .kwarg + "b"
793
+
794
+ c = KwArgBeforeInitFalse (kwarg = "a" )
795
+
796
+ assert c .kwarg == "a"
797
+ assert c .non_init_function_default == "ab"
798
+ assert c .non_init_keyword_default == "default-by-keyword"
799
+
800
+ def test_init_false_attribute_after_keyword_attribute_with_inheritance (
801
+ self
802
+ ):
803
+ """
804
+ A positional attribute cannot follow a `kw_only` attribute,
805
+ but an `init=False` attribute can because it won't appear
806
+ in `__init__`. This test checks that we allow this
807
+ even when the `kw_only` attribute appears in a parent class
808
+ """
809
+
810
+ @attr .s
811
+ class KwArgBeforeInitFalseParent :
812
+ kwarg = attr .ib (kw_only = True )
813
+
814
+ @attr .s
815
+ class KwArgBeforeInitFalseChild (KwArgBeforeInitFalseParent ):
816
+ non_init_function_default = attr .ib (init = False )
817
+ non_init_keyword_default = attr .ib (
818
+ init = False , default = "default-by-keyword"
819
+ )
820
+
821
+ @non_init_function_default .default
822
+ def _init_to_init (self ):
823
+ return self .kwarg + "b"
824
+
825
+ c = KwArgBeforeInitFalseChild (kwarg = "a" )
826
+
827
+ assert c .kwarg == "a"
828
+ assert c .non_init_function_default == "ab"
829
+ assert c .non_init_keyword_default == "default-by-keyword"
830
+
774
831
775
832
@pytest .mark .skipif (not PY2 , reason = "PY2-specific keyword-only error behavior" )
776
833
class TestKeywordOnlyAttributesOnPy2 (object ):
0 commit comments