Миграция на новый движок Python-to-.NET в версии 26.8
Введение
Версия 26.8 заменяет движок, который соединяет Python с .NET. Примитивы рисования перемещены в модуль aspose.slides.
Перейдите сразу к У меня ошибка, если после обновления у вас возникли проблемы.
Примитивы рисования перемещены в aspose.slides
Перемещено семь типов. Они сохраняют свои имена, аргументы и поведение:
| Тип | До 26.8 | 26.8 и позже |
|---|---|---|
| Point | aspose.pydrawing.Point |
aspose.slides.Point |
| PointF | aspose.pydrawing.PointF |
aspose.slides.PointF |
| Size | aspose.pydrawing.Size |
aspose.slides.Size |
| SizeF | aspose.pydrawing.SizeF |
aspose.slides.SizeF |
| Rectangle | aspose.pydrawing.Rectangle |
aspose.slides.Rectangle |
| RectangleF | aspose.pydrawing.RectangleF |
aspose.slides.RectangleF |
| Color | aspose.pydrawing.Color |
aspose.slides.Color |
Эти семь типов составляли весь оставшийся контент aspose.pydrawing. Как только вы перенаправите их, вашему коду больше не нужно ссылаться на aspose.pydrawing, и все импорты этого модуля можно удалить. Это также упрощает проверку результата — смотрите Проверка миграции.
Устаревший код:
import aspose.slides as slides
import aspose.pydrawing as drawing
with slides.Presentation() as presentation:
slide = presentation.slides[0]
shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 50, 50, 200, 100)
shape.fill_format.fill_type = slides.FillType.SOLID
shape.fill_format.solid_fill_color.color = drawing.Color.red
with slide.get_image(drawing.Size(1920, 1080)) as slide_image:
slide_image.save("slide1.jpeg", slides.ImageFormat.JPEG)
Версия 26.8:
import aspose.slides as slides
with slides.Presentation() as presentation:
slide = presentation.slides[0]
shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 50, 50, 200, 100)
shape.fill_format.fill_type = slides.FillType.SOLID
shape.fill_format.solid_fill_color.color = slides.Color.red
with slide.get_image(slides.Size(1920, 1080)) as slide_image:
slide_image.save("slide1.jpeg", slides.ImageFormat.JPEG)
Форма импорта from изменяется так же:
# Устаревший код
from aspose.pydrawing import Color, Point
# Версия 26.8
from aspose.slides import Color, Point
Исправление ошибки импорта
Найдите трассировку в первом столбце.
| Ошибка | Причина | Исправление |
|---|---|---|
AttributeError: module 'aspose.pydrawing' has no attribute 'Color' (или Point, Rectangle и т.п.) |
Пакет версии 26.8, а код всё ещё ссылается на старый модуль | Обновите ваш код |
ImportError: cannot import name 'Color' from 'aspose.pydrawing' |
Та же причина, форма импорта from |
Обновите ваш код |
ModuleNotFoundError: No module named 'aspose.pydrawing' |
Модуль и все его семь типов перемещены в aspose.slides |
Обновите ваш код, затем удалите импорт aspose.pydrawing |
ImportError: cannot import name 'Color' from 'aspose.slides' |
Код был мигрирован, но установленный пакет версии 26.7 или старее | pip install --upgrade aspose.slides |
TypeError on a color, point, or size argument |
Значение, созданное из aspose.pydrawing, передаётся в новый API |
Создайте значение также из aspose.slides |
Обновите ваш код
Поскольку aspose.pydrawing не содержит ничего, кроме семи перемещённых типов, миграция представляет собой переименование модуля. Все формы импорта покрываются этим единственным переименованием, включая псевдонимы:
# Устаревший код
import aspose.pydrawing as drawing
color = drawing.Color.red
# Версия 26.8 - псевдоним продолжает работать
import aspose.slides as drawing
color = drawing.Color.red
Это корректно в любой области видимости, включая тело функции, потому что псевдоним остаётся привязанным именно там, где был привязан ранее. Единственный недостаток — вводящее в заблуждение имя, поэтому рассмотрите возможность сделать намерение явным:
import aspose.slides as slides
color = slides.Color.red
Выберите подход, соответствующий размеру вашей кодовой базы.
Заменить вручную
Для нескольких файлов найдите aspose.pydrawing и замените его на aspose.slides, затем удалите любой импорт, который больше не нужен.
Заменить с помощью команды оболочки
Это простая текстовая замена, поэтому она также затрагивает вхождения внутри строк и комментариев. Обе команды создают копию .bak каждого изменённого файла.
Linux:
grep -rlZ --include='*.py' 'aspose\.pydrawing' . \
| xargs -0 -r sed -i.bak 's/aspose\.pydrawing/aspose.slides/g'
В macOS используйте sed -i '' вместо sed -i.bak, или установите GNU sed как gsed.
Windows PowerShell:
Get-ChildItem -Recurse -Filter *.py | ForEach-Object {
$t = Get-Content $_ -Raw
$new = $t -replace 'aspose\.pydrawing', 'aspose.slides'
if ($new -ne $t) {
Copy-Item $_.FullName "$($_.FullName).bak"
Set-Content $_.FullName $new -NoNewline
$_.FullName
}
}
Чтобы откатить изменения в Linux или macOS:
find . -name '*.py.bak' -exec sh -c 'mv "$1" "${1%.bak}"' _ {} \;
Чтобы откатить изменения в Windows:
Get-ChildItem -Recurse -Filter *.py.bak | ForEach-Object {
Move-Item $_.FullName ($_.FullName -replace '\.bak$', '') -Force
}
Заменить с помощью скрипта Python
Та же переименовка, портативна для Linux, macOS и Windows. Скрипт принимает путь в качестве аргумента и показывает изменения, если не указан --write. Добавьте --backup, чтобы сохранять копию .bak каждого изменённого файла. Сохраните его под любым именем — сообщение о использовании подхватывает имя во время выполнения.
"""Переименовать aspose.pydrawing в aspose.slides. Замена простым текстом.
python <this script> src/ # предварительный просмотр
python <this script> src/ --write # применение
python <this script> src/ --write --backup # применение, сохраняются копии .bak
"""
import sys
from pathlib import Path
W = "--write" in sys.argv
B = "--backup" in sys.argv
ROOT = next((a for a in sys.argv[1:] if not a.startswith("-")), None)
if ROOT is None:
sys.exit(f"usage: python {Path(sys.argv[0]).name} <path> [--write] [--backup]")
root = Path(ROOT)
if not root.exists():
sys.exit(f"no such path: {root}")
files = [root] if root.is_file() else root.rglob("*.py")
changed = 0
for p in files:
if {".venv", "venv", "__pycache__", ".git"} & set(p.parts):
continue
s = p.read_text(encoding="utf-8")
n = s.replace("aspose.pydrawing", "aspose.slides")
if n == s:
continue
changed += 1
print(("wrote " if W else "would change ") + str(p))
if W:
if B:
p.with_suffix(p.suffix + ".bak").write_text(s, encoding="utf-8")
p.write_text(n, encoding="utf-8")
print(f"{changed} file(s) {'changed' if W else 'to change'}"
+ ("" if W or not changed else "; rerun with --write to apply"))
Типичный запуск выглядит так:
$ python migrate.py src/
would change src/render.py
would change src/export/slides.py
2 file(s) to change; rerun with --write to apply
$ python migrate.py src/ --write --backup
wrote src/render.py
wrote src/export/slides.py
2 file(s) changed
Путь может быть каталогом, который обходится рекурсивно, или отдельным файлом .py.
Заменить скриптом на основе AST
Рекомендуется для больших кодовых баз. Этот скрипт выполняет ту же переименовку, но сначала парсит каждый файл, поэтому он никогда не меняет вхождения внутри строк, комментариев или строк документации.
Поскольку он переименовывает модуль на месте и оставляет псевдонимы нетронутыми, все формы импорта обрабатываются без специальных случаев: import aspose.pydrawing, import aspose.pydrawing as X, from aspose.pydrawing import Color, from aspose.pydrawing import Color as C, многострочные импортные выражения в скобках, импорты внутри функций и модуль, передаваемый как значение. Он принимает те же флаги --write и --backup.
"""Переименовать aspose.pydrawing в aspose.slides, пропуская строки и комментарии.
python <this script> src/ # предварительный просмотр
python <this script> src/ --write # применить
python <this script> src/ --write --backup # применить, сохраняются копии .bak
"""
import ast, sys
from pathlib import Path
MOD, DST = "aspose.pydrawing", "aspose.slides"
W = "--write" in sys.argv
B = "--backup" in sys.argv
ROOT = next((a for a in sys.argv[1:] if not a.startswith("-")), None)
if ROOT is None:
sys.exit(f"usage: python {Path(sys.argv[0]).name} <path> [--write] [--backup]")
root = Path(ROOT)
if not root.exists():
sys.exit(f"no such path: {root}")
files = [root] if root.is_file() else root.rglob("*.py")
changed = 0
def chain(n):
p = []
while isinstance(n, ast.Attribute):
p.append(n.attr)
n = n.value
return ".".join(reversed(p + [n.id])) if isinstance(n, ast.Name) else None
def fix(src):
tree = ast.parse(src)
off, o = [], 0
for l in src.encode().splitlines(keepends=True):
off.append(o)
o += len(l)
off.append(o)
edits = []
for n in ast.walk(tree):
# импорт aspose.pydrawing [as X] / from aspose.pydrawing import ...
# The module name is renamed in place, so any alias stays bound as before.
if (isinstance(n, ast.Import) and any(a.name == MOD for a in n.names)) or \
(isinstance(n, ast.ImportFrom) and n.module == MOD):
s, e = off[n.lineno - 1], off[n.end_lineno - 1] + n.end_col_offset
edits.append((s, e, src.encode()[s:e].decode().replace(MOD, DST)))
# Любое выражение, ссылающееся на модуль, включая простое `fn(aspose.pydrawing)`.
elif isinstance(n, ast.Attribute) and chain(n) == MOD:
edits.append((off[n.lineno - 1] + n.col_offset,
off[n.end_lineno - 1] + n.end_col_offset, DST))
b = src.encode()
for s, e, r in sorted(edits, reverse=True): # back to front keeps offsets valid
b = b[:s] + r.encode() + b[e:]
return b.decode()
for p in files:
if {".venv", "venv", "__pycache__", ".git"} & set(p.parts):
continue
s = p.read_text(encoding="utf-8")
try:
n = fix(s)
except SyntaxError as e:
print(f"skipped {p}: {e}")
continue
if n != s:
print(("wrote " if W else "would change ") + str(p))
if W:
if B:
p.with_suffix(p.suffix + ".bak").write_text(s, encoding="utf-8")
p.write_text(n, encoding="utf-8")
Оба скрипта идемпотентны: повторный запуск на уже мигрированном коде ничего не меняет.
Проверьте миграцию
Текстовый поиск покажет, осталось ли что‑нибудь:
grep -rn 'aspose\.pydrawing' --include='*.py' --exclude-dir=.venv .
Это быстро, но также находит вхождения внутри строк и комментариев, поэтому чистый код может всё равно дать результаты. Для окончательного ответа используйте проверку ниже. Она сообщает только о реальных ссылках в коде и завершается с ненулевым статусом, если что‑то осталось, что делает её пригодной для шлагбаума сборки.
import ast, sys
from pathlib import Path
MOD = "aspose.pydrawing"
ROOT = next((a for a in sys.argv[1:] if not a.startswith("-")), ".")
def chain(n):
p = []
while isinstance(n, ast.Attribute):
p.append(n.attr)
n = n.value
return ".".join(reversed(p + [n.id])) if isinstance(n, ast.Name) else None
def scan(tree):
for n in ast.walk(tree):
if isinstance(n, ast.Import) and any(a.name == MOD for a in n.names):
yield n.lineno, f"import {MOD}"
elif isinstance(n, ast.ImportFrom) and n.module == MOD:
names = ", ".join(a.name for a in n.names)
yield n.lineno, f"from {MOD} import {names}"
elif isinstance(n, ast.Attribute) and chain(n) == MOD:
yield n.lineno, f"reference to {MOD}"
hits = 0
for p in sorted(Path(ROOT).rglob("*.py")):
if {".venv", "venv", "__pycache__", ".git"} & set(p.parts):
continue
try:
tree = ast.parse(p.read_text(encoding="utf-8"))
except SyntaxError as e:
print(f"skipped {p}: {e}")
continue
for lineno, what in sorted(scan(tree)):
print(f"{p}:{lineno}: {what}")
hits += 1
print("migration complete" if not hits else f"{hits} reference(s) left")
sys.exit(1 if hits else 0)
Запустите её до и после миграции:
$ python verify.py src/
src/render.py:4: from aspose.pydrawing import Color, Point
src/render.py:11: import aspose.pydrawing
src/render.py:12: reference to aspose.pydrawing
3 reference(s) left
$ python migrate.py src/ --write
wrote src/render.py
$ python verify.py src/
migration complete
Наконец, запустите smoke‑тест, который проверяет перемещённые типы:
import aspose.slides as slides
with slides.Presentation() as presentation:
slide = presentation.slides[0]
shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 50, 50, 200, 100)
shape.fill_format.fill_type = slides.FillType.SOLID
shape.fill_format.solid_fill_color.color = slides.Color.red
presentation.save("smoke.pptx", slides.export.SaveFormat.PPTX)
print("OK")
Рекомендованный порядок миграции
- Сохраните базовый вариант. Запустите тесты на текущей версии и сохраните эталонные рендеры. Это позволит позже отделить ошибки миграции от различий в рендеринге.
- Предпросмотр миграции. Запустите один из скриптов без
--writeи просмотрите список файлов, которые он изменит. - Примените и проверьте. Запустите с
--write --backup, затем скрипт проверки и smoke‑тест. - Сравните рендеры с допуском. Переход на сборку .NET 6 может вызвать небольшие различия в тексте и эффектах. Используйте сравнение на основе порога, а не побайтовую проверку.
- Удалите резервные копии. После подтверждения результата удалите файлы
.bak:find . -name '*.py.bak' -deleteв Linux и macOS, илиGet-ChildItem -Recurse -Filter *.py.bak | Remove-Itemв Windows.
Поддержка обеих версий в одной кодовой базе
Если необходимо запускать версии 26.7 и 26.8 из одного исходного кода:
try:
from aspose.slides import Color, Point, Rectangle # 26.8 и позже
except ImportError:
from aspose.pydrawing import Color, Point, Rectangle # 26.7 и ранее
Что не изменилось
- Имена, аргументы и поведение перемещённых примитивов.
- Остальная часть API
aspose.slides. - Лицензирование и способ применения файла лицензии.
- Форматы файлов и поведение при сохранении и загрузке.
- Системные требования для Windows и macOS.
- Отсутствие отдельной установки .NET — среда выполнения по‑прежнему поставляется в комплекте.