Я занимаюсь программированием более 9 лет, и по совету моего первого учителя программирования я всегда держу свою main()
функцию чрезвычайно короткой.
Сначала я понятия не имел, почему. Я просто повиновался, не понимая, к радости моих профессоров.
Получив опыт, я понял, что если я правильно спроектировал свой код, то если бы у меня была короткая main()
функция, то вроде бы это произошло. Написание модульного кода и следование принципу единой ответственности позволили разработать мой код в виде «связок» и main()
послужили всего лишь катализатором для запуска программы.
Перемотав несколько недель назад, я посмотрел на исходный код Python и нашел main()
функцию:
/* Minimal main program -- everything is loaded from the library */
...
int
main(int argc, char **argv)
{
...
return Py_Main(argc, argv);
}
Yay питон. Короткая main()
функция == Хороший код.
Учителя программирования были правы.
Желая заглянуть глубже, я взглянул на Py_Main. В целом он определяется следующим образом:
/* Main program */
int
Py_Main(int argc, char **argv)
{
int c;
int sts;
char *command = NULL;
char *filename = NULL;
char *module = NULL;
FILE *fp = stdin;
char *p;
int unbuffered = 0;
int skipfirstline = 0;
int stdin_is_interactive = 0;
int help = 0;
int version = 0;
int saw_unbuffered_flag = 0;
PyCompilerFlags cf;
cf.cf_flags = 0;
orig_argc = argc; /* For Py_GetArgcArgv() */
orig_argv = argv;
#ifdef RISCOS
Py_RISCOSWimpFlag = 0;
#endif
PySys_ResetWarnOptions();
while ((c = _PyOS_GetOpt(argc, argv, PROGRAM_OPTS)) != EOF) {
if (c == 'c') {
/* -c is the last option; following arguments
that look like options are left for the
command to interpret. */
command = (char *)malloc(strlen(_PyOS_optarg) + 2);
if (command == NULL)
Py_FatalError(
"not enough memory to copy -c argument");
strcpy(command, _PyOS_optarg);
strcat(command, "\n");
break;
}
if (c == 'm') {
/* -m is the last option; following arguments
that look like options are left for the
module to interpret. */
module = (char *)malloc(strlen(_PyOS_optarg) + 2);
if (module == NULL)
Py_FatalError(
"not enough memory to copy -m argument");
strcpy(module, _PyOS_optarg);
break;
}
switch (c) {
case 'b':
Py_BytesWarningFlag++;
break;
case 'd':
Py_DebugFlag++;
break;
case '3':
Py_Py3kWarningFlag++;
if (!Py_DivisionWarningFlag)
Py_DivisionWarningFlag = 1;
break;
case 'Q':
if (strcmp(_PyOS_optarg, "old") == 0) {
Py_DivisionWarningFlag = 0;
break;
}
if (strcmp(_PyOS_optarg, "warn") == 0) {
Py_DivisionWarningFlag = 1;
break;
}
if (strcmp(_PyOS_optarg, "warnall") == 0) {
Py_DivisionWarningFlag = 2;
break;
}
if (strcmp(_PyOS_optarg, "new") == 0) {
/* This only affects __main__ */
cf.cf_flags |= CO_FUTURE_DIVISION;
/* And this tells the eval loop to treat
BINARY_DIVIDE as BINARY_TRUE_DIVIDE */
_Py_QnewFlag = 1;
break;
}
fprintf(stderr,
"-Q option should be `-Qold', "
"`-Qwarn', `-Qwarnall', or `-Qnew' only\n");
return usage(2, argv[0]);
/* NOTREACHED */
case 'i':
Py_InspectFlag++;
Py_InteractiveFlag++;
break;
/* case 'J': reserved for Jython */
case 'O':
Py_OptimizeFlag++;
break;
case 'B':
Py_DontWriteBytecodeFlag++;
break;
case 's':
Py_NoUserSiteDirectory++;
break;
case 'S':
Py_NoSiteFlag++;
break;
case 'E':
Py_IgnoreEnvironmentFlag++;
break;
case 't':
Py_TabcheckFlag++;
break;
case 'u':
unbuffered++;
saw_unbuffered_flag = 1;
break;
case 'v':
Py_VerboseFlag++;
break;
#ifdef RISCOS
case 'w':
Py_RISCOSWimpFlag = 1;
break;
#endif
case 'x':
skipfirstline = 1;
break;
/* case 'X': reserved for implementation-specific arguments */
case 'U':
Py_UnicodeFlag++;
break;
case 'h':
case '?':
help++;
break;
case 'V':
version++;
break;
case 'W':
PySys_AddWarnOption(_PyOS_optarg);
break;
/* This space reserved for other options */
default:
return usage(2, argv[0]);
/*NOTREACHED*/
}
}
if (help)
return usage(0, argv[0]);
if (version) {
fprintf(stderr, "Python %s\n", PY_VERSION);
return 0;
}
if (Py_Py3kWarningFlag && !Py_TabcheckFlag)
/* -3 implies -t (but not -tt) */
Py_TabcheckFlag = 1;
if (!Py_InspectFlag &&
(p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
Py_InspectFlag = 1;
if (!saw_unbuffered_flag &&
(p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0')
unbuffered = 1;
if (!Py_NoUserSiteDirectory &&
(p = Py_GETENV("PYTHONNOUSERSITE")) && *p != '\0')
Py_NoUserSiteDirectory = 1;
if ((p = Py_GETENV("PYTHONWARNINGS")) && *p != '\0') {
char *buf, *warning;
buf = (char *)malloc(strlen(p) + 1);
if (buf == NULL)
Py_FatalError(
"not enough memory to copy PYTHONWARNINGS");
strcpy(buf, p);
for (warning = strtok(buf, ",");
warning != NULL;
warning = strtok(NULL, ","))
PySys_AddWarnOption(warning);
free(buf);
}
if (command == NULL && module == NULL && _PyOS_optind < argc &&
strcmp(argv[_PyOS_optind], "-") != 0)
{
#ifdef __VMS
filename = decc$translate_vms(argv[_PyOS_optind]);
if (filename == (char *)0 || filename == (char *)-1)
filename = argv[_PyOS_optind];
#else
filename = argv[_PyOS_optind];
#endif
}
stdin_is_interactive = Py_FdIsInteractive(stdin, (char *)0);
if (unbuffered) {
#if defined(MS_WINDOWS) || defined(__CYGWIN__)
_setmode(fileno(stdin), O_BINARY);
_setmode(fileno(stdout), O_BINARY);
#endif
#ifdef HAVE_SETVBUF
setvbuf(stdin, (char *)NULL, _IONBF, BUFSIZ);
setvbuf(stdout, (char *)NULL, _IONBF, BUFSIZ);
setvbuf(stderr, (char *)NULL, _IONBF, BUFSIZ);
#else /* !HAVE_SETVBUF */
setbuf(stdin, (char *)NULL);
setbuf(stdout, (char *)NULL);
setbuf(stderr, (char *)NULL);
#endif /* !HAVE_SETVBUF */
}
else if (Py_InteractiveFlag) {
#ifdef MS_WINDOWS
/* Doesn't have to have line-buffered -- use unbuffered */
/* Any set[v]buf(stdin, ...) screws up Tkinter :-( */
setvbuf(stdout, (char *)NULL, _IONBF, BUFSIZ);
#else /* !MS_WINDOWS */
#ifdef HAVE_SETVBUF
setvbuf(stdin, (char *)NULL, _IOLBF, BUFSIZ);
setvbuf(stdout, (char *)NULL, _IOLBF, BUFSIZ);
#endif /* HAVE_SETVBUF */
#endif /* !MS_WINDOWS */
/* Leave stderr alone - it should be unbuffered anyway. */
}
#ifdef __VMS
else {
setvbuf (stdout, (char *)NULL, _IOLBF, BUFSIZ);
}
#endif /* __VMS */
#ifdef __APPLE__
/* On MacOS X, when the Python interpreter is embedded in an
application bundle, it gets executed by a bootstrapping script
that does os.execve() with an argv[0] that's different from the
actual Python executable. This is needed to keep the Finder happy,
or rather, to work around Apple's overly strict requirements of
the process name. However, we still need a usable sys.executable,
so the actual executable path is passed in an environment variable.
See Lib/plat-mac/bundlebuiler.py for details about the bootstrap
script. */
if ((p = Py_GETENV("PYTHONEXECUTABLE")) && *p != '\0')
Py_SetProgramName(p);
else
Py_SetProgramName(argv[0]);
#else
Py_SetProgramName(argv[0]);
#endif
Py_Initialize();
if (Py_VerboseFlag ||
(command == NULL && filename == NULL && module == NULL && stdin_is_interactive)) {
fprintf(stderr, "Python %s on %s\n",
Py_GetVersion(), Py_GetPlatform());
if (!Py_NoSiteFlag)
fprintf(stderr, "%s\n", COPYRIGHT);
}
if (command != NULL) {
/* Backup _PyOS_optind and force sys.argv[0] = '-c' */
_PyOS_optind--;
argv[_PyOS_optind] = "-c";
}
if (module != NULL) {
/* Backup _PyOS_optind and force sys.argv[0] = '-c'
so that PySys_SetArgv correctly sets sys.path[0] to ''
rather than looking for a file called "-m". See
tracker issue #8202 for details. */
_PyOS_optind--;
argv[_PyOS_optind] = "-c";
}
PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind);
if ((Py_InspectFlag || (command == NULL && filename == NULL && module == NULL)) &&
isatty(fileno(stdin))) {
PyObject *v;
v = PyImport_ImportModule("readline");
if (v == NULL)
PyErr_Clear();
else
Py_DECREF(v);
}
if (command) {
sts = PyRun_SimpleStringFlags(command, &cf) != 0;
free(command);
} else if (module) {
sts = RunModule(module, 1);
free(module);
}
else {
if (filename == NULL && stdin_is_interactive) {
Py_InspectFlag = 0; /* do exit on SystemExit */
RunStartupFile(&cf);
}
/* XXX */
sts = -1; /* keep track of whether we've already run __main__ */
if (filename != NULL) {
sts = RunMainFromImporter(filename);
}
if (sts==-1 && filename!=NULL) {
if ((fp = fopen(filename, "r")) == NULL) {
fprintf(stderr, "%s: can't open file '%s': [Errno %d] %s\n",
argv[0], filename, errno, strerror(errno));
return 2;
}
else if (skipfirstline) {
int ch;
/* Push back first newline so line numbers
remain the same */
while ((ch = getc(fp)) != EOF) {
if (ch == '\n') {
(void)ungetc(ch, fp);
break;
}
}
}
{
/* XXX: does this work on Win/Win64? (see posix_fstat) */
struct stat sb;
if (fstat(fileno(fp), &sb) == 0 &&
S_ISDIR(sb.st_mode)) {
fprintf(stderr, "%s: '%s' is a directory, cannot continue\n", argv[0], filename);
fclose(fp);
return 1;
}
}
}
if (sts==-1) {
/* call pending calls like signal handlers (SIGINT) */
if (Py_MakePendingCalls() == -1) {
PyErr_Print();
sts = 1;
} else {
sts = PyRun_AnyFileExFlags(
fp,
filename == NULL ? "<stdin>" : filename,
filename != NULL, &cf) != 0;
}
}
}
/* Check this environment variable at the end, to give programs the
* opportunity to set it from Python.
*/
if (!Py_InspectFlag &&
(p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
{
Py_InspectFlag = 1;
}
if (Py_InspectFlag && stdin_is_interactive &&
(filename != NULL || command != NULL || module != NULL)) {
Py_InspectFlag = 0;
/* XXX */
sts = PyRun_AnyFileFlags(stdin, "<stdin>", &cf) != 0;
}
Py_Finalize();
#ifdef RISCOS
if (Py_RISCOSWimpFlag)
fprintf(stderr, "\x0cq\x0c"); /* make frontend quit */
#endif
#ifdef __INSURE__
/* Insure++ is a memory analysis tool that aids in discovering
* memory leaks and other memory problems. On Python exit, the
* interned string dictionary is flagged as being in use at exit
* (which it is). Under normal circumstances, this is fine because
* the memory will be automatically reclaimed by the system. Under
* memory debugging, it's a huge source of useless noise, so we
* trade off slower shutdown for less distraction in the memory
* reports. -baw
*/
_Py_ReleaseInternedStrings();
#endif /* __INSURE__ */
return sts;
}
Боже, Всемогущий ... он достаточно большой, чтобы потопить Титаник.
Кажется, что Python проделал трюк «Введение в программирование 101» и просто переместил весь main()
код в другую функцию, названную чем-то очень похожим на «main».
Вот мой вопрос: этот код написан ужасно, или есть другие причины иметь короткую основную функцию?
В нынешнем виде я не вижу абсолютно никакой разницы между этим и простым перемещением кода Py_Main()
обратно main()
. Я ошибаюсь, думая об этом?
источник
options = ParseOptionFlags(argc,argv)
гдеoptions
это ,struct
что содержит переменныеPy_BytesWarningFlag
,Py_DebugFlag
и т.д ...Ответы:
Вы не можете экспортировать
main
из библиотеки, но вы можете экспортироватьPy_Main
, и тогда любой, кто использует эту библиотеку, может многократно «вызывать» Python с разными аргументами в одной и той же программе. В этот моментpython
становится просто еще одним потребителем библиотеки, чуть больше, чем обертка для функции библиотеки; он звонит такPy_Main
же, как и все остальные.источник
main
эффективным вызовамexit
, чего обычно не требуется делать библиотеке.main
оставляет эффект основной функции… и вызывает егоexit
с возвращаемым значением в качестве аргумента». Также см. §18.3 / 8, в котором объясняется, что «объекты со статической продолжительностью хранения уничтожаются» и «все открытые потоки C… сбрасываются» при вызовеexit
. С99 имеет похожий язык.exit
листьяmain
не имеют значения. Мы не обсуждаем поведениеexit
. Мы обсуждаем поведениеmain
. И поведениеmain
включает в себя поведениеexit
, что бы это ни было. Вот что делает нежелательным импорт и вызовmain
(если такое вообще возможно или разрешено).main
не вызывает обращенияexit
к вашему компилятору, то ваш компилятор не следует стандарту. То, что стандарт диктует такое поведение,main
доказывает, что в этом есть что-то особенное. Особенность в том,main
что возвращение из него имеет эффект звонкаexit
. ( Как это происходит, зависит от авторов компилятора. Компилятор может просто вставить код в эпилог функции, который уничтожает статические объекты, вызываетatexit
процедуры, сбрасывает файлы и завершает программу - что, опять же, не то, что вам нужно в библиотеке .)Это не значит, что
main
не должно быть долго так много , как вам следует избегать любой функции слишком долго.main
это просто особый случай функции. Более длинные функции становятся очень трудными для выполнения, снижают удобство обслуживания и, как правило, с ними сложнее работать. Сокращая функции (иmain
) короче, вы обычно улучшаете качество своего кода.В вашем примере нет никакой выгоды от перемещения кода из
main
.источник
main
не очень многоразовый.Одна из причин, по которой стоит
main()
остановиться, заключается в модульном тестировании.main()
это одна функция, которая не может быть проверена модулем, поэтому имеет смысл выделить большую часть поведения в другой класс, который может быть проверен модулем. Это соответствует тому, что вы сказалиПримечание: идея пришла отсюда .
источник
Это редко хорошая идея,
main
чтобы быть длинным; как и с любой функцией (или методом), если она длинная, вы, вероятно, упускаете возможности для рефакторинга.В конкретном случае, о котором вы упомянули выше,
main
он короткий, потому что вся эта сложность учтенаPy_Main
; если вы хотите, чтобы ваш код вел себя как оболочка Python, вы можете просто использовать этот код без особых хлопот. (Это должно быть учтено так, потому что это не работает, если вы кладетеmain
в библиотеку; странные вещи случаются, если вы делаете.)РЕДАКТИРОВАТЬ:
Чтобы уточнить,
main
не может быть в статической библиотеке, потому что она не имеет явной ссылки на нее и поэтому не будет связана правильно (если вы не поместите его в объектный файл с чем-то, на что ссылаются, что просто ужасно !) Общие библиотеки обычно рассматриваются как похожие (опять же, чтобы избежать путаницы), хотя на многих платформах дополнительным фактором является то, что общая библиотека - это просто исполняемый файл без раздела начальной загрузки (из которыхmain
только последняя и наиболее видимая часть ).источник
main
в библиотеку. Это либо не сработает, либо ужасно вас запутает. Но делегировать практически всю свою работу функции, которая находится в lib, часто разумно.Главное должно быть коротким по той же причине, что любая функция должна быть короткой. Человеческий мозг с трудом удерживает в памяти большое количество неразделенных данных одновременно. Разбейте его на логические порции, чтобы другим разработчикам (и вам самим) было легко разобраться и рассуждать.
И да, ваш пример ужасен и труден для чтения, не говоря уже о поддержке.
источник
Некоторым людям нравится более 50 функций, которые больше ничего не делают, но заключают вызов в другую функцию. Я бы предпочел нормальную основную функцию, которая выполняет основную логику программы. Хорошо структурировано, конечно.
Я не вижу никакой причины, почему я должен обернуть что-то из этого в обертку.
Это чисто личный вкус.
источник
Лучше всего, чтобы ВСЕ ваши функции были короткими, а не только основными. Однако «короткий» субъективен, он зависит от размера вашей программы и языка, который вы используете.
источник
Там нет требования
main
быть какой-либо длины, кроме стандартов кодирования.main
это функция, как и любая другая, и, как таковая, ее сложность должна быть ниже 10 (или что бы там ни говорили ваши стандарты кодирования). Вот и все, все остальное скорее аргументировано.редактировать
main
не должно быть коротким. Или долго. Он должен включать в себя функциональность, которую требуется выполнять на основе вашего дизайна, и придерживаться стандартов кодирования.Что касается конкретного кода в вашем вопросе - да, это безобразно.
Что касается вашего второго вопроса - да, вы не правы . Перемещение всего этого кода обратно в main не позволяет вам использовать его как библиотеку, связываясь
Py_Main
извне.Теперь я ясно?
источник
main
ничем не отличается от любой другой функции в этом отношении.Вот еще одна прагматическая причина, по которой следует избегать основного короткого замыкания из журнала изменений GCC 4.6.1 :
Подсветка добавлена мной.
источник
Не думайте, что только потому, что немного программного обеспечения хорошо, весь код этого программного обеспечения хорош. Хорошее программное обеспечение и хороший код - не одно и то же, и даже если хорошее программное обеспечение подкреплено хорошим кодом, неизбежно, что в большом проекте будут места, где проскальзывают стандарты.
Хорошей практикой является иметь короткую
main
функцию, но на самом деле это всего лишь частный случай общего правила, что лучше иметь короткие функции. Короткие функции легче понять и легче отлаживать, а также лучше придерживаться вида «единой цели», который делает программы более выразительными.main
это, пожалуй, более важное место для соблюдения правила, поскольку любой, кто хочет понять программу, должен понимать, вmain
то время как более неясные уголки кодовой базы могут посещаться реже.Но кодовая база Python выдвигает код не для
Py_Main
игры в это правило, а потому, что вы не можете экспортироватьmain
из библиотеки или вызывать ее как функцию.источник
Есть несколько технических ответов выше, давайте оставим это в стороне.
Основное должно быть коротким, потому что оно должно быть начальной загрузкой. Основное должно создавать небольшое количество объектов, часто один, который выполняет работу. Как и везде, эти объекты должны быть хорошо спроектированы, сплочены, слабо связаны, инкапсулированы, ...
Хотя могут быть технические причины, чтобы основной вызов из одной строки вызывал другой метод-монстр, в принципе вы правы. С точки зрения разработки программного обеспечения ничего не было получено. Если выбор между главной строкой, вызывающей метод-монстр, и основной-самой, являющейся методом-монстром, последний является менее плохим.
источник