Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.4k
bpo-35900: Enable custom reduction callback registration in _pickle#12499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
fb217c9
ENH enable custom reduction callbacks in _pickle
pierreglaser 7c89f8e
TST add a test for custom reduction callbacks
pierreglaser 3ed7293
MNT news entry
pierreglaser b04ca7d
TST enrich the tests
pierreglaser d0ebc9b
CLN rename global_hook to reducer_override
pierreglaser 70798ba
CLN NotImplementedError -> NotImplemented
pierreglaser 3f84541
FIX decref NotImplemented
pierreglaser 98e98be
FIX make reducer_override have (obj) signature
pierreglaser 17b020c
TST remove some redundancy in tests
pierreglaser ad9b0e5
ENH reducer_override should now be a method
pierreglaser 49b5c15
CLN typo
pierreglaser fa71b80
ENH add reducer_override semantics on pickle.py
pierreglaser 2e92068
Update Modules/_pickle.c
ZackerySpytz c22d6df
CLN style
pierreglaser b264222
ensure high-level errors for invalid reduce_values
pierreglaser 91a9042
DOC
pierreglaser c48192b
DOC style and phrasing
pierreglaser 229b81c
reference reducer_override in Pickler class doc
pierreglaser ea11b2e
CLN style
pierreglaser 34bb286
CLN use proper rst directive
pierreglaser 1120c10
CLN period
pierreglaser dc8f276
CLN style
pierreglaser 7df2751
DOC more rst references
pierreglaser 2de069e
CLN style
pierreglaser e6a6b49
- Fix reference leaks
pitrou File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -4,6 +4,7 @@ | ||
| import io | ||
| import functools | ||
| import os | ||
| import math | ||
| import pickle | ||
| import pickletools | ||
| import shutil | ||
| @@ -3013,6 +3014,73 @@ def setstate_bbb(obj, state): | ||
| obj.a = "custom state_setter" | ||
| class AbstractCustomPicklerClass: | ||
| """Pickler implementing a reducing hook using reducer_override.""" | ||
| def reducer_override(self, obj): | ||
| obj_name = getattr(obj, "__name__", None) | ||
| if obj_name == 'f': | ||
| # asking the pickler to save f as 5 | ||
| return int, (5, ) | ||
| if obj_name == 'MyClass': | ||
| return str, ('some str',) | ||
| elif obj_name == 'g': | ||
| # in this case, the callback returns an invalid result (not a 2-5 | ||
| # tuple or a string), the pickler should raise a proper error. | ||
| return False | ||
| elif obj_name == 'h': | ||
| # Simulate a case when the reducer fails. The error should | ||
| # be propagated to the original ``dump`` call. | ||
| raise ValueError('The reducer just failed') | ||
| return NotImplemented | ||
| class AbstractHookTests(unittest.TestCase): | ||
| def test_pickler_hook(self): | ||
| # test the ability of a custom, user-defined CPickler subclass to | ||
| # override the default reducing routines of any type using the method | ||
| # reducer_override | ||
pierreglaser marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| def f(): | ||
| pass | ||
| def g(): | ||
| pass | ||
| def h(): | ||
| pass | ||
| class MyClass: | ||
| pass | ||
| for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): | ||
| with self.subTest(proto=proto): | ||
| bio = io.BytesIO() | ||
| p = self.pickler_class(bio, proto) | ||
| p.dump([f, MyClass, math.log]) | ||
| new_f, some_str, math_log = pickle.loads(bio.getvalue()) | ||
| self.assertEqual(new_f, 5) | ||
| self.assertEqual(some_str, 'some str') | ||
| # math.log does not have its usual reducer overriden, so the | ||
| # custom reduction callback should silently direct the pickler | ||
| # to the default pickling by attribute, by returning | ||
| # NotImplemented | ||
| self.assertIs(math_log, math.log) | ||
| with self.assertRaises(pickle.PicklingError): | ||
| p.dump(g) | ||
| with self.assertRaisesRegex( | ||
| ValueError, 'The reducer just failed'): | ||
| p.dump(h) | ||
| class AbstractDispatchTableTests(unittest.TestCase): | ||
| def test_default_dispatch_table(self): | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2 Misc/NEWS.d/next/Library/2019-03-22-22-40-00.bpo-35900.oiee0o.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| enable custom reduction callback registration for functions and classes in | ||
| _pickle.c, using the new Pickler's attribute ``reducer_override`` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -616,6 +616,9 @@ typedef struct PicklerObject { | ||
| PyObject *pers_func_self; /* borrowed reference to self if pers_func | ||
| is an unbound method, NULL otherwise */ | ||
| PyObject *dispatch_table; /* private dispatch_table, can be NULL */ | ||
| PyObject *reducer_override; /* hook for invoking user-defined callbacks | ||
| instead of save_global when pickling | ||
| functions and classes*/ | ||
| PyObject *write; /* write() method of the output stream. */ | ||
| PyObject *output_buffer; /* Write into a local bytearray buffer before | ||
| @@ -1110,6 +1113,7 @@ _Pickler_New(void) | ||
| self->fast_memo = NULL; | ||
| self->max_output_len = WRITE_BUF_SIZE; | ||
| self->output_len = 0; | ||
| self->reducer_override = NULL; | ||
| self->memo = PyMemoTable_New(); | ||
| self->output_buffer = PyBytes_FromStringAndSize(NULL, | ||
| @@ -2220,7 +2224,7 @@ save_bytes(PicklerObject *self, PyObject *obj) | ||
| Python 2 *and* the appropriate 'bytes' object when unpickled | ||
| using Python 3. Again this is a hack and we don't need to do this | ||
| with newer protocols. */ | ||
| PyObject *reduce_value = NULL; | ||
| PyObject *reduce_value; | ||
| int status; | ||
| if (PyBytes_GET_SIZE(obj) == 0) { | ||
| @@ -4058,7 +4062,25 @@ save(PicklerObject *self, PyObject *obj, int pers_save) | ||
| status = save_tuple(self, obj); | ||
| goto done; | ||
| } | ||
| else if (type == &PyType_Type) { | ||
| /* Now, check reducer_override. If it returns NotImplemented, | ||
| * fallback to save_type or save_global, and then perhaps to the | ||
| * regular reduction mechanism. | ||
| */ | ||
| if (self->reducer_override != NULL) { | ||
| reduce_value = PyObject_CallFunctionObjArgs(self->reducer_override, | ||
| obj, NULL); | ||
| if (reduce_value == NULL) { | ||
| goto error; | ||
| } | ||
| if (reduce_value != Py_NotImplemented) { | ||
| goto reduce; | ||
| } | ||
pierreglaser marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| Py_DECREF(reduce_value); | ||
| reduce_value = NULL; | ||
| } | ||
| if (type == &PyType_Type) { | ||
| status = save_type(self, obj); | ||
| goto done; | ||
| } | ||
| @@ -4149,6 +4171,7 @@ save(PicklerObject *self, PyObject *obj, int pers_save) | ||
| if (reduce_value == NULL) | ||
| goto error; | ||
| reduce: | ||
| if (PyUnicode_Check(reduce_value)) { | ||
| status = save_global(self, obj, reduce_value); | ||
| goto done; | ||
| @@ -4180,6 +4203,20 @@ static int | ||
| dump(PicklerObject *self, PyObject *obj) | ||
| { | ||
| const char stop_op = STOP; | ||
| PyObject *tmp; | ||
| _Py_IDENTIFIER(reducer_override); | ||
| if (_PyObject_LookupAttrId((PyObject *)self, &PyId_reducer_override, | ||
| &tmp) < 0) { | ||
| return -1; | ||
| } | ||
| /* Cache the reducer_override method, if it exists. */ | ||
| if (tmp != NULL) { | ||
| Py_XSETREF(self->reducer_override, tmp); | ||
| } | ||
| else { | ||
| Py_CLEAR(self->reducer_override); | ||
| } | ||
| if (self->proto >= 2) { | ||
| char header[2]; | ||
| @@ -4304,6 +4341,7 @@ Pickler_dealloc(PicklerObject *self) | ||
| Py_XDECREF(self->pers_func); | ||
| Py_XDECREF(self->dispatch_table); | ||
| Py_XDECREF(self->fast_memo); | ||
| Py_XDECREF(self->reducer_override); | ||
| PyMemoTable_Del(self->memo); | ||
| @@ -4317,6 +4355,7 @@ Pickler_traverse(PicklerObject *self, visitproc visit, void *arg) | ||
| Py_VISIT(self->pers_func); | ||
| Py_VISIT(self->dispatch_table); | ||
| Py_VISIT(self->fast_memo); | ||
| Py_VISIT(self->reducer_override); | ||
| return 0; | ||
| } | ||
| @@ -4328,6 +4367,7 @@ Pickler_clear(PicklerObject *self) | ||
| Py_CLEAR(self->pers_func); | ||
| Py_CLEAR(self->dispatch_table); | ||
| Py_CLEAR(self->fast_memo); | ||
| Py_CLEAR(self->reducer_override); | ||
| if (self->memo != NULL) { | ||
| PyMemoTable *memo = self->memo; | ||
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.