• python模块:configparser


       1 """Configuration file parser.
       2 
       3 A configuration file consists of sections, lead by a "[section]" header,
       4 and followed by "name: value" entries, with continuations and such in
       5 the style of RFC 822.
       6 
       7 Intrinsic defaults can be specified by passing them into the
       8 ConfigParser constructor as a dictionary.
       9 
      10 class:
      11 
      12 ConfigParser -- responsible for parsing a list of
      13                     configuration files, and managing the parsed database.
      14 
      15     methods:
      16 
      17     __init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
      18              delimiters=('=', ':'), comment_prefixes=('#', ';'),
      19              inline_comment_prefixes=None, strict=True,
      20              empty_lines_in_values=True, default_section='DEFAULT',
      21              interpolation=<unset>, converters=<unset>):
      22         Create the parser. When `defaults' is given, it is initialized into the
      23         dictionary or intrinsic defaults. The keys must be strings, the values
      24         must be appropriate for %()s string interpolation.
      25 
      26         When `dict_type' is given, it will be used to create the dictionary
      27         objects for the list of sections, for the options within a section, and
      28         for the default values.
      29 
      30         When `delimiters' is given, it will be used as the set of substrings
      31         that divide keys from values.
      32 
      33         When `comment_prefixes' is given, it will be used as the set of
      34         substrings that prefix comments in empty lines. Comments can be
      35         indented.
      36 
      37         When `inline_comment_prefixes' is given, it will be used as the set of
      38         substrings that prefix comments in non-empty lines.
      39 
      40         When `strict` is True, the parser won't allow for any section or option
      41         duplicates while reading from a single source (file, string or
      42         dictionary). Default is True.
      43 
      44         When `empty_lines_in_values' is False (default: True), each empty line
      45         marks the end of an option. Otherwise, internal empty lines of
      46         a multiline option are kept as part of the value.
      47 
      48         When `allow_no_value' is True (default: False), options without
      49         values are accepted; the value presented for these is None.
      50 
      51         When `default_section' is given, the name of the special section is
      52         named accordingly. By default it is called ``"DEFAULT"`` but this can
      53         be customized to point to any other valid section name. Its current
      54         value can be retrieved using the ``parser_instance.default_section``
      55         attribute and may be modified at runtime.
      56 
      57         When `interpolation` is given, it should be an Interpolation subclass
      58         instance. It will be used as the handler for option value
      59         pre-processing when using getters. RawConfigParser object s don't do
      60         any sort of interpolation, whereas ConfigParser uses an instance of
      61         BasicInterpolation. The library also provides a ``zc.buildbot``
      62         inspired ExtendedInterpolation implementation.
      63 
      64         When `converters` is given, it should be a dictionary where each key
      65         represents the name of a type converter and each value is a callable
      66         implementing the conversion from string to the desired datatype. Every
      67         converter gets its corresponding get*() method on the parser object and
      68         section proxies.
      69 
      70     sections()
      71         Return all the configuration section names, sans DEFAULT.
      72 
      73     has_section(section)
      74         Return whether the given section exists.
      75 
      76     has_option(section, option)
      77         Return whether the given option exists in the given section.
      78 
      79     options(section)
      80         Return list of configuration options for the named section.
      81 
      82     read(filenames, encoding=None)
      83         Read and parse the list of named configuration files, given by
      84         name.  A single filename is also allowed.  Non-existing files
      85         are ignored.  Return list of successfully read files.
      86 
      87     read_file(f, filename=None)
      88         Read and parse one configuration file, given as a file object.
      89         The filename defaults to f.name; it is only used in error
      90         messages (if f has no `name' attribute, the string `<???>' is used).
      91 
      92     read_string(string)
      93         Read configuration from a given string.
      94 
      95     read_dict(dictionary)
      96         Read configuration from a dictionary. Keys are section names,
      97         values are dictionaries with keys and values that should be present
      98         in the section. If the used dictionary type preserves order, sections
      99         and their keys will be added in order. Values are automatically
     100         converted to strings.
     101 
     102     get(section, option, raw=False, vars=None, fallback=_UNSET)
     103         Return a string value for the named option.  All % interpolations are
     104         expanded in the return values, based on the defaults passed into the
     105         constructor and the DEFAULT section.  Additional substitutions may be
     106         provided using the `vars' argument, which must be a dictionary whose
     107         contents override any pre-existing defaults. If `option' is a key in
     108         `vars', the value from `vars' is used.
     109 
     110     getint(section, options, raw=False, vars=None, fallback=_UNSET)
     111         Like get(), but convert value to an integer.
     112 
     113     getfloat(section, options, raw=False, vars=None, fallback=_UNSET)
     114         Like get(), but convert value to a float.
     115 
     116     getboolean(section, options, raw=False, vars=None, fallback=_UNSET)
     117         Like get(), but convert value to a boolean (currently case
     118         insensitively defined as 0, false, no, off for False, and 1, true,
     119         yes, on for True).  Returns False or True.
     120 
     121     items(section=_UNSET, raw=False, vars=None)
     122         If section is given, return a list of tuples with (name, value) for
     123         each option in the section. Otherwise, return a list of tuples with
     124         (section_name, section_proxy) for each section, including DEFAULTSECT.
     125 
     126     remove_section(section)
     127         Remove the given file section and all its options.
     128 
     129     remove_option(section, option)
     130         Remove the given option from the given section.
     131 
     132     set(section, option, value)
     133         Set the given option.
     134 
     135     write(fp, space_around_delimiters=True)
     136         Write the configuration state in .ini format. If
     137         `space_around_delimiters' is True (the default), delimiters
     138         between keys and values are surrounded by spaces.
     139 """
     140 
     141 from collections.abc import MutableMapping
     142 from collections import OrderedDict as _default_dict, ChainMap as _ChainMap
     143 import functools
     144 import io
     145 import itertools
     146 import os
     147 import re
     148 import sys
     149 import warnings
     150 
     151 __all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
     152            "NoOptionError", "InterpolationError", "InterpolationDepthError",
     153            "InterpolationMissingOptionError", "InterpolationSyntaxError",
     154            "ParsingError", "MissingSectionHeaderError",
     155            "ConfigParser", "SafeConfigParser", "RawConfigParser",
     156            "Interpolation", "BasicInterpolation",  "ExtendedInterpolation",
     157            "LegacyInterpolation", "SectionProxy", "ConverterMapping",
     158            "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"]
     159 
     160 DEFAULTSECT = "DEFAULT"
     161 
     162 MAX_INTERPOLATION_DEPTH = 10
     163 
     164 
     165 
     166 # exception classes
     167 class Error(Exception):
     168     """Base class for ConfigParser exceptions."""
     169 
     170     def __init__(self, msg=''):
     171         self.message = msg
     172         Exception.__init__(self, msg)
     173 
     174     def __repr__(self):
     175         return self.message
     176 
     177     __str__ = __repr__
     178 
     179 
     180 class NoSectionError(Error):
     181     """Raised when no section matches a requested option."""
     182 
     183     def __init__(self, section):
     184         Error.__init__(self, 'No section: %r' % (section,))
     185         self.section = section
     186         self.args = (section, )
     187 
     188 
     189 class DuplicateSectionError(Error):
     190     """Raised when a section is repeated in an input source.
     191 
     192     Possible repetitions that raise this exception are: multiple creation
     193     using the API or in strict parsers when a section is found more than once
     194     in a single input file, string or dictionary.
     195     """
     196 
     197     def __init__(self, section, source=None, lineno=None):
     198         msg = [repr(section), " already exists"]
     199         if source is not None:
     200             message = ["While reading from ", repr(source)]
     201             if lineno is not None:
     202                 message.append(" [line {0:2d}]".format(lineno))
     203             message.append(": section ")
     204             message.extend(msg)
     205             msg = message
     206         else:
     207             msg.insert(0, "Section ")
     208         Error.__init__(self, "".join(msg))
     209         self.section = section
     210         self.source = source
     211         self.lineno = lineno
     212         self.args = (section, source, lineno)
     213 
     214 
     215 class DuplicateOptionError(Error):
     216     """Raised by strict parsers when an option is repeated in an input source.
     217 
     218     Current implementation raises this exception only when an option is found
     219     more than once in a single file, string or dictionary.
     220     """
     221 
     222     def __init__(self, section, option, source=None, lineno=None):
     223         msg = [repr(option), " in section ", repr(section),
     224                " already exists"]
     225         if source is not None:
     226             message = ["While reading from ", repr(source)]
     227             if lineno is not None:
     228                 message.append(" [line {0:2d}]".format(lineno))
     229             message.append(": option ")
     230             message.extend(msg)
     231             msg = message
     232         else:
     233             msg.insert(0, "Option ")
     234         Error.__init__(self, "".join(msg))
     235         self.section = section
     236         self.option = option
     237         self.source = source
     238         self.lineno = lineno
     239         self.args = (section, option, source, lineno)
     240 
     241 
     242 class NoOptionError(Error):
     243     """A requested option was not found."""
     244 
     245     def __init__(self, option, section):
     246         Error.__init__(self, "No option %r in section: %r" %
     247                        (option, section))
     248         self.option = option
     249         self.section = section
     250         self.args = (option, section)
     251 
     252 
     253 class InterpolationError(Error):
     254     """Base class for interpolation-related exceptions."""
     255 
     256     def __init__(self, option, section, msg):
     257         Error.__init__(self, msg)
     258         self.option = option
     259         self.section = section
     260         self.args = (option, section, msg)
     261 
     262 
     263 class InterpolationMissingOptionError(InterpolationError):
     264     """A string substitution required a setting which was not available."""
     265 
     266     def __init__(self, option, section, rawval, reference):
     267         msg = ("Bad value substitution: option {!r} in section {!r} contains "
     268                "an interpolation key {!r} which is not a valid option name. "
     269                "Raw value: {!r}".format(option, section, reference, rawval))
     270         InterpolationError.__init__(self, option, section, msg)
     271         self.reference = reference
     272         self.args = (option, section, rawval, reference)
     273 
     274 
     275 class InterpolationSyntaxError(InterpolationError):
     276     """Raised when the source text contains invalid syntax.
     277 
     278     Current implementation raises this exception when the source text into
     279     which substitutions are made does not conform to the required syntax.
     280     """
     281 
     282 
     283 class InterpolationDepthError(InterpolationError):
     284     """Raised when substitutions are nested too deeply."""
     285 
     286     def __init__(self, option, section, rawval):
     287         msg = ("Recursion limit exceeded in value substitution: option {!r} "
     288                "in section {!r} contains an interpolation key which "
     289                "cannot be substituted in {} steps. Raw value: {!r}"
     290                "".format(option, section, MAX_INTERPOLATION_DEPTH,
     291                          rawval))
     292         InterpolationError.__init__(self, option, section, msg)
     293         self.args = (option, section, rawval)
     294 
     295 
     296 class ParsingError(Error):
     297     """Raised when a configuration file does not follow legal syntax."""
     298 
     299     def __init__(self, source=None, filename=None):
     300         # Exactly one of `source'/`filename' arguments has to be given.
     301         # `filename' kept for compatibility.
     302         if filename and source:
     303             raise ValueError("Cannot specify both `filename' and `source'. "
     304                              "Use `source'.")
     305         elif not filename and not source:
     306             raise ValueError("Required argument `source' not given.")
     307         elif filename:
     308             source = filename
     309         Error.__init__(self, 'Source contains parsing errors: %r' % source)
     310         self.source = source
     311         self.errors = []
     312         self.args = (source, )
     313 
     314     @property
     315     def filename(self):
     316         """Deprecated, use `source'."""
     317         warnings.warn(
     318             "The 'filename' attribute will be removed in future versions.  "
     319             "Use 'source' instead.",
     320             DeprecationWarning, stacklevel=2
     321         )
     322         return self.source
     323 
     324     @filename.setter
     325     def filename(self, value):
     326         """Deprecated, user `source'."""
     327         warnings.warn(
     328             "The 'filename' attribute will be removed in future versions.  "
     329             "Use 'source' instead.",
     330             DeprecationWarning, stacklevel=2
     331         )
     332         self.source = value
     333 
     334     def append(self, lineno, line):
     335         self.errors.append((lineno, line))
     336         self.message += '
    	[line %2d]: %s' % (lineno, line)
     337 
     338 
     339 class MissingSectionHeaderError(ParsingError):
     340     """Raised when a key-value pair is found before any section header."""
     341 
     342     def __init__(self, filename, lineno, line):
     343         Error.__init__(
     344             self,
     345             'File contains no section headers.
    file: %r, line: %d
    %r' %
     346             (filename, lineno, line))
     347         self.source = filename
     348         self.lineno = lineno
     349         self.line = line
     350         self.args = (filename, lineno, line)
     351 
     352 
     353 # Used in parser getters to indicate the default behaviour when a specific
     354 # option is not found it to raise an exception. Created to enable `None' as
     355 # a valid fallback value.
     356 _UNSET = object()
     357 
     358 
     359 class Interpolation:
     360     """Dummy interpolation that passes the value through with no changes."""
     361 
     362     def before_get(self, parser, section, option, value, defaults):
     363         return value
     364 
     365     def before_set(self, parser, section, option, value):
     366         return value
     367 
     368     def before_read(self, parser, section, option, value):
     369         return value
     370 
     371     def before_write(self, parser, section, option, value):
     372         return value
     373 
     374 
     375 class BasicInterpolation(Interpolation):
     376     """Interpolation as implemented in the classic ConfigParser.
     377 
     378     The option values can contain format strings which refer to other values in
     379     the same section, or values in the special default section.
     380 
     381     For example:
     382 
     383         something: %(dir)s/whatever
     384 
     385     would resolve the "%(dir)s" to the value of dir.  All reference
     386     expansions are done late, on demand. If a user needs to use a bare % in
     387     a configuration file, she can escape it by writing %%. Other % usage
     388     is considered a user error and raises `InterpolationSyntaxError'."""
     389 
     390     _KEYCRE = re.compile(r"%(([^)]+))s")
     391 
     392     def before_get(self, parser, section, option, value, defaults):
     393         L = []
     394         self._interpolate_some(parser, option, L, value, section, defaults, 1)
     395         return ''.join(L)
     396 
     397     def before_set(self, parser, section, option, value):
     398         tmp_value = value.replace('%%', '') # escaped percent signs
     399         tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
     400         if '%' in tmp_value:
     401             raise ValueError("invalid interpolation syntax in %r at "
     402                              "position %d" % (value, tmp_value.find('%')))
     403         return value
     404 
     405     def _interpolate_some(self, parser, option, accum, rest, section, map,
     406                           depth):
     407         rawval = parser.get(section, option, raw=True, fallback=rest)
     408         if depth > MAX_INTERPOLATION_DEPTH:
     409             raise InterpolationDepthError(option, section, rawval)
     410         while rest:
     411             p = rest.find("%")
     412             if p < 0:
     413                 accum.append(rest)
     414                 return
     415             if p > 0:
     416                 accum.append(rest[:p])
     417                 rest = rest[p:]
     418             # p is no longer used
     419             c = rest[1:2]
     420             if c == "%":
     421                 accum.append("%")
     422                 rest = rest[2:]
     423             elif c == "(":
     424                 m = self._KEYCRE.match(rest)
     425                 if m is None:
     426                     raise InterpolationSyntaxError(option, section,
     427                         "bad interpolation variable reference %r" % rest)
     428                 var = parser.optionxform(m.group(1))
     429                 rest = rest[m.end():]
     430                 try:
     431                     v = map[var]
     432                 except KeyError:
     433                     raise InterpolationMissingOptionError(
     434                         option, section, rawval, var) from None
     435                 if "%" in v:
     436                     self._interpolate_some(parser, option, accum, v,
     437                                            section, map, depth + 1)
     438                 else:
     439                     accum.append(v)
     440             else:
     441                 raise InterpolationSyntaxError(
     442                     option, section,
     443                     "'%%' must be followed by '%%' or '(', "
     444                     "found: %r" % (rest,))
     445 
     446 
     447 class ExtendedInterpolation(Interpolation):
     448     """Advanced variant of interpolation, supports the syntax used by
     449     `zc.buildout'. Enables interpolation between sections."""
     450 
     451     _KEYCRE = re.compile(r"${([^}]+)}")
     452 
     453     def before_get(self, parser, section, option, value, defaults):
     454         L = []
     455         self._interpolate_some(parser, option, L, value, section, defaults, 1)
     456         return ''.join(L)
     457 
     458     def before_set(self, parser, section, option, value):
     459         tmp_value = value.replace('$$', '') # escaped dollar signs
     460         tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
     461         if '$' in tmp_value:
     462             raise ValueError("invalid interpolation syntax in %r at "
     463                              "position %d" % (value, tmp_value.find('$')))
     464         return value
     465 
     466     def _interpolate_some(self, parser, option, accum, rest, section, map,
     467                           depth):
     468         rawval = parser.get(section, option, raw=True, fallback=rest)
     469         if depth > MAX_INTERPOLATION_DEPTH:
     470             raise InterpolationDepthError(option, section, rawval)
     471         while rest:
     472             p = rest.find("$")
     473             if p < 0:
     474                 accum.append(rest)
     475                 return
     476             if p > 0:
     477                 accum.append(rest[:p])
     478                 rest = rest[p:]
     479             # p is no longer used
     480             c = rest[1:2]
     481             if c == "$":
     482                 accum.append("$")
     483                 rest = rest[2:]
     484             elif c == "{":
     485                 m = self._KEYCRE.match(rest)
     486                 if m is None:
     487                     raise InterpolationSyntaxError(option, section,
     488                         "bad interpolation variable reference %r" % rest)
     489                 path = m.group(1).split(':')
     490                 rest = rest[m.end():]
     491                 sect = section
     492                 opt = option
     493                 try:
     494                     if len(path) == 1:
     495                         opt = parser.optionxform(path[0])
     496                         v = map[opt]
     497                     elif len(path) == 2:
     498                         sect = path[0]
     499                         opt = parser.optionxform(path[1])
     500                         v = parser.get(sect, opt, raw=True)
     501                     else:
     502                         raise InterpolationSyntaxError(
     503                             option, section,
     504                             "More than one ':' found: %r" % (rest,))
     505                 except (KeyError, NoSectionError, NoOptionError):
     506                     raise InterpolationMissingOptionError(
     507                         option, section, rawval, ":".join(path)) from None
     508                 if "$" in v:
     509                     self._interpolate_some(parser, opt, accum, v, sect,
     510                                            dict(parser.items(sect, raw=True)),
     511                                            depth + 1)
     512                 else:
     513                     accum.append(v)
     514             else:
     515                 raise InterpolationSyntaxError(
     516                     option, section,
     517                     "'$' must be followed by '$' or '{', "
     518                     "found: %r" % (rest,))
     519 
     520 
     521 class LegacyInterpolation(Interpolation):
     522     """Deprecated interpolation used in old versions of ConfigParser.
     523     Use BasicInterpolation or ExtendedInterpolation instead."""
     524 
     525     _KEYCRE = re.compile(r"%(([^)]*))s|.")
     526 
     527     def before_get(self, parser, section, option, value, vars):
     528         rawval = value
     529         depth = MAX_INTERPOLATION_DEPTH
     530         while depth:                    # Loop through this until it's done
     531             depth -= 1
     532             if value and "%(" in value:
     533                 replace = functools.partial(self._interpolation_replace,
     534                                             parser=parser)
     535                 value = self._KEYCRE.sub(replace, value)
     536                 try:
     537                     value = value % vars
     538                 except KeyError as e:
     539                     raise InterpolationMissingOptionError(
     540                         option, section, rawval, e.args[0]) from None
     541             else:
     542                 break
     543         if value and "%(" in value:
     544             raise InterpolationDepthError(option, section, rawval)
     545         return value
     546 
     547     def before_set(self, parser, section, option, value):
     548         return value
     549 
     550     @staticmethod
     551     def _interpolation_replace(match, parser):
     552         s = match.group(1)
     553         if s is None:
     554             return match.group()
     555         else:
     556             return "%%(%s)s" % parser.optionxform(s)
     557 
     558 
     559 class RawConfigParser(MutableMapping):
     560     """ConfigParser that does not do interpolation."""
     561 
     562     # Regular expressions for parsing section headers and options
     563     _SECT_TMPL = r"""
     564         [                                 # [
     565         (?P<header>[^]]+)                  # very permissive!
     566         ]                                 # ]
     567         """
     568     _OPT_TMPL = r"""
     569         (?P<option>.*?)                    # very permissive!
     570         s*(?P<vi>{delim})s*              # any number of space/tab,
     571                                            # followed by any of the
     572                                            # allowed delimiters,
     573                                            # followed by any space/tab
     574         (?P<value>.*)$                     # everything up to eol
     575         """
     576     _OPT_NV_TMPL = r"""
     577         (?P<option>.*?)                    # very permissive!
     578         s*(?:                             # any number of space/tab,
     579         (?P<vi>{delim})s*                 # optionally followed by
     580                                            # any of the allowed
     581                                            # delimiters, followed by any
     582                                            # space/tab
     583         (?P<value>.*))?$                   # everything up to eol
     584         """
     585     # Interpolation algorithm to be used if the user does not specify another
     586     _DEFAULT_INTERPOLATION = Interpolation()
     587     # Compiled regular expression for matching sections
     588     SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE)
     589     # Compiled regular expression for matching options with typical separators
     590     OPTCRE = re.compile(_OPT_TMPL.format(delim="=|:"), re.VERBOSE)
     591     # Compiled regular expression for matching options with optional values
     592     # delimited using typical separators
     593     OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE)
     594     # Compiled regular expression for matching leading whitespace in a line
     595     NONSPACECRE = re.compile(r"S")
     596     # Possible boolean values in the configuration.
     597     BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
     598                       '0': False, 'no': False, 'false': False, 'off': False}
     599 
     600     def __init__(self, defaults=None, dict_type=_default_dict,
     601                  allow_no_value=False, *, delimiters=('=', ':'),
     602                  comment_prefixes=('#', ';'), inline_comment_prefixes=None,
     603                  strict=True, empty_lines_in_values=True,
     604                  default_section=DEFAULTSECT,
     605                  interpolation=_UNSET, converters=_UNSET):
     606 
     607         self._dict = dict_type
     608         self._sections = self._dict()
     609         self._defaults = self._dict()
     610         self._converters = ConverterMapping(self)
     611         self._proxies = self._dict()
     612         self._proxies[default_section] = SectionProxy(self, default_section)
     613         if defaults:
     614             for key, value in defaults.items():
     615                 self._defaults[self.optionxform(key)] = value
     616         self._delimiters = tuple(delimiters)
     617         if delimiters == ('=', ':'):
     618             self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE
     619         else:
     620             d = "|".join(re.escape(d) for d in delimiters)
     621             if allow_no_value:
     622                 self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=d),
     623                                           re.VERBOSE)
     624             else:
     625                 self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
     626                                           re.VERBOSE)
     627         self._comment_prefixes = tuple(comment_prefixes or ())
     628         self._inline_comment_prefixes = tuple(inline_comment_prefixes or ())
     629         self._strict = strict
     630         self._allow_no_value = allow_no_value
     631         self._empty_lines_in_values = empty_lines_in_values
     632         self.default_section=default_section
     633         self._interpolation = interpolation
     634         if self._interpolation is _UNSET:
     635             self._interpolation = self._DEFAULT_INTERPOLATION
     636         if self._interpolation is None:
     637             self._interpolation = Interpolation()
     638         if converters is not _UNSET:
     639             self._converters.update(converters)
     640 
     641     def defaults(self):
     642         return self._defaults
     643 
     644     def sections(self):
     645         """Return a list of section names, excluding [DEFAULT]"""
     646         # self._sections will never have [DEFAULT] in it
     647         return list(self._sections.keys())
     648 
     649     def add_section(self, section):
     650         """Create a new section in the configuration.
     651 
     652         Raise DuplicateSectionError if a section by the specified name
     653         already exists. Raise ValueError if name is DEFAULT.
     654         """
     655         if section == self.default_section:
     656             raise ValueError('Invalid section name: %r' % section)
     657 
     658         if section in self._sections:
     659             raise DuplicateSectionError(section)
     660         self._sections[section] = self._dict()
     661         self._proxies[section] = SectionProxy(self, section)
     662 
     663     def has_section(self, section):
     664         """Indicate whether the named section is present in the configuration.
     665 
     666         The DEFAULT section is not acknowledged.
     667         """
     668         return section in self._sections
     669 
     670     def options(self, section):
     671         """Return a list of option names for the given section name."""
     672         try:
     673             opts = self._sections[section].copy()
     674         except KeyError:
     675             raise NoSectionError(section) from None
     676         opts.update(self._defaults)
     677         return list(opts.keys())
     678 
     679     def read(self, filenames, encoding=None):
     680         """Read and parse a filename or a list of filenames.
     681 
     682         Files that cannot be opened are silently ignored; this is
     683         designed so that you can specify a list of potential
     684         configuration file locations (e.g. current directory, user's
     685         home directory, systemwide directory), and all existing
     686         configuration files in the list will be read.  A single
     687         filename may also be given.
     688 
     689         Return list of successfully read files.
     690         """
     691         if isinstance(filenames, (str, os.PathLike)):
     692             filenames = [filenames]
     693         read_ok = []
     694         for filename in filenames:
     695             try:
     696                 with open(filename, encoding=encoding) as fp:
     697                     self._read(fp, filename)
     698             except OSError:
     699                 continue
     700             if isinstance(filename, os.PathLike):
     701                 filename = os.fspath(filename)
     702             read_ok.append(filename)
     703         return read_ok
     704 
     705     def read_file(self, f, source=None):
     706         """Like read() but the argument must be a file-like object.
     707 
     708         The `f' argument must be iterable, returning one line at a time.
     709         Optional second argument is the `source' specifying the name of the
     710         file being read. If not given, it is taken from f.name. If `f' has no
     711         `name' attribute, `<???>' is used.
     712         """
     713         if source is None:
     714             try:
     715                 source = f.name
     716             except AttributeError:
     717                 source = '<???>'
     718         self._read(f, source)
     719 
     720     def read_string(self, string, source='<string>'):
     721         """Read configuration from a given string."""
     722         sfile = io.StringIO(string)
     723         self.read_file(sfile, source)
     724 
     725     def read_dict(self, dictionary, source='<dict>'):
     726         """Read configuration from a dictionary.
     727 
     728         Keys are section names, values are dictionaries with keys and values
     729         that should be present in the section. If the used dictionary type
     730         preserves order, sections and their keys will be added in order.
     731 
     732         All types held in the dictionary are converted to strings during
     733         reading, including section names, option names and keys.
     734 
     735         Optional second argument is the `source' specifying the name of the
     736         dictionary being read.
     737         """
     738         elements_added = set()
     739         for section, keys in dictionary.items():
     740             section = str(section)
     741             try:
     742                 self.add_section(section)
     743             except (DuplicateSectionError, ValueError):
     744                 if self._strict and section in elements_added:
     745                     raise
     746             elements_added.add(section)
     747             for key, value in keys.items():
     748                 key = self.optionxform(str(key))
     749                 if value is not None:
     750                     value = str(value)
     751                 if self._strict and (section, key) in elements_added:
     752                     raise DuplicateOptionError(section, key, source)
     753                 elements_added.add((section, key))
     754                 self.set(section, key, value)
     755 
     756     def readfp(self, fp, filename=None):
     757         """Deprecated, use read_file instead."""
     758         warnings.warn(
     759             "This method will be removed in future versions.  "
     760             "Use 'parser.read_file()' instead.",
     761             DeprecationWarning, stacklevel=2
     762         )
     763         self.read_file(fp, source=filename)
     764 
     765     def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
     766         """Get an option value for a given section.
     767 
     768         If `vars' is provided, it must be a dictionary. The option is looked up
     769         in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
     770         If the key is not found and `fallback' is provided, it is used as
     771         a fallback value. `None' can be provided as a `fallback' value.
     772 
     773         If interpolation is enabled and the optional argument `raw' is False,
     774         all interpolations are expanded in the return values.
     775 
     776         Arguments `raw', `vars', and `fallback' are keyword only.
     777 
     778         The section DEFAULT is special.
     779         """
     780         try:
     781             d = self._unify_values(section, vars)
     782         except NoSectionError:
     783             if fallback is _UNSET:
     784                 raise
     785             else:
     786                 return fallback
     787         option = self.optionxform(option)
     788         try:
     789             value = d[option]
     790         except KeyError:
     791             if fallback is _UNSET:
     792                 raise NoOptionError(option, section)
     793             else:
     794                 return fallback
     795 
     796         if raw or value is None:
     797             return value
     798         else:
     799             return self._interpolation.before_get(self, section, option, value,
     800                                                   d)
     801 
     802     def _get(self, section, conv, option, **kwargs):
     803         return conv(self.get(section, option, **kwargs))
     804 
     805     def _get_conv(self, section, option, conv, *, raw=False, vars=None,
     806                   fallback=_UNSET, **kwargs):
     807         try:
     808             return self._get(section, conv, option, raw=raw, vars=vars,
     809                              **kwargs)
     810         except (NoSectionError, NoOptionError):
     811             if fallback is _UNSET:
     812                 raise
     813             return fallback
     814 
     815     # getint, getfloat and getboolean provided directly for backwards compat
     816     def getint(self, section, option, *, raw=False, vars=None,
     817                fallback=_UNSET, **kwargs):
     818         return self._get_conv(section, option, int, raw=raw, vars=vars,
     819                               fallback=fallback, **kwargs)
     820 
     821     def getfloat(self, section, option, *, raw=False, vars=None,
     822                  fallback=_UNSET, **kwargs):
     823         return self._get_conv(section, option, float, raw=raw, vars=vars,
     824                               fallback=fallback, **kwargs)
     825 
     826     def getboolean(self, section, option, *, raw=False, vars=None,
     827                    fallback=_UNSET, **kwargs):
     828         return self._get_conv(section, option, self._convert_to_boolean,
     829                               raw=raw, vars=vars, fallback=fallback, **kwargs)
     830 
     831     def items(self, section=_UNSET, raw=False, vars=None):
     832         """Return a list of (name, value) tuples for each option in a section.
     833 
     834         All % interpolations are expanded in the return values, based on the
     835         defaults passed into the constructor, unless the optional argument
     836         `raw' is true.  Additional substitutions may be provided using the
     837         `vars' argument, which must be a dictionary whose contents overrides
     838         any pre-existing defaults.
     839 
     840         The section DEFAULT is special.
     841         """
     842         if section is _UNSET:
     843             return super().items()
     844         d = self._defaults.copy()
     845         try:
     846             d.update(self._sections[section])
     847         except KeyError:
     848             if section != self.default_section:
     849                 raise NoSectionError(section)
     850         # Update with the entry specific variables
     851         if vars:
     852             for key, value in vars.items():
     853                 d[self.optionxform(key)] = value
     854         value_getter = lambda option: self._interpolation.before_get(self,
     855             section, option, d[option], d)
     856         if raw:
     857             value_getter = lambda option: d[option]
     858         return [(option, value_getter(option)) for option in d.keys()]
     859 
     860     def popitem(self):
     861         """Remove a section from the parser and return it as
     862         a (section_name, section_proxy) tuple. If no section is present, raise
     863         KeyError.
     864 
     865         The section DEFAULT is never returned because it cannot be removed.
     866         """
     867         for key in self.sections():
     868             value = self[key]
     869             del self[key]
     870             return key, value
     871         raise KeyError
     872 
     873     def optionxform(self, optionstr):
     874         return optionstr.lower()
     875 
     876     def has_option(self, section, option):
     877         """Check for the existence of a given option in a given section.
     878         If the specified `section' is None or an empty string, DEFAULT is
     879         assumed. If the specified `section' does not exist, returns False."""
     880         if not section or section == self.default_section:
     881             option = self.optionxform(option)
     882             return option in self._defaults
     883         elif section not in self._sections:
     884             return False
     885         else:
     886             option = self.optionxform(option)
     887             return (option in self._sections[section]
     888                     or option in self._defaults)
     889 
     890     def set(self, section, option, value=None):
     891         """Set an option."""
     892         if value:
     893             value = self._interpolation.before_set(self, section, option,
     894                                                    value)
     895         if not section or section == self.default_section:
     896             sectdict = self._defaults
     897         else:
     898             try:
     899                 sectdict = self._sections[section]
     900             except KeyError:
     901                 raise NoSectionError(section) from None
     902         sectdict[self.optionxform(option)] = value
     903 
     904     def write(self, fp, space_around_delimiters=True):
     905         """Write an .ini-format representation of the configuration state.
     906 
     907         If `space_around_delimiters' is True (the default), delimiters
     908         between keys and values are surrounded by spaces.
     909         """
     910         if space_around_delimiters:
     911             d = " {} ".format(self._delimiters[0])
     912         else:
     913             d = self._delimiters[0]
     914         if self._defaults:
     915             self._write_section(fp, self.default_section,
     916                                     self._defaults.items(), d)
     917         for section in self._sections:
     918             self._write_section(fp, section,
     919                                 self._sections[section].items(), d)
     920 
     921     def _write_section(self, fp, section_name, section_items, delimiter):
     922         """Write a single section to the specified `fp'."""
     923         fp.write("[{}]
    ".format(section_name))
     924         for key, value in section_items:
     925             value = self._interpolation.before_write(self, section_name, key,
     926                                                      value)
     927             if value is not None or not self._allow_no_value:
     928                 value = delimiter + str(value).replace('
    ', '
    	')
     929             else:
     930                 value = ""
     931             fp.write("{}{}
    ".format(key, value))
     932         fp.write("
    ")
     933 
     934     def remove_option(self, section, option):
     935         """Remove an option."""
     936         if not section or section == self.default_section:
     937             sectdict = self._defaults
     938         else:
     939             try:
     940                 sectdict = self._sections[section]
     941             except KeyError:
     942                 raise NoSectionError(section) from None
     943         option = self.optionxform(option)
     944         existed = option in sectdict
     945         if existed:
     946             del sectdict[option]
     947         return existed
     948 
     949     def remove_section(self, section):
     950         """Remove a file section."""
     951         existed = section in self._sections
     952         if existed:
     953             del self._sections[section]
     954             del self._proxies[section]
     955         return existed
     956 
     957     def __getitem__(self, key):
     958         if key != self.default_section and not self.has_section(key):
     959             raise KeyError(key)
     960         return self._proxies[key]
     961 
     962     def __setitem__(self, key, value):
     963         # To conform with the mapping protocol, overwrites existing values in
     964         # the section.
     965 
     966         # XXX this is not atomic if read_dict fails at any point. Then again,
     967         # no update method in configparser is atomic in this implementation.
     968         if key == self.default_section:
     969             self._defaults.clear()
     970         elif key in self._sections:
     971             self._sections[key].clear()
     972         self.read_dict({key: value})
     973 
     974     def __delitem__(self, key):
     975         if key == self.default_section:
     976             raise ValueError("Cannot remove the default section.")
     977         if not self.has_section(key):
     978             raise KeyError(key)
     979         self.remove_section(key)
     980 
     981     def __contains__(self, key):
     982         return key == self.default_section or self.has_section(key)
     983 
     984     def __len__(self):
     985         return len(self._sections) + 1 # the default section
     986 
     987     def __iter__(self):
     988         # XXX does it break when underlying container state changed?
     989         return itertools.chain((self.default_section,), self._sections.keys())
     990 
     991     def _read(self, fp, fpname):
     992         """Parse a sectioned configuration file.
     993 
     994         Each section in a configuration file contains a header, indicated by
     995         a name in square brackets (`[]'), plus key/value options, indicated by
     996         `name' and `value' delimited with a specific substring (`=' or `:' by
     997         default).
     998 
     999         Values can span multiple lines, as long as they are indented deeper
    1000         than the first line of the value. Depending on the parser's mode, blank
    1001         lines may be treated as parts of multiline values or ignored.
    1002 
    1003         Configuration files may include comments, prefixed by specific
    1004         characters (`#' and `;' by default). Comments may appear on their own
    1005         in an otherwise empty line or may be entered in lines holding values or
    1006         section names.
    1007         """
    1008         elements_added = set()
    1009         cursect = None                        # None, or a dictionary
    1010         sectname = None
    1011         optname = None
    1012         lineno = 0
    1013         indent_level = 0
    1014         e = None                              # None, or an exception
    1015         for lineno, line in enumerate(fp, start=1):
    1016             comment_start = sys.maxsize
    1017             # strip inline comments
    1018             inline_prefixes = {p: -1 for p in self._inline_comment_prefixes}
    1019             while comment_start == sys.maxsize and inline_prefixes:
    1020                 next_prefixes = {}
    1021                 for prefix, index in inline_prefixes.items():
    1022                     index = line.find(prefix, index+1)
    1023                     if index == -1:
    1024                         continue
    1025                     next_prefixes[prefix] = index
    1026                     if index == 0 or (index > 0 and line[index-1].isspace()):
    1027                         comment_start = min(comment_start, index)
    1028                 inline_prefixes = next_prefixes
    1029             # strip full line comments
    1030             for prefix in self._comment_prefixes:
    1031                 if line.strip().startswith(prefix):
    1032                     comment_start = 0
    1033                     break
    1034             if comment_start == sys.maxsize:
    1035                 comment_start = None
    1036             value = line[:comment_start].strip()
    1037             if not value:
    1038                 if self._empty_lines_in_values:
    1039                     # add empty line to the value, but only if there was no
    1040                     # comment on the line
    1041                     if (comment_start is None and
    1042                         cursect is not None and
    1043                         optname and
    1044                         cursect[optname] is not None):
    1045                         cursect[optname].append('') # newlines added at join
    1046                 else:
    1047                     # empty line marks end of value
    1048                     indent_level = sys.maxsize
    1049                 continue
    1050             # continuation line?
    1051             first_nonspace = self.NONSPACECRE.search(line)
    1052             cur_indent_level = first_nonspace.start() if first_nonspace else 0
    1053             if (cursect is not None and optname and
    1054                 cur_indent_level > indent_level):
    1055                 cursect[optname].append(value)
    1056             # a section header or option header?
    1057             else:
    1058                 indent_level = cur_indent_level
    1059                 # is it a section header?
    1060                 mo = self.SECTCRE.match(value)
    1061                 if mo:
    1062                     sectname = mo.group('header')
    1063                     if sectname in self._sections:
    1064                         if self._strict and sectname in elements_added:
    1065                             raise DuplicateSectionError(sectname, fpname,
    1066                                                         lineno)
    1067                         cursect = self._sections[sectname]
    1068                         elements_added.add(sectname)
    1069                     elif sectname == self.default_section:
    1070                         cursect = self._defaults
    1071                     else:
    1072                         cursect = self._dict()
    1073                         self._sections[sectname] = cursect
    1074                         self._proxies[sectname] = SectionProxy(self, sectname)
    1075                         elements_added.add(sectname)
    1076                     # So sections can't start with a continuation line
    1077                     optname = None
    1078                 # no section header in the file?
    1079                 elif cursect is None:
    1080                     raise MissingSectionHeaderError(fpname, lineno, line)
    1081                 # an option line?
    1082                 else:
    1083                     mo = self._optcre.match(value)
    1084                     if mo:
    1085                         optname, vi, optval = mo.group('option', 'vi', 'value')
    1086                         if not optname:
    1087                             e = self._handle_error(e, fpname, lineno, line)
    1088                         optname = self.optionxform(optname.rstrip())
    1089                         if (self._strict and
    1090                             (sectname, optname) in elements_added):
    1091                             raise DuplicateOptionError(sectname, optname,
    1092                                                        fpname, lineno)
    1093                         elements_added.add((sectname, optname))
    1094                         # This check is fine because the OPTCRE cannot
    1095                         # match if it would set optval to None
    1096                         if optval is not None:
    1097                             optval = optval.strip()
    1098                             cursect[optname] = [optval]
    1099                         else:
    1100                             # valueless option handling
    1101                             cursect[optname] = None
    1102                     else:
    1103                         # a non-fatal parsing error occurred. set up the
    1104                         # exception but keep going. the exception will be
    1105                         # raised at the end of the file and will contain a
    1106                         # list of all bogus lines
    1107                         e = self._handle_error(e, fpname, lineno, line)
    1108         self._join_multiline_values()
    1109         # if any parsing errors occurred, raise an exception
    1110         if e:
    1111             raise e
    1112 
    1113     def _join_multiline_values(self):
    1114         defaults = self.default_section, self._defaults
    1115         all_sections = itertools.chain((defaults,),
    1116                                        self._sections.items())
    1117         for section, options in all_sections:
    1118             for name, val in options.items():
    1119                 if isinstance(val, list):
    1120                     val = '
    '.join(val).rstrip()
    1121                 options[name] = self._interpolation.before_read(self,
    1122                                                                 section,
    1123                                                                 name, val)
    1124 
    1125     def _handle_error(self, exc, fpname, lineno, line):
    1126         if not exc:
    1127             exc = ParsingError(fpname)
    1128         exc.append(lineno, repr(line))
    1129         return exc
    1130 
    1131     def _unify_values(self, section, vars):
    1132         """Create a sequence of lookups with 'vars' taking priority over
    1133         the 'section' which takes priority over the DEFAULTSECT.
    1134 
    1135         """
    1136         sectiondict = {}
    1137         try:
    1138             sectiondict = self._sections[section]
    1139         except KeyError:
    1140             if section != self.default_section:
    1141                 raise NoSectionError(section)
    1142         # Update with the entry specific variables
    1143         vardict = {}
    1144         if vars:
    1145             for key, value in vars.items():
    1146                 if value is not None:
    1147                     value = str(value)
    1148                 vardict[self.optionxform(key)] = value
    1149         return _ChainMap(vardict, sectiondict, self._defaults)
    1150 
    1151     def _convert_to_boolean(self, value):
    1152         """Return a boolean value translating from other types if necessary.
    1153         """
    1154         if value.lower() not in self.BOOLEAN_STATES:
    1155             raise ValueError('Not a boolean: %s' % value)
    1156         return self.BOOLEAN_STATES[value.lower()]
    1157 
    1158     def _validate_value_types(self, *, section="", option="", value=""):
    1159         """Raises a TypeError for non-string values.
    1160 
    1161         The only legal non-string value if we allow valueless
    1162         options is None, so we need to check if the value is a
    1163         string if:
    1164         - we do not allow valueless options, or
    1165         - we allow valueless options but the value is not None
    1166 
    1167         For compatibility reasons this method is not used in classic set()
    1168         for RawConfigParsers. It is invoked in every case for mapping protocol
    1169         access and in ConfigParser.set().
    1170         """
    1171         if not isinstance(section, str):
    1172             raise TypeError("section names must be strings")
    1173         if not isinstance(option, str):
    1174             raise TypeError("option keys must be strings")
    1175         if not self._allow_no_value or value:
    1176             if not isinstance(value, str):
    1177                 raise TypeError("option values must be strings")
    1178 
    1179     @property
    1180     def converters(self):
    1181         return self._converters
    1182 
    1183 
    1184 class ConfigParser(RawConfigParser):
    1185     """ConfigParser implementing interpolation."""
    1186 
    1187     _DEFAULT_INTERPOLATION = BasicInterpolation()
    1188 
    1189     def set(self, section, option, value=None):
    1190         """Set an option.  Extends RawConfigParser.set by validating type and
    1191         interpolation syntax on the value."""
    1192         self._validate_value_types(option=option, value=value)
    1193         super().set(section, option, value)
    1194 
    1195     def add_section(self, section):
    1196         """Create a new section in the configuration.  Extends
    1197         RawConfigParser.add_section by validating if the section name is
    1198         a string."""
    1199         self._validate_value_types(section=section)
    1200         super().add_section(section)
    1201 
    1202 
    1203 class SafeConfigParser(ConfigParser):
    1204     """ConfigParser alias for backwards compatibility purposes."""
    1205 
    1206     def __init__(self, *args, **kwargs):
    1207         super().__init__(*args, **kwargs)
    1208         warnings.warn(
    1209             "The SafeConfigParser class has been renamed to ConfigParser "
    1210             "in Python 3.2. This alias will be removed in future versions."
    1211             " Use ConfigParser directly instead.",
    1212             DeprecationWarning, stacklevel=2
    1213         )
    1214 
    1215 
    1216 class SectionProxy(MutableMapping):
    1217     """A proxy for a single section from a parser."""
    1218 
    1219     def __init__(self, parser, name):
    1220         """Creates a view on a section of the specified `name` in `parser`."""
    1221         self._parser = parser
    1222         self._name = name
    1223         for conv in parser.converters:
    1224             key = 'get' + conv
    1225             getter = functools.partial(self.get, _impl=getattr(parser, key))
    1226             setattr(self, key, getter)
    1227 
    1228     def __repr__(self):
    1229         return '<Section: {}>'.format(self._name)
    1230 
    1231     def __getitem__(self, key):
    1232         if not self._parser.has_option(self._name, key):
    1233             raise KeyError(key)
    1234         return self._parser.get(self._name, key)
    1235 
    1236     def __setitem__(self, key, value):
    1237         self._parser._validate_value_types(option=key, value=value)
    1238         return self._parser.set(self._name, key, value)
    1239 
    1240     def __delitem__(self, key):
    1241         if not (self._parser.has_option(self._name, key) and
    1242                 self._parser.remove_option(self._name, key)):
    1243             raise KeyError(key)
    1244 
    1245     def __contains__(self, key):
    1246         return self._parser.has_option(self._name, key)
    1247 
    1248     def __len__(self):
    1249         return len(self._options())
    1250 
    1251     def __iter__(self):
    1252         return self._options().__iter__()
    1253 
    1254     def _options(self):
    1255         if self._name != self._parser.default_section:
    1256             return self._parser.options(self._name)
    1257         else:
    1258             return self._parser.defaults()
    1259 
    1260     @property
    1261     def parser(self):
    1262         # The parser object of the proxy is read-only.
    1263         return self._parser
    1264 
    1265     @property
    1266     def name(self):
    1267         # The name of the section on a proxy is read-only.
    1268         return self._name
    1269 
    1270     def get(self, option, fallback=None, *, raw=False, vars=None,
    1271             _impl=None, **kwargs):
    1272         """Get an option value.
    1273 
    1274         Unless `fallback` is provided, `None` will be returned if the option
    1275         is not found.
    1276 
    1277         """
    1278         # If `_impl` is provided, it should be a getter method on the parser
    1279         # object that provides the desired type conversion.
    1280         if not _impl:
    1281             _impl = self._parser.get
    1282         return _impl(self._name, option, raw=raw, vars=vars,
    1283                      fallback=fallback, **kwargs)
    1284 
    1285 
    1286 class ConverterMapping(MutableMapping):
    1287     """Enables reuse of get*() methods between the parser and section proxies.
    1288 
    1289     If a parser class implements a getter directly, the value for the given
    1290     key will be ``None``. The presence of the converter name here enables
    1291     section proxies to find and use the implementation on the parser class.
    1292     """
    1293 
    1294     GETTERCRE = re.compile(r"^get(?P<name>.+)$")
    1295 
    1296     def __init__(self, parser):
    1297         self._parser = parser
    1298         self._data = {}
    1299         for getter in dir(self._parser):
    1300             m = self.GETTERCRE.match(getter)
    1301             if not m or not callable(getattr(self._parser, getter)):
    1302                 continue
    1303             self._data[m.group('name')] = None   # See class docstring.
    1304 
    1305     def __getitem__(self, key):
    1306         return self._data[key]
    1307 
    1308     def __setitem__(self, key, value):
    1309         try:
    1310             k = 'get' + key
    1311         except TypeError:
    1312             raise ValueError('Incompatible key: {} (type: {})'
    1313                              ''.format(key, type(key)))
    1314         if k == 'get':
    1315             raise ValueError('Incompatible key: cannot use "" as a name')
    1316         self._data[key] = value
    1317         func = functools.partial(self._parser._get_conv, conv=value)
    1318         func.converter = value
    1319         setattr(self._parser, k, func)
    1320         for proxy in self._parser.values():
    1321             getter = functools.partial(proxy.get, _impl=func)
    1322             setattr(proxy, k, getter)
    1323 
    1324     def __delitem__(self, key):
    1325         try:
    1326             k = 'get' + (key or None)
    1327         except TypeError:
    1328             raise KeyError(key)
    1329         del self._data[key]
    1330         for inst in itertools.chain((self._parser,), self._parser.values()):
    1331             try:
    1332                 delattr(inst, k)
    1333             except AttributeError:
    1334                 # don't raise since the entry was present in _data, silently
    1335                 # clean up
    1336                 continue
    1337 
    1338     def __iter__(self):
    1339         return iter(self._data)
    1340 
    1341     def __len__(self):
    1342         return len(self._data)
    每天更新一点点,温习一点点点,进步一点点
  • 相关阅读:
    websocket在线测试工具
    短信线上发送错误,线下成功
    nginx添加stream模块1.20
    sort 的使用
    实验、进程的同步与互斥——生产者消费者
    实验、可变分区存储管理系统模拟 —— 最先适应分配算法
    test
    c++的大数阶乘算法
    C#解析JSON字符串总结
    C++ 查看单个类对象模型利用Vs开发者命令提示工具
  • 原文地址:https://www.cnblogs.com/lmgsanm/p/8386628.html
Copyright © 2020-2023  润新知