이 글은 PYTHON 정규 표현식의 re 모듈을 사용하는 방법을 소개합니다. 이는 특정 참조 가치가 있습니다. 이제 이를 공유합니다. 도움이 필요한 친구들은 이를 참조할 수 있습니다.
정규 표현식은 복잡한 주제입니다. 이 글이 이해하는 데 도움이 될까요? 해당 부분이 불분명합니까, 아니면 여기서 발견하지 못한 문제입니까? 그렇다면 개선을 위해 작성자에게 제안 사항을 보내주세요
먼저 Python 인터프리터를 실행하고 re 모듈을 가져온 다음 RE:
#!python Python 2.2.2 (#1, Feb 10 2003, 12:57:01) >>> import re >>> p = re.compile('[a-z]+') >>> p <_sre.SRE_Pattern object at 80c3c28>
를 컴파일해 보세요. 이제 [a-z]+를 다음과 일치시켜 볼 수 있습니다. RE 다른 문자열. +는 "1회 이상의 반복"을 의미하므로 빈 문자열은 전혀 일치하지 않습니다. 이 경우 match()는 인터프리터에 출력이 없기 때문에 None을 반환합니다. match()의 결과를 명시적으로 인쇄하여 이를 알아낼 수 있습니다.
#!python
>>> p.match("")
>>> print p.match("")
None
이제 이를 사용해 문자열을 일치시켜 보겠습니다. "템포"와 같은. 이 경우 match()는 MatchObject를 반환합니다. 따라서 나중에 사용하기 위해 결과를 변수에 저장할 수 있습니다.
#!python >>> m = p.match( 'tempo') >>> print m <_sre.SRE_Match object at 80c4f68>
이제 `MatchObject`를 쿼리하여 일치하는 문자열에 대한 정보를 얻을 수 있습니다. MatchObject 인스턴스에도 여러 가지 메서드와 속성이 있습니다. 가장 중요한 것은 다음과 같습니다.
Method/PropertyFunctiongroup() | RE | |||||||||||||||||||
start() | 경기가 시작되는 위치를 반환합니다. | |||||||||||||||||||
end() | 경기가 끝나는 위치를 반환합니다. | |||||||||||||||||||
span() | 경기 위치를 포함하는 튜플을 반환합니다(start, 끝) | |||||||||||||||||||
#!python >>> m.group() 'tempo' >>> m.start(), m.end() (0, 5) >>> m.span() (0, 5) 로그인 후 복사 group() 返回 RE 匹配的子串。start() 和 end() 返回匹配开始和结束时的索引。span() 则用单个元组把开始和结束时的索引一起返回。因为匹配方法检查到如果 RE 在字符串开始处开始匹配,那么 start() 将总是为零。然而, `RegexObject` 实例的 search 方法扫描下面的字符串的话,在这种情况下,匹配开始的位置就也许不是零了。 #!python >>> print p.match('::: message') None >>> m = p.search('::: message') ; print m <re.MatchObject instance at 80c9650> >>> m.group() 'message' >>> m.span() (4, 11) 로그인 후 복사 在实际程序中,最常见的作法是将 `MatchObject` 保存在一个变量里,然后检查它是否为 None,通常如下所示: #!python p = re.compile( ... ) m = p.match( 'string goes here' ) if m: print 'Match found: ', m.group() else: print 'No match' 로그인 후 복사 两个 `RegexObject` 方法返回所有匹配模式的子串。findall()返回一个匹配字符串行表: #!python >>> p = re.compile('\d+') >>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping') ['12', '11', '10'] 로그인 후 복사 findall() 在它返回结果时不得不创建一个列表。在 Python 2.2中,也可以用 finditer() 方法。 #!python >>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...') >>> iterator <callable-iterator object at 0x401833ac> >>> for match in iterator: ... print match.span() ... (0, 2) (22, 24) (29, 31) 로그인 후 복사
模块级函数你不一定要产生一个 `RegexObject` 对象然后再调用它的方法;re 模块也提供了顶级函数调用如 match()、search()、sub() 等等。这些函数使用 RE 字符串作为第一个参数,而后面的参数则与相应 `RegexObject` 的方法参数相同,返回则要么是 None 要么就是一个 `MatchObject` 的实例。 #!python >>> print re.match(r'From\s+', 'Fromage amk') None >>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998') <re.MatchObject instance at 80c5978> 로그인 후 복사 Under the hood, 这些函数简单地产生一个 RegexOject 并在其上调用相应的方法。它们也在缓存里保存编译后的对象,因此在将来调用用到相同 RE 时就会更快。
#!python ref = re.compile( ... ) entityref = re.compile( ... ) charref = re.compile( ... ) starttagopen = re.compile( ... ) 로그인 후 복사 我通常更喜欢使用编译对象,甚至它只用一次,but few people will be as much of a purist about this as I am。
编译标志编译标志让你可以修改正则表达式的一些运行方式。在 re 模块中标志可以使用两个名字,一个是全名如 IGNORECASE,一个是缩写,一字母形式如 I。(如果你熟悉 Perl 的模式修改,一字母形式使用同样的字母;例如 re.VERBOSE的缩写形式是 re.X。)多个标志可以通过按位 OR-ing 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:
I 使匹配对大小写不敏感;字符类和字符串匹配字母时忽略大小写。举个例子,[A-Z]也可以匹配小写字母,Spam 可以匹配 "Spam", "spam", 或 "spAM"。这个小写字母并不考虑当前位置。 L 影响 \w, \W, \b, 和 \B,这取决于当前的本地化设置。 locales 是 C 语言库中的一项功能,是用来为需要考虑不同语言的编程提供帮助的。举个例子,如果你正在处理法文文本,你想用 \w+ 来匹配文字,但 \w 只匹配字符类 [A-Za-z];它并不能匹配 "é" 或 "ç"。如果你的系统配置适当且本地化设置为法语,那么内部的 C 函数将告诉程序 "é" 也应该被认为是一个字母。当在编译正则表达式时使用 LOCALE 标志会得到用这些 C 函数来处理 \w 后的编译对象;这会更慢,但也会象你希望的那样可以用 \w+ 来匹配法文文本。 M
S 使 "." 特殊字符完全匹配任何字符,包括换行;没有这个标志, "." 匹配除了换行外的任何字符。 X
#!python charref = re.compile(r""" &[[]] # Start of a numeric entity reference ( [0-9]+[^0-9] # Decimal form | 0[0-7]+[^0-7] # Octal form | x[0-9a-fA-F]+[^0-9a-fA-F] # Hexadecimal form ) """, re.VERBOSE) 로그인 후 복사 没有 verbose 设置, RE 会看起来象这样: #!python charref = re.compile("([0-9]+[^0-9]" "|0[0-7]+[^0-7]" "|x[0-9a-fA-F]+[^0-9a-fA-F])") 로그인 후 복사 在上面的例子里,Python 的字符串自动连接可以用来将 RE 分成更小的部分,但它比用 re.VERBOSE 标志时更难懂。
更多模式功能到目前为止,我们只展示了正则表达式的一部分功能。在本节,我们将展示一些新的元字符和如何使用组来检索被匹配的文本部分。
更多的元字符还有一些我们还没展示的元字符,其中的大部分将在本节展示。
|
^
#!python >>> print re.search('^From', 'From Here to Eternity') <re.MatchObject instance at 80c1520> >>> print re.search('^From', 'Reciting From Memory') None 로그인 후 복사 $
#!python >>> print re.search('}$', '{block}') <re.MatchObject instance at 80adfa8> >>> print re.search('}$', '{block} ') None >>> print re.search('}$', '{block}\n') <re.MatchObject instance at 80adfa8> 로그인 후 복사 匹配一个 "$",使用 \$ 或将其包含在字符类中,如[$]。 \A
\Z Matches only at the end of the string. \b 单词边界。这是个零宽界定符(zero-width assertions)只用以匹配单词的词首和词尾。单词被定义为一个字母数字序列,因此词尾就是用空白符或非字母数字符来标示的。
#!python >>> p = re.compile(r'\bclass\b') >>> print p.search('no class at all') <re.MatchObject instance at 80c8f28> >>> print p.search('the declassified algorithm') None >>> print p.search('one subclass is') None 로그인 후 복사 当用这个特殊序列时你应该记住这里有两个微妙之处。第一个是 Python 字符串和正则表达式之间最糟的冲突。在 Python 字符串里,"\b" 是反斜杠字符,ASCII值是8。如果你没有使用 raw 字符串时,那么 Python 将会把 "\b" 转换成一个回退符,你的 RE 将无法象你希望的那样匹配它了。下面的例子看起来和我们前面的 RE 一样,但在 RE 字符串前少了一个 "r" 。 #!python >>> p = re.compile('\bclass\b') >>> print p.search('no class at all') None >>> print p.search('\b' + 'class' + '\b') <re.MatchObject instance at 80c3ee0> 로그인 후 복사 第二个在字符类中,这个限定符(assertion)不起作用,\b 表示回退符,以便与 Python 字符串兼容。 \B
分组你经常需要得到比 RE 是否匹配还要多的信息。正则表达式常常用来分析字符串,编写一个 RE 匹配感兴趣的部分并将其分成几个小组。举个例子,一个 RFC-822 的头部用 ":" 隔成一个头部名和一个值,这就可以通过编写一个正则表达式匹配整个头部,用一组匹配头部名,另一组匹配头部值的方式来处理。
#!python >>> p = re.compile('(ab)*') >>> print p.match('ababababab').span() (0, 10) 로그인 후 복사 组用 "(" 和 ")" 来指定,并且得到它们匹配文本的开始和结尾索引;这就可以通过一个参数用 group()、start()、end() 和 span() 来进行检索。组是从 0 开始计数的。组 0 总是存在;它就是整个 RE,所以 `MatchObject` 的方法都把组 0 作为它们缺省的参数。稍后我们将看到怎样表达不能得到它们所匹配文本的 span。 #!python >>> p = re.compile('(a)b') >>> m = p.match('ab') >>> m.group() 'ab' >>> m.group(0) 'ab' 로그인 후 복사 小组是从左向右计数的,从1开始。组可以被嵌套。计数的数值可以能过从左到右计算打开的括号数来确定。 #!python >>> p = re.compile('(a(b)c)d') >>> m = p.match('abcd') >>> m.group(0) 'abcd' >>> m.group(1) 'abc' >>> m.group(2) 'b' 로그인 후 복사 group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 #!python >>> m.group(2,1,2) ('b', 'abc', 'b') 로그인 후 복사 The groups() 方法返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 #!python >>> m.groups() ('abc', 'b') 로그인 후 복사 模式中的逆向引用允许你指定先前捕获组的内容,该组也必须在字符串当前位置被找到。举个例子,如果组 1 的内容能够在当前位置找到的话,\1 就成功否则失败。记住 Python 字符串也是用反斜杠加数据来允许字符串中包含任意字符的,所以当在 RE 中使用逆向引用时确保使用 raw 字符串。
#!python >>> p = re.compile(r'(\b\w+)\s+\1') >>> p.search('Paris in the the spring').group() 'the the' 로그인 후 복사 象这样只是搜索一个字符串的逆向引用并不常见 -- 用这种方式重复数据的文本格式并不多见 -- 但你不久就可以发现它们用在字符串替换上非常有用。
无捕获组和命名组精心设计的 REs 也许会用很多组,既可以捕获感兴趣的子串,又可以分组和结构化 RE 本身。在复杂的 REs 里,追踪组号变得困难。有两个功能可以对这个问题有所帮助。它们也都使用正则表达式扩展的通用语法,因此我们来看看第一个。
#!python >>> m = re.match("([abc])+", "abc") >>> m.groups() ('c',) >>> m = re.match("(?:[abc])+", "abc") >>> m.groups() () 로그인 후 복사 除了捕获匹配组的内容之外,无捕获组与捕获组表现完全一样;你可以在其中放置任何字符,可以用重复元字符如 "*" 来重复它,可以在其他组(无捕获组与捕获组)中嵌套它。(?:...) 对于修改已有组尤其有用,因为你可以不用改变所有其他组号的情况下添加一个新组。捕获组和无捕获组在搜索效率方面也没什么不同,没有哪一个比另一个更快。
#!python >>> p = re.compile(r'(?P<word>\b\w+\b)') >>> m = p.search( '(((( Lots of punctuation )))' ) >>> m.group('word') 'Lots' >>> m.group(1) 'Lots' 로그인 후 복사 命名组是便于使用的,因为它可以让你使用容易记住的名字来代替不得不记住的数字。这里有一个来自 imaplib 模块的 RE 示例: #!python InternalDate = re.compile(r'INTERNALDATE "' r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-' r'(?P<year>[0-9][0-9][0-9][0-9])' r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])' r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])' r'"') 로그인 후 복사 很明显,得到 m.group('zonem') 要比记住得到组 9 要容易得多。
#!python >>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)') >>> p.search('Paris in the the spring').group() 'the the' 로그인 후 복사
前向界定符另一个零宽界定符(zero-width assertion)是前向界定符。前向界定符包括前向肯定界定符和后向肯定界定符,所下所示: (?=...) 前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩馀部分还要尝试界定符的右边。 (?!...) 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功
.*[.].*$ 로그인 후 복사 注意 "." 需要特殊对待,因为它是一个元字符;我把它放在一个字符类中。另外注意后面的 $; 添加这个是为了确保字符串所有的剩馀部分必须被包含在扩展名中。这个正则表达式匹配 "foo.bar"、"autoexec.bat"、 "sendmail.cf" 和 "printers.conf"。
.*[.][^b].*$ 로그인 후 복사 上面的第一次去除 "bat" 的尝试是要求扩展名的第一个字符不是 "b"。这是错误的,因为该模式也不能匹配 "foo.bar"。 .*[.]([^b]..|.[^a].|..[^t])$ 로그인 후 복사 当你试着修补第一个解决方法而要求匹配下列情况之一时表达式更乱了:扩展名的第一个字符不是 "b"; 第二个字符不是 "a";或第三个字符不是 "t"。这样可以接受 "foo.bar" 而拒绝 "autoexec.bat",但这要求只能是三个字符的扩展名而不接受两个字符的扩展名如 "sendmail.cf"。我们将在努力修补它时再次把该模式变得复杂。 .*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$ 로그인 후 복사 在第三次尝试中,第二和第三个字母都变成可选,为的是允许匹配比三个字符更短的扩展名,如 "sendmail.cf"。
.*[.](?!bat$).*$ 로그인 후 복사 前向的意思:如果表达式 bat 在这里没有匹配,尝试模式的其馀部分;如果 bat$ 匹配,整个模式将失败。后面的 $ 被要求是为了确保象 "sample.batch" 这样扩展名以 "bat" 开头的会被允许。
.*[.](?!bat$|exe$).*$ 로그인 후 복사
修改字符串到目前为止,我们简单地搜索了一个静态字符串。正则表达式通常也用不同的方式,通过下面的 `RegexObject` 方法,来修改字符串。
将字符串分片`RegexObject` 的 split() 方法在 RE 匹配的地方将字符串分片,将返回列表。它同字符串的 split() 方法相似但提供更多的定界符;split()只支持空白符和固定字符串。就象你预料的那样,也有一个模块级的 re.split() 函数。 split(string [, maxsplit = 0]) 로그인 후 복사 通过正则表达式将字符串分片。如果捕获括号在 RE 中使用,那么它们的内容也会作为结果列表的一部分返回。如果 maxsplit 非零,那么最多只能分出 maxsplit 个分片。
#!python >>> p = re.compile(r'\W+') >>> p.split('This is a test, short and sweet, of split().') ['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', ''] >>> p.split('This is a test, short and sweet, of split().', 3) ['This', 'is', 'a', 'test, short and sweet, of split().'] 로그인 후 복사 有时,你不仅对定界符之间的文本感兴趣,也需要知道定界符是什么。如果捕获括号在 RE 中使用,那么它们的值也会当作列表的一部分返回。比较下面的调用: #!python >>> p = re.compile(r'\W+') >>> p2 = re.compile(r'(\W+)') >>> p.split('This... is a test.') ['This', 'is', 'a', 'test', ''] >>> p2.split('This... is a test.') ['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', ''] 로그인 후 복사 模块级函数 re.split() 将 RE 作为第一个参数,其他一样。 #!python >>> re.split('[\W]+', 'Words, words, words.') ['Words', 'words', 'words', ''] >>> re.split('([\W]+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] >>> re.split('[\W]+', 'Words, words, words.', 1) ['Words', 'words, words.'] 로그인 후 복사
搜索和替换其他常见的用途就是找到所有模式匹配的字符串并用不同的字符串来替换它们。sub() 方法提供一个替换值,可以是字符串或一个函数,和一个要被处理的字符串。 sub(replacement, string[, count = 0]) 로그인 후 복사 返回的字符串是在字符串中用 RE 最左边不重复的匹配来替换。如果模式没有发现,字符将被没有改变地返回。
#!python >>> p = re.compile( '(blue|white|red)') >>> p.sub( 'colour', 'blue socks and red shoes') 'colour socks and colour shoes' >>> p.sub( 'colour', 'blue socks and red shoes', count=1) 'colour socks and red shoes' 로그인 후 복사 subn() 方法作用一样,但返回的是包含新字符串和替换执行次数的两元组。 #!python >>> p = re.compile( '(blue|white|red)') >>> p.subn( 'colour', 'blue socks and red shoes') ('colour socks and colour shoes', 2) >>> p.subn( 'colour', 'no colours at all') ('no colours at all', 0) 로그인 후 복사 空匹配只有在它们没有紧挨着前一个匹配时才会被替换掉。 #!python >>> p = re.compile('x*') >>> p.sub('-', 'abxd') '-a-b-d-' 로그인 후 복사 如果替换的是一个字符串,任何在其中的反斜杠都会被处理。"\n" 将会被转换成一个换行符,"\r"转换成回车等等。未知的转义如 "\j" 则保持原样。逆向引用,如 "\6",被 RE 中相应的组匹配而被子串替换。这使你可以在替换后的字符串中插入原始文本的一部分。
#!python >>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE) >>> p.sub(r'subsection{\1}','section{First} section{second}') 'subsection{First} subsection{second}' 로그인 후 복사 还可以指定用 (?P #!python >>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE) >>> p.sub(r'subsection{\1}','section{First}') 'subsection{First}' >>> p.sub(r'subsection{\g<1>}','section{First}') 'subsection{First}' >>> p.sub(r'subsection{\g<name>}','section{First}') 'subsection{First}' 로그인 후 복사 替换也可以是一个甚至给你更多控制的函数。如果替换是个函数,该函数将会被模式中每一个不重复的匹配所调用。在每个调用时,函数被作为 `MatchObject` 的匹配函属,并可以使用这个信息去计算预期的字符串并返回它。
#!python >>> def hexrepl( match ): ... "Return the hex string for a decimal number" ... value = int( match.group() ) ... return hex(value) ... >>> p = re.compile(r'\d+') >>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.') 'Call 0xffd2 for printing, 0xc000 for user code.' 로그인 후 복사 当使用模块级的 re.sub() 函数时,模式作为第一个参数。模式也许是一个字符串或一个 `RegexObject`;如果你需要指定正则表达式标志,你必须要么使用 `RegexObject` 做第一个参数,或用使用模式内嵌修正器,如 sub("(?i)b+", "x", "bbbb BBBB") returns 'x x'。
常见问题正则表达式对一些应用程序来说是一个强大的工具,但在有些时候它并不直观而且有时它们不按你期望的运行。本节将指出一些最容易犯的常见错误。
使用字符串方式有时使用 re 模块是个错误。如果你匹配一个固定的字符串或单个的字符类,并且你没有使用 re 的任何象 IGNORECASE 标志的功能,那么就没有必要使用正则表达式了。字符串有一些方法是对固定字符串进行操作的,它们通常快很多,因为都是一个个经过优化的C 小循环,用以代替大的、更具通用性的正则表达式引擎。
match() vs search()match() 函数只检查 RE 是否在字符串开始处匹配,而 search() 则是扫描整个字符串。记住这一区别是重要的。记住,match() 只报告一次成功的匹配,它将从 0 处开始;如果匹配不是从 0 开始的,match() 将不会报告它。 #!python >>> print re.match('super', 'superstition').span() (0, 5) >>> print re.match('super', 'insuperable') None 로그인 후 복사 另一方面,search() 将扫描整个字符串,并报告它找到的第一个匹配。 #!python >>> print re.search('super', 'superstition').span() (0, 5) >>> print re.search('super', 'insuperable').span() (2, 7) 로그인 후 복사 有时你可能倾向于使用 re.match(),只在RE的前面部分添加 .* 。请尽量不要这么做,最好采用 re.search() 代替之。正则表达式编译器会对 REs 做一些分析以便可以在查找匹配时提高处理速度。一个那样的分析机会指出匹配的第一个字符是什么;举个例子,模式 Crow 必须从 "C" 开始匹配。分析机可以让引擎快速扫描字符串以找到开始字符,并只在 "C" 被发现后才开始全部匹配。 添加 .* 会使这个优化失败,这就要扫描到字符串尾部,然后回溯以找到 RE 剩馀部分的匹配。使用 re.search() 代替。
贪婪 vs 不贪婪当重复一个正则表达式时,如用 a*,操作结果是尽可能多地匹配模式。当你试着匹配一对对称的定界符,如 HTML 标志中的尖括号时这个事实经常困扰你。匹配单个 HTML 标志的模式不能正常工作,因为 .* 的本质是“贪婪”的 #!python >>> s = '<html><head><title>Title</title>' >>> len(s) 32 >>> print re.match('<.*>', s).span() (0, 32) >>> print re.match('<.*>', s).group() <html><head><title>Title</title> 로그인 후 복사 RE 匹配 在 "" 中的 "<",.* 消耗掉子符串的剩馀部分。在 RE 中保持更多的左,虽然 > 不能匹配在字符串结尾,因此正则表达式必须一个字符一个字符地回溯,直到它找到 > 的匹配。最终的匹配从 "" 中的 ">",这并不是你所想要的结果。
#!python >>> print re.match('<.*?>', s).group() <html> 로그인 후 복사 注意用正则表达式分析 HTML 或 XML 是痛苦的。变化混乱的模式将处理常见情况,但 HTML 和 XML 则是明显会打破正则表达式的特殊情况;当你编写一个正则表达式去处理所有可能的情况时,模式将变得非常复杂。象这样的任务用 HTML 或 XML 解析器。
不用 re.VERBOSE现在你可能注意到正则表达式的表示是十分紧凑,但它们非常不好读。中度复杂的 REs 可以变成反斜杠、圆括号和元字符的长长集合,以致于使它们很难读懂。
#!python pat = re.compile(r""" \s* # Skip leading whitespace (?P<header>[^:]+) # Header name \s* : # Whitespace, and a colon (?P<value>.*?) # The header's value -- *? used to # lose the following trailing whitespace \s*$ # Trailing whitespace to end-of-line """, re.VERBOSE) 로그인 후 복사 这个要难读得多: #!python pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$") 로그인 후 복사 反馈正则表达式是一个复杂的主题。本文能否有助于你理解呢?那些部分是否不清晰,或在这儿没有找到你所遇到的问题?如果是那样的话,请将建议发给作者以便改进。 相关推荐:
|
위 내용은 PYTHON 정규 표현식의 re 모듈 사용 지침의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!