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

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
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: IntConversionDescriptor = IntConversionD 67E6 escriptor(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 A3E2 +
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
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+
with support.catch_unraisable_exception() as cm:
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