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);
}
Python是。
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 != 'q4312078q')
Py_InspectFlag = 1;
if (!saw_unbuffered_flag &&
(p = Py_GETENV("PYTHONUNBUFFERED")) && *p != 'q4312078q')
unbuffered = 1;
if (!Py_NoUserSiteDirectory &&
(p = Py_GETENV("PYTHONNOUSERSITE")) && *p != 'q4312078q')
Py_NoUserSiteDirectory = 1;
if ((p = Py_GETENV("PYTHONWARNINGS")) && *p != 'q4312078q') {
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 != 'q4312078q')
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 != 'q4312078q')
{
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”非常相似的函数中。这是我的问题:是这段代码编写得很糟糕,还是有其他原因需要简短的main函数?就目前而言,我认为这样做与将
Py_Main()
中的代码移回main()
绝对没有区别。我在想这个吗?#1 楼
您不能从库中导出main
,但是可以导出Py_Main
,然后使用该库的任何人都可以在同一程序中使用不同的参数多次“调用” Python。到那时,python
只是该库的另一个使用者,只不过是库函数的包装器。它像其他所有人一样调用Py_Main
。评论
有一个很好的答案。
–riwalk
2011年6月20日在21:37
我想说您无法导入@Shoosh可能更准确。 C ++标准禁止从您自己的代码中调用它。此外,其链接是实现定义的。同样,从main有效返回将退出退出,通常您不希望库执行此操作。
–罗伯·肯尼迪
2011年6月20日在21:53
@Coder,请参见C ++ 03§3.6.1/ 5:“ main中的return语句具有离开main函数…并以返回值作为参数调用exit的效果。”另请参见§18.3/ 8,其中解释了在调用exit时“具有静态存储持续时间的对象已销毁”和“所有打开的C流...已刷新”。 C99具有相似的语言。
–罗伯·肯尼迪
2011年6月21日13:58
@Coder,退出是否离开主无关。我们不在讨论退出行为。我们正在讨论main的行为。 main的行为包括exit的行为,无论可能是什么。这就是不希望导入和调用main的原因(如果这样做是可能或允许的)。
–罗伯·肯尼迪
2011年6月21日15:22
@Coder,如果从main返回不具有在编译器上调用exit的作用,则您的编译器未遵循标准。该标准规定了main的这种行为证明了它有一些特殊之处。 main的特殊之处在于,从main返回具有调用exit的作用。 (这是由编译器编写者决定的。编译器可以简单地在函数结尾中插入代码,以破坏静态对象,调用atexit例程,刷新文件并终止程序,这又不是您想要的。图书馆。)
–罗伯·肯尼迪
2011年6月21日15:53
#2 楼
并不是说main
的长度不应该太长,否则您应该避免任何功能都太长。 main
只是功能的一种特例。较长的功能很难使用,降低了可维护性,并且通常很难使用。通过缩短函数(和main
),通常可以提高代码的质量。在您的示例中,将代码移出
main
根本没有任何好处。评论
黄金字可能是“重用”。较长的主线不是很可重用。
– S.Lott
2011年6月20日22:11在
@S-这是一个黄金字。另一个是天哪!多动症刚刚踢!!或通俗易懂的话:易读。
–爱德华·奇异
2011年6月20日在22:18
main()还具有其他函数没有的一些限制。
–马丁·约克
2011年6月20日23:33
main()也没有实际含义。您的代码都应该对另一个程序员有意义。我使用main来解析参数,仅此而已-如果超过几行,我甚至会委托它。
– Bill K
2011年6月21日在22:07
@Bill K:好点,仅使用main()解析参数(并启动程序的其余部分)也符合单一责任原则。
–乔治
13年3月17日在11:41
#3 楼
缩短main()
的原因之一涉及单元测试。 main()
是无法进行单元测试的一个函数,因此将大多数行为提取到可以进行单元测试的另一类中是很有意义的。这与您所说的相吻合编写模块化代码并遵循单一职责原则,使我的代码可以“分批”设计,而main()仅仅是催化剂使程序运行。
注意:我从这里得到了这个主意。
评论
另一个好人。从来没有想过这方面。
–riwalk
2011年6月20日22:46
#4 楼
main
太长并不是一个好主意;与任何函数(或方法)一样,如果很长的话,您可能会缺少重构的机会。如果您希望您的代码表现得像python shell,则可以直接使用该代码,而无需多加摆弄。 (必须这样考虑,因为如果将main
放在库中将无法正常工作;如果这样做,则会发生奇怪的事情。)编辑: '不要在静态库中,因为它没有显式链接,因此无法正确链接(除非您将它与所引用的内容放置在对象文件中,这太可怕了!)共享库通常是被视为相似(再次避免混乱),尽管在许多平台上,另一个因素是共享库只是没有引导程序部分的可执行文件(其中
Py_Main
只是最后也是最可见的部分)。 >评论
简而言之,不要将main放在库中。它要么行不通,要么会使您非常困惑。但是实际上将其所有工作委托给lib中的某个函数通常是明智的。
–研究员
2011年6月20日在21:53
#5 楼
Main应该短路,原因与任何功能都应该短路的原因相同。人脑很难一次将大量未分区的数据保存在内存中。将其分解为逻辑块,以便其他开发人员(以及您自己!)容易地进行消化和推理。是的,您的示例非常糟糕且难以阅读,更不用说维护了。
评论
是的,我一直怀疑代码本身很糟糕(尽管问题只涉及代码的位置,而不是代码本身)。恐怕我对Python的看法已被固有地破坏了...
–riwalk
2011年6月20日22:58
@stargazer:我不知道代码本身很糟糕,只是代码的组织方式不适合人类使用。就是说,这里有很多“丑陋”的代码,它们运作良好并且表现出色。代码美并不是全部,尽管我们应该始终尽最大努力编写尽可能干净的代码。
– Ed S.
2011年6月20日23:07
嗯对我来说,它们是相同的。干净的代码往往更稳定。
–riwalk
2011年6月21日在2:46
该代码并不可怕,主要是存在切换情况和多个平台的处理。你到底发现什么可怕?
– Francesco
2011年6月21日,下午5:27
@Francesco:抱歉,从维护和可读性的角度来看,“糟糕”,而不是功能上的。
– Ed S.
2011年9月6日在21:06
#6 楼
有些人喜欢50多个函数,它们什么都不做,而是将调用包装到另一个函数。我宁愿选择执行主程序逻辑的普通主函数。当然结构良好。int main()
{
CheckInstanceCountAndRegister();
InitGlobals();
ProcessCmdParams();
DoInitialization();
ProgramMainLoopOrSomething();
DeInit();
ClearGlobals();
UnregisterInstance();
return 0; //ToMainCRTStartup which cleans heap, etc.
}
我看不出有什么理由将任何东西都包裹在包装纸中。
这纯粹是个人品味。
评论
因为它是文件的代码。您可以以这种方式编写代码,而无需(几乎)编写注释。而且,当您更改代码时,文档会自动更改:-)。
–奥利弗·韦勒
2011年6月28日上午9:49
#7 楼
最佳做法是使所有功能(而不只是主要功能)简短。但是,“ short”是主观的,它取决于程序的大小和所使用的语言。#8 楼
除了编码标准外,main
不需要任何长度。 main
是任何其他函数,因此它的复杂度应低于10(或您的编码标准所说的任何东西)。就是这样,其他任何事情都颇具争议。 编辑
main
不应简短。或长。它应该包括根据您的设计执行所需的功能,并遵守编码标准。关于您问题中的特定代码-是的,这很丑陋。
关于第二个问题-是的,您错了。将所有代码移回main不允许您通过从外部链接
Py_Main
模块化地将其用作库。现在我明白了吗?
评论
我没有问是否会很长。我问为什么不应该那么长。
–riwalk
2011年6月20日21:31
“复杂度低于10”?是否有一个测量单位?
–研究员
2011年6月20日21:32
@ Stargazer712函数长度通常也由编码标准规定。这是一个可读性问题(复杂度高,通常将长函数分支出来,使复杂度远高于20),并且正如我所说-main在这方面与任何其他函数没有什么不同。
–littleadv
2011年6月20日在21:33
@Donal-是的,单击链接。
–littleadv
2011年6月20日在21:33
我将不得不投票反对这一芽。您完全错过了问题的意图。
–riwalk
2011年6月20日21:38
#9 楼
这也是一个新的实用原因,也使主程序与GCC 4.6.1变更日志保持较短:在大多数具有命名节
支持的目标上,仅在
启动时使用的功能(静态构造函数和
main),仅在出口使用的函数和被检测为冷的函数
放置在单独的文本段中。这扩展了
-freorder-functions功能,并由同一开关控制。
目标是缩短大型C ++程序的启动时间。
我添加了突出显示。
#10 楼
不要仅仅因为某些软件是好的就认为该软件背后的所有代码都是好的。好的软件和好的代码不是一回事,即使好的软件有好的代码作为后盾,在大型项目中也不可避免地会出现标准滑脱的地方。有一个简短的
main
函数,但这实际上只是一般规则的一个特例,那就是最好有一个简短的函数。短函数更易于理解和调试,并且更好地坚持使程序更具表现力的“单一目的”设计。 main
也许是一个更重要的规则,因为任何想了解程序的人都必须了解main
,而代码库中较为晦涩的角落可能不那么常被访问。 codebase不会将代码推送到Py_Main
来处理此规则,但是因为您无法从库中导出main
或将其作为函数调用。#11 楼
上面有几个技术性的答案,请放开。主线应该简短,因为它应该是一个引导程序。主体应实例化少数几个可以完成工作的对象,通常是一个。像其他任何地方一样,这些对象应经过精心设计,紧密结合,松散耦合,封装,...
虽然出于技术原因,可以使用单行主调用另一个怪兽方法,但从原则上讲,是正确的。从软件工程的角度来看,什么也没有得到。如果选择是在一个行的主方法中调用monster方法,还是在主方法本身是monster方法之间进行选择,则后者的错误程度要小一些。
评论
您假设“ C ++代码应仅使用对象,而仅使用对象”。事实并非如此,C ++是一种多范式语言,不会像其他任何语言一样将所有内容强加到OO模具中。
– Ben Voigt
2011年6月21日下午4:48
评论
对于codereviews.stackexchange.com来说不是更好吗?@Luzhin,不。我不是要任何人查看Python的源代码。这是编程问题。
TBH,一半的代码是选项处理,并且只要您的程序支持很多选项,并且您编写了自定义处理器,这就是您最终要做的...
@Star不,Programmers.SE也适用于最佳实践,编码样式等。实际上,这就是我访问该网站的目的。
@Nim,我知道它正在做什么,但是没有理由不将其写为options = ParseOptionFlags(argc,argv),其中options是一个包含变量Py_BytesWarningFlag,Py_DebugFlag等的结构...