۳۰ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

نحوه نوشتن پسوندهای پایتون در Rust با PyO3

Py03 به شما امکان می دهد سرعت Rust و ایمنی حافظه را با سهولت استفاده Python ترکیب کنید. شروع به نوشتن پسوندهای Rust برای پایتون کنید که درست مانند ماژول‌های معمولی پایتون کار می‌کنند.

Py03 به شما امکان می دهد سرعت Rust و ایمنی حافظه را با سهولت استفاده Python ترکیب کنید. شروع به نوشتن پسوندهای Rust برای پایتون کنید که درست مانند ماژول‌های معمولی پایتون کار می‌کنند.

هر زبان برنامه نویسی دارای نقاط قوت و ضعف است. Python بسیاری از قراردادهای برنامه نویسی راحت را ارائه می دهد اما از نظر محاسباتی کند است. Rust به شما سرعتی در سطح ماشین و ایمنی قوی حافظه می دهد، اما پیچیده تر از پایتون است. خبر خوب این است که می‌توانید این دو زبان را با هم ترکیب کنید و از سهولت استفاده پایتون برای مهار سرعت و قدرت Rust استفاده کنید. پروژه PyO3 به شما امکان می‌دهد با نوشتن برنامه‌های افزودنی Python در Rust از بهترین‌های هر دو دنیا بهره ببرید.

با PyO3، کد Rust را می‌نویسید، نحوه ارتباط آن با پایتون را نشان می‌دهید، سپس Rust را کامپایل می‌کنید و آن را مستقیماً در محیط مجازی پایتون مستقر می‌کنید، جایی که می‌توانید بدون مزاحمت از آن با کد پایتون خود استفاده کنید.< /p>

این مقاله یک تور سریع از نحوه عملکرد PyO3 است. شما یاد خواهید گرفت که چگونه یک پروژه Python را با PyO3 create راه اندازی کنید، چگونه توابع Rust را به عنوان یک ماژول Python در معرض دید قرار دهید، و چگونه اشیاء Python مانند کلاس ها و استثناها را در Rust ایجاد کنید.

تنظیم پروژه پایتون با PyO3

برای شروع ایجاد پروژه PyO3، باید با محیط مجازی پایتون یا venv شروع کنید. این فقط به خاطر سازماندهی پروژه پایتون نیست، بلکه برای ایجاد مکانی برای نصب جعبه Rust که با PyO3 می‌سازید نیز می‌باشد. (اگر قبلاً زنجیره ابزار Rust را نصب نکرده‌اید، اکنون این کار را انجام دهید.)

سازمان دقیق دایرکتوری های پروژه می تواند متفاوت باشد. در نمونه‌های نشان داده شده در اسناد PyO3، پروژه PyO3 در یک دایرکتوری که شامل پروژه پایتون و محیط مجازی آن است. روش دیگر ایجاد دو زیرشاخه است: یکی برای پروژه پایتون و venv آن و دیگری برای پروژه PyO3. رویکرد دوم سازماندهی امور را آسان‌تر می‌کند، بنابراین ما این کار را انجام خواهیم داد:

  1. یک دایرکتوری جدید برای نگهداری پروژه های Python و Rust خود ایجاد کنید. ما آنها را به ترتیب pyexample و rustexample می نامیم.
  2. .

  3. در فهرست pyexample، محیط مجازی خود را ایجاد کرده و آن را فعال کنید. در نهایت تعدادی کد پایتون را در اینجا اضافه می کنیم. مهم است که تمام کارهای خود را با کد Rust و Python در venv فعال شده خود انجام دهید.
  4. در venv فعال شده خود، بسته maturin را با pip install maturin نصب کنید. maturin ابزاری است که ما برای ساختن پروژه Rust خود و ادغام آن با پروژه Python خود استفاده می کنیم.
  5. به فهرست راهنمای پروژه Rust بروید و maturin init را تایپ کنید. وقتی از شما پرسیده شد که چه پیوندهایی را انتخاب کنید، pyo3 را انتخاب کنید.
  6. سپس

  7. maturin یک پروژه Rust را در آن دایرکتوری ایجاد می کند که با فایل Cargo.toml تکمیل می شود که پروژه را توصیف می کند. توجه داشته باشید که پروژه به همان نام دایرکتوری که در آن قرار گرفته است داده می شود. در این حالت rustexample خواهد بود.
