8000 Merge branch 'pickle-close-files' of https://github.com/kj7rrv/cpytho… · python/cpython@a7ba2e6 · GitHub
[go: up one dir, main page]

Skip to content

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a7ba2e6

Browse files
committed
Merge branch 'pickle-close-files' of https://github.com/kj7rrv/cpython into pickle-close-files
2 parents e43b831 + ed65ed7 commit a7ba2e6

File tree

8 files changed

+272
-3
lines changed

8 files changed

+272
-3
lines changed

Doc/library/dataclasses.rst

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,3 +749,54 @@ mutable types as default values for fields::
749749
``dict``, or ``set``, unhashable objects are now not allowed as
750750
default values. Unhashability is used to approximate
751751
mutability.
752+
753+
Descriptor-typed fields
754+
-----------------------
755+
756+
Fields that are assigned :ref:`descriptor objects <descriptors>` as their
757+
default value have the following special behaviors:
758+
759+
* The value for the field passed to the dataclass's ``__init__`` method is
760+
passed to the descriptor's ``__set__`` method rather than overwriting the
761+
descriptor object.
762+
* Similarly, when getting or setting the field, the descriptor's
763+
``__get__`` or ``__set__`` method is called rather than returning or
764+
overwriting the descriptor object.
765+
* To determine whether a field contains a default value, ``dataclasses``
766+
will call the descriptor's ``__get__`` method using its class access
767+
form (i.e. ``descriptor.__get__(obj=None, type=cls)``. If the
768+
descriptor returns a value in this case, it will be used as the
769+
field's default. On the other hand, if the descriptor raises
770+
:exc:`AttributeError` in this situation, no default value will be
771+
provided for the field.
772+
773+
::
774+
775+
class IntConversionDescriptor:
776+
def __init__(self, *, default):
777+
self._default = default
778+
779+
def __set_name__(self, owner, name):
780+
self._name = "_" + name
781+
782+
def __get__(self, obj, type):
783+
if obj is None:
784+
return self._default
785+
786+
return getattr(obj, self._name, self._default)
787+
788+
def __set__(self, obj, value):
789+
setattr(obj, self._name, int(value))
790+
791+
@dataclass
792+
class InventoryItem:
793+
quantity_on_hand: IntConversionDescripto 57AE r = IntConversionDescriptor(default=100)
794+
795+
i = InventoryItem()
796+
print(i.quantity_on_hand) # 100
797+
i.quantity_on_hand = 2.5 # calls __set__ with 2.5
798+
print(i.quantity_on_hand) # 2
799+
800+
Note that if a field is annotated with a descriptor type, but is not assigned
801+
a descriptor object as its default value, the field will act like a normal
802+
field.

Lib/test/test_dataclasses.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3229,6 +3229,115 @@ class C:
32293229

32303230
self.assertEqual(D.__set_name__.call_count, 1)
32313231

3232+
def test_init_calls_set(self):
3233+
class D:
3234+
pass
3235+
3236+
D.__set__ = Mock()
3237+
3238+
@dataclass
3239+
class C:
3240+
i: D = D()
3241+
3242+
# Make sure D.__set__ is called.
3243+
D.__set__.reset_mock()
3244+
c = C(5)
3245+
self.assertEqual(D.__set__.call_count, 1)
3246+
3247+
def test_getting_field_calls_get(self):
3248+
class D:
3249+
pass
3250+
3251+
D.__set__ = Mock()
3252+
D.__get__ = Mock()
3253+
3254+
@dataclass
3255+
class C:
3256+
i: D = D()
3257+
3258+
c = C(5)
3259+
3260+
# Make sure D.__get__ is called.
3261+
D.__get__.reset_mock()
3262+
value = c.i
3263+
self.assertEqual(D.__get__.call_count, 1)
3264+
3265+
def test_setting_field_calls_set(self):
3266+
class D:
3267+
pass
3268+
3269+
D.__set__ = Mock()
3270+
3271+
@dataclass
3272+
class C:
3273+
i: D = D()
3274+
3275+
c = C(5)
3276+
3277+
# Make sure D.__set__ is called.
3278+
D.__set__.reset_mock()
3279+
c.i = 10
3280+
self.assertEqual(D.__set__.call_count, 1)
3281+
3282+
def test_setting_uninitialized_descriptor_field(self):
3283+
class D:
3284+
pass
3285+
3286+
D.__set__ = Mock()
3287+
3288+
@dataclass
3289+
class C:
3290+
i: D
3291+
3292+
# D.__set__ is not called because there's no D instance to call it on
3293+
D.__set__.reset_mock()
3294+
c = C(5)
3295+
self.assertEqual(D.__set__.call_count, 0)
3296+
3297+
# D.__set__ still isn't called after setting i to an instance of D
3298+
# because descriptors don't behave like that when stored as instance vars
3299+
c.i = D()
3300+
c.i = 5
3301+
self.assertEqual(D.__set__.call_count, 0)
3302+
3303+
def test_default_value(self):
3304+
class D:
3305+
def __get__(self, instance: Any, owner: object) -> int:
3306+
if instance is None:
3307+
return 100
3308+
3309+
return instance._x
3310+
3311+
def __set__(self, instance: Any, value: int) -> None:
3312+
instance._x = value
3313+
3314+
@dataclass
3315+
class C:
3316+
i: D = D()
3317+
3318+
c = C()
3319+
self.assertEqual(c.i, 100)
3320+
3321+
c = C(5)
3322+
self.assertEqual(c.i, 5)
3323+
3324+
def test_no_default_value(self):
3325+
class D:
3326+
def __get__(self, instance: Any, owner: object) -> int:
3327+
if instance is None:
3328+
raise AttributeError()
3329+
3330+
return instance._x
3331+
3332+
def __set__(self, instance: Any, value: int) -> None:
3333+
instance._x = value
3334+
3335+
@dataclass
3336+
class C:
3337+
i: D = D()
3338+
3339+
with self.assertRaisesRegex(TypeError, 'missing 1 required positional argument'):
3340+
c = C()
32323341

32333342
class TestStringAnnotations(unittest.TestCase):
32343343
def test_classvar(self):

Lib/test/test_sys_setprofile.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pprint
33
import sys
44
import unittest
5+
from test import support
56

67

78
class TestGetProfile(unittest.TestCase):
@@ -415,5 +416,43 @@ def show_events(callable):
415416
pprint.pprint(capture_events(callable))
416417

417418

419+
class TestEdgeCases(unittest.TestCase):
420+
421+
def setUp(self):
422+
self.addCleanup(sys.setprofile, sys.getprofile())
423+
sys.setprofile(None)
424+
425+
def test_reentrancy(self):
426+
def foo(*args):
427+
...
428+
429+
def bar(*args):
430+
...
431+
432+
class A:
433+
def __call__(self, *args):
434+
pass
435+
436+
def __del__(self):
437+
sys.setprofile(bar)
438+
439+
sys.setprofile(A())
440+
with support.catch_unraisable_exception() as cm:
441+
sys.setprofile(foo)
442+
self.assertEqual(cm.unraisable.object, A.__del__)
443+
self.assertIsInstance(cm.unraisable.exc_value, RuntimeError)
444+
445+
self.assertEqual(sys.getprofile(), foo)
446+
447+
448+
def test_same_object(self):
449+
def foo(*args):
450+
...
451+
452+
sys.setprofile(foo)
453+
del foo
454+
sys.setprofile(sys.getprofile())
455+
456+
418457
if __name__ == "__main__":
419458
unittest.main()

Lib/test/test_sys_settrace.py

Lines changed: 39 additions & 0 deletions
+
with support.catch_unraisable_exception() as cm:
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from test import support
44
import unittest
5+
from unittest.mock import MagicMock
56
import sys
67
import difflib
78
import gc
@@ -2684,5 +2685,43 @@ def f():
26842685
self.assertEqual(counts, {'call': 1, 'line': 2000, 'return': 1})
26852686

26862687

2688+
class TestEdgeCases(unittest.TestCase):
2689+
2690+
def setUp(self):
2691+
self.addCleanup(sys.settrace, sys.gettrace())
2692+
sys.settrace(None)
2693+
2694+
def test_reentrancy(self):
2695+
def foo(*args):
2696+
...
2697+
2698+
def bar(*args):
2699+
...
2700+
2701+
class A:
2702+
def __call__(self, *args):
2703+
pass
2704+
2705+
def __del__(self):
2706+
sys.settrace(bar)
2707+
2708+
sys.settrace(A())
2709
2710+
sys.settrace(foo)
2711+
self.assertEqual(cm.unraisable.object, A.__del__)
2712+
self.assertIsInstance(cm.unraisable.exc_value, RuntimeError)
2713+
2714+
self.assertEqual(sys.gettrace(), foo)
2715+
2716+
2717+
def test_same_object(self):
2718+
def foo(*args):
2719+
...
2720+
2721+
sys.settrace(foo)
2722+
del foo
2723+
sys.settrace(sys.gettrace())
2724+
2725+
26872726
if __name__ == "__main__":
26882727
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Re-entrant calls to :func:`sys.setprofile` and :func:`sys.settrace` now
2+
raise :exc:`RuntimeError`. Patch by Pablo Galindo.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Added more tests for :mod:`dataclasses` to cover behavior with data
2+
descriptor-based fields.
3+
4+
# Write your Misc/NEWS entry below. It should be a simple ReST paragraph. #
5+
Don't start with "- Issue #<n>: " or "- gh-issue-<n>: " or that sort of
6+
stuff.
7+
###########################################################################

Modules/_lsprof.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ profiler_dealloc(ProfilerObject *op)
750750
if (op->flags & POF_ENABLED) {
751751
PyThreadState *tstate = _PyThreadState_GET();
752752
if (_PyEval_SetProfile(tstate, NULL, NULL) < 0) {
753-
PyErr_WriteUnraisable((PyObject *)op);
753+
_PyErr_WriteUnraisableMsg("When destroying _lsprof profiler", NULL);
754754
}
755755
}
756756

Python/ceval.c

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6973,10 +6973,20 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
69736973
/* The caller must hold the GIL */
69746974
assert(PyGILState_Check());
69756975

6976+
static int reentrant = 0;
6977+
if (reentrant) {
6978+
_PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a profile function "
6979+
"while another profile function is being installed");
6980+
reentrant = 0;
6981+
return -1;
6982+
}
6983+
reentrant = 1;
6984+
69766985
/* Call _PySys_Audit() in the context of the current thread state,
69776986
even if tstate is not the current thread state. */
69786987
PyThreadState *current_tstate = _PyThreadState_GET();
69796988
if (_PySys_Audit(current_tstate, "sys.setprofile", NULL) < 0) {
6989+
reentrant = 0;
69806990
return -1;
69816991
}
69826992

