Skip to content

Commit 97e5381

Browse files
authored
Merge pull request #8242 from radarhere/imagemath_options
2 parents 205695d + 7248cde commit 97e5381

File tree

6 files changed

+123
-78
lines changed

6 files changed

+123
-78
lines changed

Tests/test_imagemath_lambda_eval.py

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from __future__ import annotations
22

3+
from typing import Any
4+
5+
import pytest
6+
37
from PIL import Image, ImageMath
48

59

@@ -19,7 +23,7 @@ def pixel(im: Image.Image | int) -> str | int:
1923
A2 = A.resize((2, 2))
2024
B2 = B.resize((2, 2))
2125

22-
images = {"A": A, "B": B, "F": F, "I": I}
26+
images: dict[str, Any] = {"A": A, "B": B, "F": F, "I": I}
2327

2428

2529
def test_sanity() -> None:
@@ -30,104 +34,111 @@ def test_sanity() -> None:
3034
== "I 3"
3135
)
3236
assert (
33-
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images))
37+
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images))
3438
== "I 3"
3539
)
3640
assert (
3741
pixel(
3842
ImageMath.lambda_eval(
39-
lambda args: args["float"](args["A"]) + args["B"], images
43+
lambda args: args["float"](args["A"]) + args["B"], **images
4044
)
4145
)
4246
== "F 3.0"
4347
)
4448
assert (
4549
pixel(
4650
ImageMath.lambda_eval(
47-
lambda args: args["int"](args["float"](args["A"]) + args["B"]), images
51+
lambda args: args["int"](args["float"](args["A"]) + args["B"]), **images
4852
)
4953
)
5054
== "I 3"
5155
)
5256

5357

58+
def test_options_deprecated() -> None:
59+
with pytest.warns(DeprecationWarning):
60+
assert ImageMath.lambda_eval(lambda args: 1, images) == 1
61+
62+
5463
def test_ops() -> None:
55-
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, images)) == "I -1"
64+
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, **images)) == "I -1"
5665

5766
assert (
58-
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images))
67+
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images))
5968
== "I 3"
6069
)
6170
assert (
62-
pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], images))
71+
pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], **images))
6372
== "I -1"
6473
)
6574
assert (
66-
pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], images))
75+
pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], **images))
6776
== "I 2"
6877
)
6978
assert (
70-
pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], images))
79+
pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], **images))
7180
== "I 0"
7281
)
73-
assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, images)) == "I 4"
82+
assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, **images)) == "I 4"
7483
assert (
75-
pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, images))
84+
pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, **images))
7685
== "I 2147483647"
7786
)
7887

7988
assert (
8089
pixel(
8190
ImageMath.lambda_eval(
82-
lambda args: args["float"](args["A"]) + args["B"], images
91+
lambda args: args["float"](args["A"]) + args["B"], **images
8392
)
8493
)
8594
== "F 3.0"
8695
)
8796
assert (
8897
pixel(
8998
ImageMath.lambda_eval(
90-
lambda args: args["float"](args["A"]) - args["B"], images
99+
lambda args: args["float"](args["A"]) - args["B"], **images
91100
)
92101
)
93102
== "F -1.0"
94103
)
95104
assert (
96105
pixel(
97106
ImageMath.lambda_eval(
98-
lambda args: args["float"](args["A"]) * args["B"], images
107+
lambda args: args["float"](args["A"]) * args["B"], **images
99108
)
100109
)
101110
== "F 2.0"
102111
)
103112
assert (
104113
pixel(
105114
ImageMath.lambda_eval(
106-
lambda args: args["float"](args["A"]) / args["B"], images
115+
lambda args: args["float"](args["A"]) / args["B"], **images
107116
)
108117
)
109118
== "F 0.5"
110119
)
111120
assert (
112-
pixel(ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, images))
121+
pixel(
122+
ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, **images)
123+
)
113124
== "F 4.0"
114125
)
115126
assert (
116127
pixel(
117-
ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, images)
128+
ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, **images)
118129
)
119130
== "F 8589934592.0"
120131
)
121132

122133

123134
def test_logical() -> None:
124-
assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], images)) == 0
135+
assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], **images)) == 0
125136
assert (
126-
pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], images))
137+
pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], **images))
127138
== "L 2"
128139
)
129140
assert (
130-
pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], images))
141+
pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], **images))
131142
== "L 1"
132143
)
133144