WSO2 PaaS با کد پایین و بومی ابری را برای توسعه API راه‌اندازی می‌کند

عملکردهای Rust در پروژه PyO3

وقتی داربست پروژه PyO3 را با maturin ایجاد می‌کنید، به طور خودکار یک فایل خرد کد در src/lib.rs ایجاد می‌کند. این خرد حاوی کدی برای دو تابع است – یک تابع نمونه واحد، sum_as_string، و یک تابع به نام پروژه شما که سایر توابع را به عنوان یک ماژول پایتون نمایش می دهد.

در اینجا یک نمونه تابع sum_as_string آورده شده است:

#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

ماکرو #[pyfunction]، از جعبه pyo3، نشان می‌دهد که یک تابع معین باید با یک رابط به پایتون پیچیده شود. آرگومان‌هایی که می‌گیرد و نتایجی که برمی‌گرداند همگی به طور خودکار از و به انواع پایتون ترجمه می‌شوند. (همچنین می‌توان انواع بومی پایتون را برای دریافت و بازگشت مشخص کرد؛ در ادامه در این مورد بیشتر توضیح خواهیم داد.)

در این مثال، sum_as_string دو آرگومان می گیرد که باید به یک عدد صحیح ۶۴ بیتی Rust-native ترجمه شوند. برای چنین موردی، یک برنامه پایتون در دو نوع int پایتون ارسال می‌شود. اما حتی در این صورت، باید مراقب باشید: آن نوع int باید به عنوان یک عدد صحیح ۶۴ بیتی قابل بیان باشد. اگر ۲**۶۵ را به این تابع ارسال کنید، با خطای زمان اجرا مواجه می شوید زیرا عددی به این بزرگی را نمی توان به عنوان یک عدد صحیح ۶۴ بیتی بیان کرد. (ما در مورد راه دیگری برای دور زدن این محدودیت بعدا صحبت خواهیم کرد.)

مقدار برگشتی برای این تابع یک نوع بومی پایتون است—یک شی PyResult که حاوی رشته است. آخرین خط تابع یک String را برمی‌گرداند که پوشش PyO3 به طور خودکار به عنوان یک شی پایتون می‌پیچد.

همچنین این امکان وجود دارد که pyfunction امضای یک تابع معین را توصیف کند می‌پذیرد—به‌عنوان مثال، اگر می‌خواهید چندین آرگومان موقعیتی یا کلیدواژه را بپذیرید.

انواع پایتون و Rust در توابع PyO3

باید با نحوه نگاشت انواع Python و Rust به یکدیگر آشنا شوید، و برخی از انواع را انتخاب کنید.

تابع شما می تواند انواع Rust را بپذیرد که به طور خودکار از انواع پایتون تبدیل می شوند، اما این بدان معناست که کانتینرهایی مانند دیکشنری ها باید کاملاً در مرز تابع تبدیل شوند. اگر از کنار یک شی بزرگ، مانند لیستی با هزاران شی، عبور کنید، ممکن است کند باشد. برای این منظور، اگر یک مقدار واحد، مانند یک عدد صحیح یا شناور، یا اشیایی ظرفی که می‌دانید عناصر زیادی ندارند، ارسال می‌کنید، بهتر است انجام شود.

همچنین می‌توانید انواع Python-native را در مرز تابع بپذیرید و از روش‌های Python-native برای دسترسی به آنها در داخل تابع استفاده کنید. این سرعت در مرز تابع سریعتر است، بنابراین اگر از اشیاء ظرف با تعداد نامشخصی از عناصر عبور می کنید، انتخاب بهتری است. اما دسترسی به اشیاء کانتینر مستلزم استفاده از روش‌های بومی پایتون است که توسط GIL (قفل مترجم جهانی) محدود شده‌اند، بنابراین برای سرعت باید هر مقداری را از شی به انواع Rust-Native تبدیل کنید.

ماژول های پایتون در پروژه PyO3

توابع

pyfunction به خودی خود مستقیماً از طریق یک ماژول در معرض پایتون قرار نمی گیرند. برای انجام این کار، باید یک شی ماژول پایتون را از طریق PyO3 ایجاد کنیم و توابع pyfunction خود را از طریق آن در معرض دید قرار دهیم.

چرا باید از داکر و کانتینرها استفاده کنید؟

