mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-03 03:50:09 +03:00
Keep existing behaviour for eval()
This commit is contained in:
parent
828fa507a4
commit
aa30dc506e
|
@ -13,9 +13,9 @@ which reached end-of-life in October 2024.
|
||||||
ImageMath comparison operations
|
ImageMath comparison operations
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Comparison operations in :py:mod:`~PIL.ImageMath` will now return booleans
|
In :py:meth:`~PIL.ImageMath.lambda_eval` and :py:meth:`~PIL.ImageMath.unsafe_eval`,
|
||||||
instead of images. ``==``, ``!=``, ``<``, ``<=``, ``>``, ``>=``, ``equal()`` and
|
comparison operations will now return booleans instead of images. ``==``, ``!=``,
|
||||||
``notequal()`` are all affected::
|
``<``, ``<=``, ``>``, ``>=``, ``equal()`` and ``notequal()`` are all affected::
|
||||||
|
|
||||||
from PIL import Image, ImageMath
|
from PIL import Image, ImageMath
|
||||||
A = Image.new("L", (1, 1))
|
A = Image.new("L", (1, 1))
|
||||||
|
@ -24,6 +24,8 @@ instead of images. ``==``, ``!=``, ``<``, ``<=``, ``>``, ``>=``, ``equal()`` and
|
||||||
# It will now return True
|
# It will now return True
|
||||||
ImageMath.lambda_eval(lambda args: args["A"] == args["A"], A=A))
|
ImageMath.lambda_eval(lambda args: args["A"] == args["A"], A=A))
|
||||||
|
|
||||||
|
The deprecated ``ImageMath.eval()`` remains unchanged.
|
||||||
|
|
||||||
Python 3.12 on macOS <= 10.12
|
Python 3.12 on macOS <= 10.12
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -279,6 +279,30 @@ def lambda_eval(
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _unsafe_eval(
|
||||||
|
expression: str,
|
||||||
|
args: dict[str, Any],
|
||||||
|
) -> Any:
|
||||||
|
compiled_code = compile(expression, "<string>", "eval")
|
||||||
|
|
||||||
|
def scan(code: CodeType) -> None:
|
||||||
|
for const in code.co_consts:
|
||||||
|
if type(const) is type(compiled_code):
|
||||||
|
scan(const)
|
||||||
|
|
||||||
|
for name in code.co_names:
|
||||||
|
if name not in args and name != "abs":
|
||||||
|
msg = f"'{name}' not allowed"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
scan(compiled_code)
|
||||||
|
out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args)
|
||||||
|
try:
|
||||||
|
return out.im
|
||||||
|
except AttributeError:
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def unsafe_eval(
|
def unsafe_eval(
|
||||||
expression: str,
|
expression: str,
|
||||||
options: dict[str, Any] = {},
|
options: dict[str, Any] = {},
|
||||||
|
@ -323,24 +347,64 @@ def unsafe_eval(
|
||||||
if isinstance(v, Image.Image):
|
if isinstance(v, Image.Image):
|
||||||
args[k] = _Operand(v)
|
args[k] = _Operand(v)
|
||||||
|
|
||||||
compiled_code = compile(expression, "<string>", "eval")
|
return _unsafe_eval(expression, args)
|
||||||
|
|
||||||
def scan(code: CodeType) -> None:
|
|
||||||
for const in code.co_consts:
|
|
||||||
if type(const) is type(compiled_code):
|
|
||||||
scan(const)
|
|
||||||
|
|
||||||
for name in code.co_names:
|
class _Operand_Eval(_Operand):
|
||||||
if name not in args and name != "abs":
|
def apply(
|
||||||
msg = f"'{name}' not allowed"
|
self,
|
||||||
raise ValueError(msg)
|
op: str,
|
||||||
|
im1: _Operand | float,
|
||||||
|
im2: _Operand | float | None = None,
|
||||||
|
mode: str | None = None,
|
||||||
|
) -> _Operand:
|
||||||
|
operand = super().apply(op, im1, im2, mode)
|
||||||
|
return _Operand_Eval(operand.im)
|
||||||
|
|
||||||
scan(compiled_code)
|
def __eq__(self, other: _Operand_Eval | float | None) -> _Operand: # type: ignore[override]
|
||||||
out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args)
|
return self.apply("eq", self, other)
|
||||||
try:
|
|
||||||
return out.im
|
def __ne__(self, other: _Operand_Eval | float | None) -> _Operand: # type: ignore[override]
|
||||||
except AttributeError:
|
return self.apply("ne", self, other)
|
||||||
return out
|
|
||||||
|
def __lt__(self, other: _Operand_Eval | float) -> _Operand: # type: ignore[override]
|
||||||
|
return self.apply("lt", self, other)
|
||||||
|
|
||||||
|
def __le__(self, other: _Operand_Eval | float) -> _Operand: # type: ignore[override]
|
||||||
|
return self.apply("le", self, other)
|
||||||
|
|
||||||
|
def __gt__(self, other: _Operand_Eval | float) -> _Operand: # type: ignore[override]
|
||||||
|
return self.apply("gt", self, other)
|
||||||
|
|
||||||
|
def __ge__(self, other: _Operand_Eval | float) -> _Operand: # type: ignore[override]
|
||||||
|
return self.apply("ge", self, other)
|
||||||
|
|
||||||
|
|
||||||
|
def imagemath_int_eval(self: _Operand) -> _Operand:
|
||||||
|
operand = _Operand(self.im.convert("I"))
|
||||||
|
return _Operand_Eval(operand.im)
|
||||||
|
|
||||||
|
|
||||||
|
def imagemath_float_eval(self: _Operand) -> _Operand:
|
||||||
|
operand = _Operand(self.im.convert("F"))
|
||||||
|
return _Operand_Eval(operand.im)
|
||||||
|
|
||||||
|
|
||||||
|
def imagemath_equal_eval(
|
||||||
|
self: _Operand_Eval, other: _Operand_Eval | float | None
|
||||||
|
) -> _Operand:
|
||||||
|
return self.apply("eq", self, other, mode="I")
|
||||||
|
|
||||||
|
|
||||||
|
def imagemath_notequal_eval(
|
||||||
|
self: _Operand_Eval, other: _Operand_Eval | float | None
|
||||||
|
) -> _Operand:
|
||||||
|
return self.apply("ne", self, other, mode="I")
|
||||||
|
|
||||||
|
|
||||||
|
def imagemath_convert_eval(self: _Operand, mode: str) -> _Operand_Eval:
|
||||||
|
operand = _Operand(self.im.convert(mode))
|
||||||
|
return _Operand_Eval(operand.im)
|
||||||
|
|
||||||
|
|
||||||
def eval(
|
def eval(
|
||||||
|
@ -358,7 +422,7 @@ def eval(
|
||||||
can either use a dictionary, or one or more keyword
|
can either use a dictionary, or one or more keyword
|
||||||
arguments.
|
arguments.
|
||||||
:return: The evaluated expression. This is usually an image object, but can
|
:return: The evaluated expression. This is usually an image object, but can
|
||||||
also be an integer, a floating point value, a boolean, or a pixel tuple,
|
also be an integer, a floating point value, or a pixel tuple,
|
||||||
depending on the expression.
|
depending on the expression.
|
||||||
|
|
||||||
.. deprecated:: 10.3.0
|
.. deprecated:: 10.3.0
|
||||||
|
@ -369,4 +433,26 @@ def eval(
|
||||||
12,
|
12,
|
||||||
"ImageMath.lambda_eval or ImageMath.unsafe_eval",
|
"ImageMath.lambda_eval or ImageMath.unsafe_eval",
|
||||||
)
|
)
|
||||||
return unsafe_eval(expression, _dict, **kw)
|
|
||||||
|
# build execution namespace
|
||||||
|
args: dict[str, Any] = {
|
||||||
|
"int": imagemath_int_eval,
|
||||||
|
"float": imagemath_float_eval,
|
||||||
|
"equal": imagemath_equal_eval,
|
||||||
|
"notequal": imagemath_notequal_eval,
|
||||||
|
"min": imagemath_min,
|
||||||
|
"max": imagemath_max,
|
||||||
|
"convert": imagemath_convert_eval,
|
||||||
|
}
|
||||||
|
for k in list(_dict.keys()) + list(kw.keys()):
|
||||||
|
if "__" in k or hasattr(builtins, k):
|
||||||
|
msg = f"'{k}' not allowed"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
args.update(_dict)
|
||||||
|
args.update(kw)
|
||||||
|
for k, v in args.items():
|
||||||
|
if isinstance(v, Image.Image):
|
||||||
|
args[k] = _Operand_Eval(v)
|
||||||
|
|
||||||
|
return _unsafe_eval(expression, args)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user