@@ -136,23 +147,23 @@ def test_convert() -> None:
136147
assert (
137148
pixel(
138149
ImageMath.lambda_eval(
139-
lambda args: args["convert"](args["A"] + args["B"], "L"), images
150+
lambda args: args["convert"](args["A"] + args["B"], "L"), **images
140151
)
141152
)
142153
== "L 3"
143154
)
144155
assert (
145156
pixel(
146157
ImageMath.lambda_eval(
147-
lambda args: args["convert"](args["A"] + args["B"], "1"), images
158+
lambda args: args["convert"](args["A"] + args["B"], "1"), **images
148159
)
149160
)
150161
== "1 0"
151162
)
152163
assert (
153164
pixel(
154165
ImageMath.lambda_eval(
155-
lambda args: args["convert"](args["A"] + args["B"], "RGB"), images
166+
lambda args: args["convert"](args["A"] + args["B"], "RGB"), **images
156167
)
157168
)
158169
== "RGB (3, 3, 3)"
@@ -163,21 +174,21 @@ def test_compare() -> None:
163174
assert (
164175
pixel(
165176
ImageMath.lambda_eval(
166-
lambda args: args["min"](args["A"], args["B"]), images
177+
lambda args: args["min"](args["A"], args["B"]), **images
167178
)
168179
)
169180
== "I 1"
170181
)
171182
assert (
172183
pixel(
173184
ImageMath.lambda_eval(
174-
lambda args: args["max"](args["A"], args["B"]), images
185+
lambda args: args["max"](args["A"], args["B"]), **images
175186
)
176187
)
177188
== "I 2"
178189
)
179-
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, images)) == "I 1"
180-
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, images)) == "I 0"
190+
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, **images)) == "I 1"
191+
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, **images)) == "I 0"
181192

182193

183194
def test_one_image_larger() -> None:

Tests/test_imagemath_unsafe_eval.py

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from typing import Any
4+
35
import pytest
46

57
from PIL import Image, ImageMath
@@ -21,40 +23,45 @@ def pixel(im: Image.Image | int) -> str | int:
2123
A2 = A.resize((2, 2))
2224
B2 = B.resize((2, 2))
2325

24-
images = {"A": A, "B": B, "F": F, "I": I}
26+
images: dict[str, Any] = {"A": A, "B": B, "F": F, "I": I}
2527

2628

2729
def test_sanity() -> None:
2830
assert ImageMath.unsafe_eval("1") == 1
2931
assert ImageMath.unsafe_eval("1+A", A=2) == 3
3032
assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B)) == "I 3"
31-
assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3"
32-
assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0"
33-
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", images)) == "I 3"
33+
assert pixel(ImageMath.unsafe_eval("A+B", **images)) == "I 3"
34+
assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0"
35+
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", **images)) == "I 3"
3436

3537

3638
def test_eval_deprecated() -> None:
3739
with pytest.warns(DeprecationWarning):
3840
assert ImageMath.eval("1") == 1
3941

4042

43+
def test_options_deprecated() -> None:
44+
with pytest.warns(DeprecationWarning):
45+
assert ImageMath.unsafe_eval("1", images) == 1
46+
47+
4148
def test_ops() -> None:
42-
assert pixel(ImageMath.unsafe_eval("-A", images)) == "I -1"
43-
assert pixel(ImageMath.unsafe_eval("+B", images)) == "L 2"
49+
assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1"
50+
assert pixel(ImageMath.unsafe_eval("+B", **images)) == "L 2"
4451

45-
assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3"
46-
assert pixel(ImageMath.unsafe_eval("A-B", images)) == "I -1"
47-
assert pixel(ImageMath.unsafe_eval("A*B", images)) == "I 2"
48-
assert pixel(ImageMath.unsafe_eval("A/B", images)) == "I 0"
49-
assert pixel(ImageMath.unsafe_eval("B**2", images)) == "I 4"
50-
assert pixel(ImageMath.unsafe_eval("B**33", images)) == "I 2147483647"
52+
assert pixel(ImageMath.unsafe_eval("A+B", **images)) == "I 3"
53+
assert pixel(ImageMath.unsafe_eval("A-B", **images)) == "I -1"
54+
assert pixel(ImageMath.unsafe_eval("A*B", **images)) == "I 2"
55+
assert pixel(ImageMath.unsafe_eval("A/B", **images)) == "I 0"
56+
assert pixel(ImageMath.unsafe_eval("B**2", **images)) == "I 4"
57+
assert pixel(ImageMath.unsafe_eval("B**33", **images)) == "I 2147483647"
5158

52-
assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0"
53-
assert pixel(ImageMath.unsafe_eval("float(A)-B", images)) == "F -1.0"
54-
assert pixel(ImageMath.unsafe_eval("float(A)*B", images)) == "F 2.0"
55-
assert pixel(ImageMath.unsafe_eval("float(A)/B", images)) == "F 0.5"
56-
assert pixel(ImageMath.unsafe_eval("float(B)**2", images)) == "F 4.0"
57-
assert pixel(ImageMath.unsafe_eval("float(B)**33", images)) == "F 8589934592.0"
59+
assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0"
60+
assert pixel(ImageMath.unsafe_eval("float(A)-B", **images)) == "F -1.0"
61+
assert pixel(ImageMath.unsafe_eval("float(A)*B", **images)) == "F 2.0"
62+
assert pixel(ImageMath.unsafe_eval("float(A)/B", **images)) == "F 0.5"
63+
assert pixel(ImageMath.unsafe_eval("float(B)**2", **images)) == "F 4.0"
64+
assert pixel(ImageMath.unsafe_eval("float(B)**33", **images)) == "F 8589934592.0"
5865

5966