فایل lib.rs قبلاً یک نسخه اصلی برای شما ایجاد شده است که به شکل زیر است:


#[pymodule]
fn rustexample(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}

ماکرو pymodule نشان می‌دهد که تابع مورد نظر به عنوان یک ماژول در معرض پایتون قرار می‌گیرد، با همان نام (rustexample). ما هر یک از توابع تعریف شده قبلی را می گیریم و آنها را از طریق ماژول با استفاده از روش .add_function در معرض دید قرار می دهیم. این ممکن است کمی سخت به نظر برسد، اما در هنگام ایجاد ماژول انعطاف‌پذیری را فراهم می‌کند—مثلاً با اجازه دادن به در صورت نیاز، زیر ماژول‌ها را ایجاد کنید.

کامپایل پروژه PyO3

کامپایل کردن پروژه PyO3 برای استفاده در پایتون به طور کلی بسیار ساده است:

  1. اگر قبلاً این کار را نکرده‌اید، محیط مجازی را که maturin را در آن نصب کرده‌اید، فعال کنید.
  2. پروژه Rust خود را به عنوان فهرست کاری فعلی خود تنظیم کنید.
  3. فرمان maturin dev را برای ساخت پروژه خود اجرا کنید.

نتایج باید چیزی شبیه به این باشد:


(.env) PS D:\Dev\pyo3-article\rustexample> maturin dev -r
    Updating crates.io index
    [ ... snip ... ]
  Downloaded 10 crates (3.2 MB) in 2.50s (largest was `windows-sys` at 2.6 MB)
🔗 Found pyo3 bindings
🐍 Found CPython 3.11 at D:\Dev\pyo3-article\pyexample\.env\Scripts\python.exe
   [ ... snip ... ]
   Compiling rustexample v0.1.0 (D:\Dev\pyo3-article\rustexample)
    Finished release [optimized] target(s) in 10.86s
📦 Built wheel for CPython 3.11 to [ ... snip ...]
\.tmpUbXtlF\rustexample-0.1.0-cp311-none-win_amd64.whl 🛠 Installed rustexample-0.1.0

به‌طور پیش‌فرض، maturin کد Rust را در حالت پیش‌انتشار می‌سازد. در این مثال، ما پرچم -r را به maturin دادیم تا Rust را در حالت انتشار بسازیم.

کد حاصل باید مستقیماً در محیط مجازی شما نصب شود و شما باید بتوانید آن را با pip list مشاهده کنید:


(.env) PS D:\Dev\pyo3-article\rustexample> pip list
Package     Version
----------- -------
maturin     0.14.12
pip         23.0
rustexample 0.1.0
setuptools  67.1.0

برای آزمایش بسته ساخته شده خود، نمونه Python را در محیط مجازی خود راه اندازی کنید و سعی کنید بسته را وارد کنید:


