Vsebina
V prejšnjem sklopu ste videli minimalni mehanizem: PYBIND11_MODULE + m.def(...).
Zdaj se osredotočimo na del, ki začetnikom navadno povzroča največ težav: pretvorba tipov in podpisi funkcij.
Ključni miselni model je:
Python vrednosti so dinamični objekti, C++ vrednosti pa imajo statične tipe. Vezava opiše statični C++ podpis, pybind11 pa Python objekte prevede vanj.
Uporabili bomo kratke, realistične izseke. Celotna poganjana koda je v repozitorijih, cilj pa je, da je ta stran berljiva sama zase.
Skalarji: primer, kjer »preprosto deluje«
Vzemimo navadno C++ funkcijo:
double scale(double x, double factor) {
return factor * x;
}
Vezava:
m.def("scale", &scale, "Multiply x by factor");
V Pythonu jo lahko pokličete neposredno:
scale(3.0, 2.5) # -> 7.5
Kaj se je zgodilo? pybind11 je Python float pretvoril v C++ double, poklical funkcijo
in C++ double pretvoril nazaj v Python float.
Ko pretvorba ne uspe (in zakaj je to dobro)
Če klica iz Pythona ni mogoče pretvoriti v C++ podpis, dobite Python TypeError.
To je uporabna napaka, ker natančno pove, kateri podpisi so veljavni. Pri učenju je smiselno,
da to napako enkrat namenoma sprožite in se navadite brati sporočilo.
Naj bodo vezave »pythonske«: imenski argumenti in privzete vrednosti
Raziskovalci ne želimo pomniti vrstnega reda argumentov za vsako numerično funkcijo. Majhna, a pomembna izboljšava je, da argumente poimenujete in določite privzete vrednosti že v vezavi.
namespace py = pybind11;
double smooth(double x, double alpha) {
return alpha * x;
}
m.def("smooth", &smooth,
py::arg("x"),
py::arg("alpha") = 0.5,
"Toy example with a default argument");
V Pythonu lahko zdaj pišete:
smooth(2.0) # uporabi alpha=0.5
smooth(2.0, alpha=1.0) # imenski argument
S tem algoritma v C++ sploh ne spremenite; samo Python vmesnik postane bolj berljiv in manj nagnjen k napakam.
Vračanje več vrednosti
Python funkcije pogosto vrnejo več vrednosti kot terko:
mn, mx = minmax(data)
C++ nima enake sintakse za vračanje več vrednosti, ima pa tipe iz standardne knjižnice za združevanje več rezultatov.
Najpogostejša sta:
std::pair<T1, T2>- točno dve vrednostistd::tuple<T1, T2, ...>- poljubno število vrednosti
Ta tipa prideta iz glave:
#include <utility> // std::pair
#include <tuple> // std::tuple
Za preproste funkcije z »dvema rezultatoma« je std::pair običajno najbolj čist izbor.
Primer: vrnemo minimum in maksimum
Spodaj je majhna C++ funkcija, ki za vektor vrne minimum in maksimum:
#include <utility>
#include <vector>
#include <algorithm>
std::pair<double,double> minmax(const std::vector<double>& v) {
auto mm = std::minmax_element(v.begin(), v.end());
return {*mm.first, *mm.second};
}
Razčlenimo, kaj ta koda počne.
1) Povratni tip
std::pair<double,double>
To pomeni: »vrni dve števili tipa double v enem paketu.« Na C++ strani sta dostopni kot
pair.first in pair.second.
2) Učinkovito iskanje minimuma in maksimuma
auto mm = std::minmax_element(v.begin(), v.end());
std::minmax_element (iz <algorithm>) vektor preleti enkrat in vrne par iteratorjev:
mm.firstkaže na najmanjši elementmm.secondkaže na največji element
Ker sta to iteratorja, do vrednosti dostopate z odkazovanjem (*mm.first).
3) Sestava povratnega para
return {*mm.first, *mm.second};
S tem zgradite std::pair<double,double> z vrednostma (min_value, max_value) prek inicializacije s seznamom.
Kaj vidi Python
Če funkcijo zvežete tako:
m.def("minmax", &minmax, "Return (min, max) of a vector");
Python dobi navadno terko:
mn, mx = minmax([3.0, 1.0, 2.0])
Hiter preizkus:
result = minmax([3.0, 1.0, 2.0])
print(result)
print(type(result))
Videti bi morali nekaj kot (1.0, 3.0) in <class 'tuple'>.
Izjeme: glasen padec je prednost
V numerični kodi se neveljavni vhodi zgodijo (prazna polja, napačne oblike, slabi parametri). Takih primerov ne skrivajte z »čarobnimi« povratnimi vrednostmi. V C++ vrzite izjemo in naj jo Python dobi.
#include <stdexcept>
std::pair<double,double> minmax(const std::vector<double>& v) {
if (v.empty())
throw std::runtime_error("minmax: empty input");
auto mm = std::minmax_element(v.begin(), v.end());
return {*mm.first, *mm.second};
}
V Pythonu to postane izjema, ki jo lahko ulovite s try/except.
Pomembno sporočilo za udeležence je: dobra vezava ni le hitra, ampak tudi varna in eksplicitna.
Kontrolna točka za zasnovo raziskovalne kode
Preden funkcijo izpostavite, se vprašajte:
- jo bo Python klical enkrat na podatkovni sklop ali enkrat na skalar v zanki?
- vmesnik spodbuja obdelavo po vektorjih (dobro) ali klice po elementih (pogosto slabo)?
- bo API čez nekaj mesecev v beležnici še vedno berljiv?
Ko prvi modul enkrat prevede, so ta vprašanja pomembnejša od gole sintakse.