Python/C: Parse all values at once for return to Python?
If you are outputting a lot of values from C to a dict in Python, is there a better (quicker and less error prone) way to do it than:
return Py_BuildValue("{s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:(i,i,i,i),s:(i,i,i,i),s:(i,i,i,i)}",
"jd\0", spa.jd, //Julian day
"jc\0", spa.jc, //Julian century
"jde\0", spa.jde, //Julian ephemeris day
"jce\0", spa.jce, //Julian ephemeris century
"jme\0", spa.jme, //Julian ephemeris millennium
"l\0", spa.l, //earth heliocentric longitude [degrees]
"b\0", spa.b, //earth heliocentric latitude [degrees]
"r\0", spa.r, //earth radius vector [Astronomical Units, AU]
"theta\0", spa.theta, //geocentric longitude [degrees]
"beta\0", spa.beta, //geocentric latitude [degrees]
"x0\0", spa.x0, //mean elongation (moon-sun) [degrees]
"x1\0", spa.x1, //mean anomaly (sun) [degrees]
"x2\0", spa.x2, //mean anomaly (moon) [degrees]
"x3\0", spa.x3, //argument latitude (moon) [degrees]
"x4\0", spa.x4, //ascending longitude (moon) [degrees]
"del_psi\0", spa.del_psi, //nutation longitude [degrees]
"del_epsilon\0", spa.del_epsilon, //nutation obliquity [degrees]
"epsilon0\0", spa.epsilon0, //ecliptic mean obliquity [arc seconds]
"epsilon\0", spa.epsilon, //ecliptic true obliquity [degrees]
"del_tau\0", spa.del_tau, //aberration correction [degrees]
"lamda\0", spa.lamda, //apparent sun longitude [degrees]
"nu0\0开发者_如何学Go", spa.nu0, //Greenwich mean sidereal time [degrees]
"nu\0", spa.nu, //Greenwich sidereal time [degrees]
"alpha\0", spa.alpha, //geocentric sun right ascension [degrees]
"delta\0", spa.delta, //geocentric sun declination [degrees]
"h\0", spa.h, //observer hour angle [degrees]
"xi\0", spa.xi, //sun equatorial horizontal parallax [degrees]
"del_alpha\0", spa.del_alpha, //sun right ascension parallax [degrees]
"delta_prime\0", spa.delta_prime, //topocentric sun declination [degrees]
"alpha_prime\0", spa.alpha_prime, //topocentric sun right ascension [degrees]
"h_prime\0", spa.h_prime, //topocentric local hour angle [degrees],
"h0_prime\0", spa.h0_prime,
"delta_zero\0", spa.delta_zero,
"e0\0", spa.e0, //topocentric elevation angle (uncorrected) [degrees]
"del_e\0", spa.del_e, //atmospheric refraction correction [degrees]
"e\0", spa.e, //topocentric elevation angle (corrected) [degrees]
"eot\0", spa.eot, //equation of time [minutes]
"srha\0", spa.srha, //sunrise hour angle [degrees]
"ssha\0", spa.ssha, //sunset hour angle [degrees]
"sta\0", spa.sta, //sun transit altitude [degrees]
"zenith\0", spa.zenith, //topocentric zenith angle [degrees]
"azimuth180\0", spa.azimuth180, //topocentric azimuth angle (westward from south) [-180 to 180 degrees]
"azimuth\0", spa.azimuth, //topocentric azimuth angle (eastward from north) [ 0 to 360 degrees]
"incidence\0", spa.incidence, //surface incidence angle [degrees]
"_suntransit\0", spa.suntransit, //local sun transit time (or solar noon) [fractional hour]
"_sunrise\0", spa.sunrise, //local sunrise time (+/- 30 seconds) [fractional hour]
"_sunset\0", spa.sunset, //local sunset time (+/- 30 seconds) [fractional hour]
"sunrise\0", sunrise_hour, sunrise_min, sunrise_sec, sunrise_microsec,
"sunset\0", sunset_hour, sunset_min, sunset_sec, sunset_microsec,
"noon\0", transit_hour, transit_min, transit_sec, transit_microsec
);
I agree with @Martin v. Löwis about using the C preprocessor and its macro capabilities to ease at least some of the burden of setting and maintaining something like what you are doings. If you define these macros properly you can arrange to have all of the defining information in a single spot in a single header file and avoid repeating yourself.
Basically you need two pieces of information about each item or key & value pair that are to go into the dictionary you're building. One piece is what is put in Py_BuildValue()
's format string argument, and the second is the source of key and associated value for it.
You can extract each of these two sets of information by defining and then redefining the macros used as needed for the task. For your example, the following header file could be created. Notice how one of two different sets of macros are defined depending on whether FORMAT
or FIELDS
was defined at the time it was #include
d.
// builddict.h -- for defining Py_BuildValue() arguments
// define apppropriate macros for current usage
#ifdef FORMAT
#define SPA_FIELD_LAST(FIELD) "s:d"
#define SPA_FIELD(FIELD) SPA_FIELD_LAST(FIELD)", "
#define TIME_FIELD_LAST(NAME) "s:(i,i,i,i)"
#define TIME_FIELD(NAME) TIME_FIELD_LAST(NAME)", "
#define TIME_KEY_FIELD_LAST(KEY,NAME) "s:(i,i,i,i)"
#define TIME_KEY_FIELD(KEY,NAME) TIME_KEY_FIELD_LAST(KEY,NAME)", "
#undef FORMAT
#elif defined FIELDS
#define SPA_FIELD_LAST(FIELD) #FIELD, spa.FIELD
#define SPA_FIELD(FIELD) SPA_FIELD_LAST(FIELD),
#define TIME_FIELD_LAST(NAME) #NAME, NAME##_hour, NAME##_min, NAME##_sec, NAME##_microsec
#define TIME_FIELD(NAME) TIME_FIELD_LAST(NAME),
#define TIME_KEY_FIELD_LAST(KEY,NAME) #KEY, NAME##_hour, NAME##_min, NAME##_sec, NAME##_microsec
#define TIME_KEY_FIELD(KEY,NAME) TIME_KEY_FIELD_LAST(KEY,NAME),
#undef FIELDS
#else
#error neither FORMAT nor FIELDS usage macros are defined
#endif
SPA_FIELD(jd) // Julian day
SPA_FIELD(jc) // Julian century
SPA_FIELD(jde) // Julian ephemeris day
SPA_FIELD(jce) // Julian ephemeris century
SPA_FIELD(jme) // Julian ephemeris millennium
SPA_FIELD(l) // earth heliocentric longitude [degrees]
SPA_FIELD(b) // earth heliocentric latitude [degrees]
SPA_FIELD(r) // earth radius vector [Astronomical Units) AU]
SPA_FIELD(theta) // geocentric longitude [degrees]
SPA_FIELD(beta) // geocentric latitude [degrees]
SPA_FIELD(x0) // mean elongation (moon-sun) [degrees]
SPA_FIELD(x1) // mean anomaly (sun) [degrees]
SPA_FIELD(x2) // mean anomaly (moon) [degrees]
SPA_FIELD(x3) // argument latitude (moon) [degrees]
SPA_FIELD(x4) // ascending longitude (moon) [degrees]
SPA_FIELD(del_psi) // nutation longitude [degrees]
SPA_FIELD(del_epsilon) // nutation obliquity [degrees]
SPA_FIELD(epsilon0) // ecliptic mean obliquity [arc seconds]
SPA_FIELD(epsilon) // ecliptic true obliquity [degrees]
SPA_FIELD(del_tau) // aberration correction [degrees]
SPA_FIELD(lamda) // apparent sun longitude [degrees]
SPA_FIELD(nu0) // Greenwich mean sidereal time [degrees]
SPA_FIELD(nu) // Greenwich sidereal time [degrees]
SPA_FIELD(alpha) // geocentric sun right ascension [degrees]
SPA_FIELD(delta) // geocentric sun declination [degrees]
SPA_FIELD(h) // observer hour angle [degrees]
SPA_FIELD(xi) // sun equatorial horizontal parallax [degrees]
SPA_FIELD(del_alpha) // sun right ascension parallax [degrees]
SPA_FIELD(delta_prime) // topocentric sun declination [degrees]
SPA_FIELD(alpha_prime) // topocentric sun right ascension [degrees]
SPA_FIELD(h_prime) // topocentric local hour angle [degrees])
SPA_FIELD(h0_prime)
SPA_FIELD(delta_zero)
SPA_FIELD(e0) // topocentric elevation angle (uncorrected) [degrees]
SPA_FIELD(del_e) // atmospheric refraction correction [degrees]
SPA_FIELD(e) // topocentric elevation angle (corrected) [degrees]
SPA_FIELD(eot) // equation of time [minutes]
SPA_FIELD(srha) // sunrise hour angle [degrees]
SPA_FIELD(ssha) // sunset hour angle [degrees]
SPA_FIELD(sta) // sun transit altitude [degrees]
SPA_FIELD(zenith) // topocentric zenith angle [degrees]
SPA_FIELD(azimuth180) // topocentric azimuth angle (westward from south) [-180 to 180 degrees]
SPA_FIELD(azimuth) // topocentric azimuth angle (eastward from north) [ 0 to 360 degrees]
SPA_FIELD(incidence) // surface incidence angle [degrees]
SPA_FIELD(suntransit) // local sun transit time (or solar noon) [fractional hour]
SPA_FIELD(sunrise) // local sunrise time (+/- 30 seconds) [fractional hour]
SPA_FIELD(sunset) // local sunset time (+/- 30 seconds) [fractional hour]
TIME_FIELD(sunrise)
TIME_FIELD(sunset)
TIME_KEY_FIELD_LAST(noon, transit) // must use a xxx_LAST macro on last one
// clean up to prevent warnings about redefining macros
#undef SPA_FIELD_LAST
#undef SPA_FIELD
#undef TIME_FIELD_LAST
#undef TIME_FIELD
#undef TIME_KEY_FIELD_LAST
#undef TIME_KEY_FIELD
Once you have it all set up, your build_dict()
function to become something fairly short and independent of what the actual contents of the dictionary are going to be:
// build format string using header
char format_string[] = "{"
#define FORMAT
#include "builddict.h"
"}";
// use header again to build list of fields
PyObject* build_dict(SPA spa)
{
return Py_BuildValue(format_string,
#define FIELDS
#include "builddict.h"
);
}
While this doesn't completely automate the process, but could help a lot. There are likely additional text processing or C interfacing tools available (or you could write your own) to further assist you in creating this single header file since it's in a very uniform format.
Given that most of the values seem to come from spa
, it's probably a better idea to encapsulate it along with the other loose properties within an object and just return that. Also, datetime.time
.
You can use macros:
#define ADD_FIELD(F) PyDict_SetItemString(d, #F, spa.F)
ADD_FIELD(jd);
ADD_FIELD(jc);
...
This will prevent mistakes in the string names, and the format string. Mistakes in not listing all fields are not easy to prevent AFAICT.
Also, you can drop the trailing \0
; it does not serve a purpose.
If that is needed for several structs then I'd probably write a small python script for generating this code by reading the structure definition from the .h (e.g. by tagging with a special comment what are the structs and fields you need to export as dicts) ... the last three fields in the shown case would need to be added manually to the dict however.
I wouldn't do that just for one struct especially if the struct is stable.
Did you consider exporting the objects instead of dicts for example using SIP ?
精彩评论