Python 3.11.1 (tags/v3.11.1:a7a450f, Dec  6 2022, 19:58:39)
[MSC v.1934 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import rustexample >>> rustexample <module 'rustexample' from 'D:\\Dev\\pyo3-article\\pyexample\\
.env\\Lib\\site-packages\\rustexample\\__init__.py'>

این بسته باید مانند هر بسته پایتون دیگری وارد و اجرا شود.

PYO3 پیشرفته

تا کنون، شما فقط اصول اولیه کاری که PyO3 می تواند انجام دهد را دیده اید. اما PyO3 از بسیاری دیگر از ویژگی‌های پایتون پشتیبانی می‌کند، که احتمالاً می‌خواهید با کد Rust ارتباط برقرار کنید.

پشتیبانی عدد صحیح بزرگ

پایتون به طور خودکار اعداد صحیح را به “اعداد صحیح بزرگ” یا اعداد صحیح با اندازه دلخواه تبدیل می کند. اگر می‌خواهید یک شی عدد صحیح پایتون را به یک تابع PyO3 ارسال کنید و از آن به عنوان یک عدد صحیح بزرگ بومی Rust استفاده کنید، می‌توانید این کار را با pyo3::num_bigint، که از num_bigint. فقط به یاد داشته باشید که اعداد صحیح بزرگ ممکن است از همه عملیات پشتیبانی نکنند.

موازی

همانند Cython، هر کد Rust خالصی که زمان اجرای Python را لمس نمی کند، می تواند خارج از Python GIL اجرا شود. شما می توانید چنین تابعی را در روش Python::allow_threads بپیچید تا GIL در حین اجرا به حالت تعلیق درآید. باز هم، این باید صرفاً کد Rust با بدون اشیاء پایتون در حال استفاده باشد.

مدیر عامل آزول آینده هوش مصنوعی جاوا را درخشان می بیند

نگهداری GIL با طول عمر Rust

PyO3 راهی برای نگه داشتن GIL از طریق مکانیسم طول عمر Rust ارائه می‌کند که به شما این امکان را می‌دهد راهی برای دسترسی قابل تغییر یا اشتراکی به اشیاء پایتون. انواع شیء مختلف قوانین GIL متفاوتی دارند.

می‌توانید با نوع PyAny به یک شیء عمومی پایتون دسترسی پیدا کنید، یا می‌توانید از انواع دقیق‌تر مانند PyTuple یا PyList استفاده کنید. اینها کمی سریع‌تر هستند، زیرا PyO3 می‌تواند کد مخصوص آن نوع را تولید کند. مهم نیست از کدام نوع استفاده می کنید، باید فرض کنید که باید GIL را برای تمام مدتی که با شی کار می کنید نگه دارید.

اگر می‌خواهید به یک شی پایتون در خارج از GIL ارجاع دهید – برای مثال، اگر یک مرجع شی پایتون را در ساختار Rust ذخیره می‌کنید، می‌توانید از Py یا < استفاده کنید. انواع code>PyObject (در اصل Py).

برای یک شی Rust پیچیده شده در یک شی پایتون (GIL-holding)—بله، این امکان پذیر است—شما می توانید از PyCell استفاده کنید. اگر بخواهید به شی Rust دسترسی داشته باشید و قوانین مرجع Rust را حفظ کنید، معمولاً این کار را انجام می دهید. در آن صورت، رفتار شیء پیچیده پایتون با کاری که می‌خواهید انجام دهید تداخلی ایجاد نمی‌کند. به همین ترتیب، می‌توانید از PyRef و PyRefMut برای دریافت ارجاعات استاتیک و قابل تغییر به چنین اشیایی استفاده کنید.

کلاس ها

می‌توانید کلاس‌های Python را در ماژول‌های PyO3 تعریف کنید. اگر ویژگی #[pyclass] را به یک ساختار Rust یا یک enum بدون فیلد اضافه کنید، می‌توان آنها را به عنوان ساختار داده پایه برای یک کلاس در نظر گرفت. برای افزودن متدهای نمونه، باید از #[pymethods] با یک بلوک impl برای کلاسی که حاوی توابع برای استفاده به عنوان متدها است استفاده کنید. همچنین می‌توان متدهای کلاس، ویژگی‌ها، روش‌های جادویی، اسلات‌ها، کلاس‌های قابل فراخوانی و بسیاری از رفتارهای رایج دیگر را ایجاد کرد.

به خاطر داشته باشید که رفتارهای Rust محدودیت هایی را ایجاد می کند. شما نمی توانید پارامترهای طول عمر را برای کلاس ها ارائه دهید. همه آنها باید به عنوان 'static کار کنند. همچنین نمی توانید از پارامترهای عمومی در انواعی که به عنوان کلاس های پایتون استفاده می شوند استفاده کنید.

استثناها

استثناهای پایتون در PyO3 را می‌توان در کد Rust ایجاد کرد با create_exception!< ماکرو /code>، یا با وارد کردن یکی از چند استثنای استاندارد از پیش تعریف شده با ماکرو import_exception!. توجه داشته باشید که مانند توابع، باید به صورت دستی استثناهای ایجاد شده توسط PyO3 را به یک ماژول اضافه کنید تا آنها را برای Python در دسترس قرار دهید.

نتیجه گیری

برای مدت طولانی، ساخت برنامه‌های افزودنی پایتون معمولاً به معنای یادگیری زبان C با تمام مینیمالیسم و ​​عدم امنیت بومی بود. یا می توانید از ابزاری مانند Cython با تمام ویژگی های خاص خود استفاده کنید. اما برای توسعه‌دهندگانی که Rust را می‌شناسند و می‌خواهند از آن با پایتون استفاده کنند، PyO3 یک راه راحت و قدرتمند برای انجام آن ارائه می‌کند.