46
46
# Unique object for unequivocal getattr() defaults.
47
47
_sentinel = object ()
48
48
49
+ # Need a forward definition of Factory because _make_init() refers to
50
+ # it, but since Factory is an attrs class, it calls _make_init()
51
+ Factory = None
52
+
49
53
50
54
class _Nothing (object ):
51
55
"""
@@ -848,22 +852,38 @@ def add_hash(self):
848
852
849
853
return self
850
854
851
- def add_init (self ):
852
- self ._cls_dict ["__init__" ] = self ._add_method_dunders (
853
- _make_init (
854
- self ._cls ,
855
- self ._attrs ,
856
- self ._has_post_init ,
857
- self ._frozen ,
858
- self ._slots ,
859
- self ._cache_hash ,
860
- self ._base_attr_map ,
861
- self ._is_exc ,
855
+ def add_init (self , init , auto_detect ):
856
+ should_implement_init = _determine_whether_to_implement (
857
+ self ._cls , init , auto_detect , ("__init__" ,)
858
+ )
859
+ attrs_init_method , init_method = _make_init (
860
+ self ._cls ,
861
+ self ._attrs ,
862
+ self ._has_post_init ,
863
+ self ._frozen ,
864
+ self ._slots ,
865
+ self ._cache_hash ,
866
+ self ._base_attr_map ,
867
+ self ._is_exc ,
868
+ (
862
869
self ._on_setattr is not None
863
- and self ._on_setattr is not setters .NO_OP ,
864
- )
870
+ and self ._on_setattr is not setters .NO_OP
871
+ ),
872
+ should_implement_init ,
865
873
)
866
874
875
+ self ._cls_dict ["__attrs_init__" ] = self ._add_method_dunders (
876
+ attrs_init_method
877
+ )
878
+ if init_method is not None :
879
+ self ._cls_dict ["__init__" ] = self ._add_method_dunders (init_method )
880
+ else :
881
+ if self ._cache_hash :
882
+ raise TypeError (
883
+ "Invalid value for cache_hash. To use hash caching,"
884
+ " init must be True."
885
+ )
886
+
867
887
return self
868
888
869
889
def add_eq (self ):
@@ -1367,17 +1387,7 @@ def wrap(cls):
1367
1387
)
1368
1388
builder .make_unhashable ()
1369
1389
1370
- if _determine_whether_to_implement (
1371
- cls , init , auto_detect , ("__init__" ,)
1372
- ):
1373
- builder .add_init ()
1374
- else :
1375
- if cache_hash :
1376
- raise TypeError (
1377
- "Invalid value for cache_hash. To use hash caching,"
1378
- " init must be True."
1379
- )
1380
-
1390
+ builder .add_init (init , auto_detect )
1381
1391
return builder .build_class ()
1382
1392
1383
1393
# maybe_cls's type depends on the usage of the decorator. It's a class
@@ -1836,6 +1846,7 @@ def _make_init(
1836
1846
base_attr_map ,
1837
1847
is_exc ,
1838
1848
has_global_on_setattr ,
1849
+ should_implement_init ,
1839
1850
):
1840
1851
if frozen and has_global_on_setattr :
1841
1852
raise ValueError ("Frozen classes can't use on_setattr." )
@@ -1872,6 +1883,7 @@ def _make_init(
1872
1883
is_exc ,
1873
1884
needs_cached_setattr ,
1874
1885
has_global_on_setattr ,
1886
+ should_implement_init ,
1875
1887
)
1876
1888
locs = {}
1877
1889
bytecode = compile (script , unique_filename , "exec" )
@@ -1893,10 +1905,16 @@ def _make_init(
1893
1905
unique_filename ,
1894
1906
)
1895
1907
1896
- __init__ = locs ["__init__" ]
1897
- __init__ .__annotations__ = annotations
1908
+ __attrs_init__ = locs ["__attrs_init__" ]
1909
+ __attrs_init__ .__annotations__ = annotations
1910
+
1911
+ if "__init__" in locs :
1912
+ __init__ = locs ["__init__" ]
1913
+ __init__ .__annotations__ = annotations
1914
+ else :
1915
+ __init__ = None
1898
1916
1899
- return __init__
1917
+ return __attrs_init__ , __init__
1900
1918
1901
1919
1902
1920
def _setattr (attr_name , value_var , has_on_setattr ):
@@ -2011,6 +2029,7 @@ def _attrs_to_init_script(
2011
2029
is_exc ,
2012
2030
needs_cached_setattr ,
2013
2031
has_global_on_setattr ,
2032
+ should_implement_init ,
2014
2033
):
2015
2034
"""
2016
2035
Return a script of an initializer for *attrs* and a dict of globals.
@@ -2084,7 +2103,7 @@ def fmt_setter_with_converter(
2084
2103
)
2085
2104
arg_name = a .name .lstrip ("_" )
2086
2105
2087
- has_factory = isinstance (a .default , Factory )
2106
+ has_factory = Factory is not None and isinstance (a .default , Factory )
2088
2107
if has_factory and a .default .takes_self :
2089
2108
maybe_self = "self"
2090
2109
else :
@@ -2225,9 +2244,6 @@ def fmt_setter_with_converter(
2225
2244
names_for_globals [val_name ] = a .validator
2226
2245
names_for_globals [attr_name ] = a
2227
2246
2228
- if post_init :
2229
- lines .append ("self.__attrs_post_init__()" )
2230
-
2231
2247
# because this is set only after __attrs_post_init is called, a crash
2232
2248
# will result if post-init tries to access the hash code. This seemed
2233
2249
# preferable to setting this beforehand, in which case alteration to
@@ -2263,12 +2279,30 @@ def fmt_setter_with_converter(
2263
2279
", " if args else "" , # leading comma
2264
2280
", " .join (kw_only_args ), # kw_only args
2265
2281
)
2282
+
2283
+ init_lines = ""
2284
+ if should_implement_init :
2285
+ init_lines = """\
2286
+
2287
+ def __init__(self, {args}):
2288
+ args = locals()
2289
+ del args['self']
2290
+ self.__attrs_init__(**args)
2291
+ {post_init_call}
2292
+ """ .format (
2293
+ args = args ,
2294
+ post_init_call = "self.__attrs_post_init__()" if post_init else "" ,
2295
+ )
2296
+
2266
2297
return (
2267
2298
"""\
2268
- def __init__ (self, {args}):
2299
+ def __attrs_init__ (self, {args}):
2269
2300
{lines}
2301
+ {init_lines}
2270
2302
""" .format (
2271
- args = args , lines = "\n " .join (lines ) if lines else "pass"
2303
+ args = args ,
2304
+ lines = "\n " .join (lines ) if lines else "pass" ,
2305
+ init_lines = init_lines ,
2272
2306
),
2273
2307
names_for_globals ,
2274
2308
annotations ,
0 commit comments