@@ -6994,6 +7004,7 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
69947004

69957005
/* Flag that tracing or profiling is turned on */
69967006
_PyThreadState_UpdateTracingState(tstate);
7007+
reentrant = 0;
69977008
return 0;
69987009
}
69997010

@@ -7014,10 +7025,21 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
70147025
/* The caller must hold the GIL */
70157026
assert(PyGILState_Check());
70167027

7028+
static int reentrant = 0;
7029+
7030+
if (reentrant) {
7031+
_PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a trace function "
7032+
"while another trace function is being installed");
7033+
reentrant = 0;
7034+
return -1;
7035+
}
7036+
reentrant = 1;
7037+
70177038
/* Call _PySys_Audit() in the context of the current thread state,
70187039
even if tstate is not the current thread state. */
70197040
PyThreadState *current_tstate = _PyThreadState_GET();
70207041
if (_PySys_Audit(current_tstate, "sys.settrace", NULL) < 0) {
7042+
reentrant = 0;
70217043
return -1;
70227044
}
70237045

@@ -7027,15 +7049,15 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
70277049
tstate->c_traceobj = NULL;
70287050
/* Must make sure that profiling is not ignored if 'traceobj' is freed */
70297051
_PyThreadState_UpdateTracingState(tstate);
7030-
Py_XDECREF(traceobj);
7031-
70327052
Py_XINCREF(arg);
7053+
Py_XDECREF(traceobj);
70337054
tstate->c_traceobj = arg;
70347055
tstate->c_tracefunc = func;
70357056

70367057
/* Flag that tracing or profiling is turned on */
70377058
_PyThreadState_UpdateTracingState(tstate);
70387059

7060+
reentrant = 0;
70397061
return 0;
70407062
}
70417063

0 commit comments

Comments
 (0)
0