6067
@pytest.mark.parametrize(
@@ -72,33 +79,33 @@ def test_prevent_exec(expression: str) -> None:
7279

7380
def test_prevent_double_underscores() -> None:
7481
with pytest.raises(ValueError):
75-
ImageMath.unsafe_eval("1", {"__": None})
82+
ImageMath.unsafe_eval("1", __=None)
7683

7784

7885
def test_prevent_builtins() -> None:
7986
with pytest.raises(ValueError):
80-
ImageMath.unsafe_eval("(lambda: exec('exit()'))()", {"exec": None})
87+
ImageMath.unsafe_eval("(lambda: exec('exit()'))()", exec=None)
8188

8289

8390
def test_logical() -> None:
84-
assert pixel(ImageMath.unsafe_eval("not A", images)) == 0
85-
assert pixel(ImageMath.unsafe_eval("A and B", images)) == "L 2"
86-
assert pixel(ImageMath.unsafe_eval("A or B", images)) == "L 1"
91+
assert pixel(ImageMath.unsafe_eval("not A", **images)) == 0
92+
assert pixel(ImageMath.unsafe_eval("A and B", **images)) == "L 2"
93+
assert pixel(ImageMath.unsafe_eval("A or B", **images)) == "L 1"
8794

8895

8996
def test_convert() -> None:
90-
assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", images)) == "L 3"
91-
assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", images)) == "1 0"
97+
assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", **images)) == "L 3"
98+
assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", **images)) == "1 0"
9299
assert (
93-
pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", images)) == "RGB (3, 3, 3)"
100+
pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", **images)) == "RGB (3, 3, 3)"
94101
)
95102

96103

97104
def test_compare() -> None:
98-
assert pixel(ImageMath.unsafe_eval("min(A, B)", images)) == "I 1"
99-
assert pixel(ImageMath.unsafe_eval("max(A, B)", images)) == "I 2"
100-
assert pixel(ImageMath.unsafe_eval("A == 1", images)) == "I 1"
101-
assert pixel(ImageMath.unsafe_eval("A == 2", images)) == "I 0"
105+
assert pixel(ImageMath.unsafe_eval("min(A, B)", **images)) == "I 1"
106+
assert pixel(ImageMath.unsafe_eval("max(A, B)", **images)) == "I 2"
107+
assert pixel(ImageMath.unsafe_eval("A == 1", **images)) == "I 1"
108+
assert pixel(ImageMath.unsafe_eval("A == 2", **images)) == "I 0"
102109

103110

104111
def test_one_image_larger() -> None:

docs/deprecations.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,15 @@ ImageDraw.getdraw hints parameter
109109

110110
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated.
111111

112+
ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter
113+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114+
115+
.. deprecated:: 11.0.0
116+
117+
The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and
118+
:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more keyword
119+
arguments can be used instead.
120+
112121
Removed features
113122
----------------
114123

docs/reference/ImageMath.rst

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,21 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
3131
b=im2
3232
)
3333

34-
.. py:function:: lambda_eval(expression, options)
34+
.. py:function:: lambda_eval(expression, options, **kw)
3535
3636
Returns the result of an image function.
3737

3838
:param expression: A function that receives a dictionary.
39-
:param options: Values to add to the function's dictionary, mapping image
40-
names to Image instances. You can use one or more keyword
41-
arguments instead of a dictionary, as shown in the above
42-
example. Note that the names must be valid Python
43-
identifiers.
39+
:param options: Values to add to the function's dictionary. Note that the names
40+
must be valid Python identifiers. Deprecated.
41+
You can instead use one or more keyword arguments, as
42+
shown in the above example.
43+
:param \**kw: Values to add to the function's dictionary, mapping image names to
44+
Image instances.
4445
:return: An image, an integer value, a floating point value,
4546
or a pixel tuple, depending on the expression.
4647

47-
.. py:function:: unsafe_eval(expression, options)
48+
.. py:function:: unsafe_eval(expression, options, **kw)
4849
4950
Evaluates an image expression.
5051

@@ -61,11 +62,12 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
6162
:param expression: A string which uses the standard Python expression
6263
syntax. In addition to the standard operators, you can
6364
also use the functions described below.
64-
:param options: Values to add to the function's dictionary, mapping image
65-
names to Image instances. You can use one or more keyword
66-
arguments instead of a dictionary, as shown in the above
67-
example. Note that the names must be valid Python
68-
identifiers.
65+
:param options: Values to add to the evaluation context. Note that the names must
66+
be valid Python identifiers. Deprecated.
67+
You can instead use one or more keyword arguments, as
68+
shown in the above example.
69+
:param \**kw: Values to add to the evaluation context, mapping image names to Image
70+
instances.
6971
:return: An image, an integer value, a floating point value,
7072
or a pixel tuple, depending on the expression.
7173

docs/releasenotes/11.0.0.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@ similarly removed.
4343
Deprecations
4444
============
4545

46-
TODO
47-
^^^^
46+
ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter
47+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4848

49-
TODO
49+
The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and
50+
:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more
51+
keyword arguments can be used instead.
5052

5153
API Changes
5254
===========

0 commit comments

Comments
 (0)