diff -r c4e4886c6052 Lib/test/test_zipfile.py --- a/Lib/test/test_zipfile.py Fri Jan 22 16:39:02 2016 -0800 +++ b/Lib/test/test_zipfile.py Sat Jan 23 05:24:19 2016 -0500 @@ -1428,6 +1428,33 @@ # testzip returns the name of the first corrupt file, or None self.assertIsNone(zipf.testzip()) + def test_lazy_init_info_slot_attributes(self): + """Check that ZipInfo slots can be lazily initialized.""" + zinfo = zipfile.ZipInfo() + self.assertIsNone(zinfo.header_offset) + self.assertIsNone(zinfo.CRC) + self.assertIsNone(zinfo.compress_size) + self.assertIsNone(zinfo.file_size) + self.assertIsNone(zinfo._raw_time) + + def test_nonslot_info_attribute_access_still_raises_error(self): + """Check that accessing a non-slot ZipInfo attribute still + raises AttributeError. + + """ + self.assertNotIn("i_am_not_herbert", zipfile.ZipInfo.__slots__) + zinfo = zipfile.ZipInfo() + self.assertRaises(AttributeError, getattr, zinfo, "i_am_not_herbert") + + def test_repr_with_noninitialized_info_slot_attributes(self): + """Check that non-initialized slot attributes no longer cause + AttributeError when a ZipInfo object is repr()'ed. + + """ + zinfo = zipfile.ZipInfo() + r = repr(zinfo) + self.assertIn("file_size=None", r) + def tearDown(self): unlink(TESTFN) unlink(TESTFN2) diff -r c4e4886c6052 Lib/zipfile.py --- a/Lib/zipfile.py Fri Jan 22 16:39:02 2016 -0800 +++ b/Lib/zipfile.py Sat Jan 23 05:24:19 2016 -0500 @@ -353,12 +353,20 @@ self.volume = 0 # Volume number of file header self.internal_attr = 0 # Internal attributes self.external_attr = 0 # External file attributes - # Other attributes are set by class ZipFile: + # Other attributes are set by class ZipFile: (but see __getattr__) # header_offset Byte offset to the file header # CRC CRC-32 of the uncompressed file # compress_size Size of the compressed file # file_size Size of the uncompressed file + def __getattr__(self, name): + # Prevent unexpected AttributeError for any slot that might be accessed + # before it is initialized by lazily initializing it to None + # - only happens once + # - attributes NOT defined as slots will still raise AttributeError + # - future-proof, and subclasses will inherit the benefit, too + setattr(self, name, None) + def __repr__(self): result = ['<%s filename=%r' % (self.__class__.__name__, self.filename)] if self.compress_type != ZIP_STORED: