简体   繁体   English

Python 中 json 加载中的单引号和双引号

[英]Single versus double quotes in json loads in Python

I notice that single quotes cause simplejson 's loads function to fail:我注意到单引号导致simplejsonloads函数失败:

>>> import simplejson as json
>>> json.loads("\"foo\"")
'foo'
>>> json.loads("\'foo\'")
Traceback (most recent call last):
...
ValueError: No JSON object could be decoded

I'm parsing things like: foo = ["a", "b", "c"] from a textfile into lists in Python and would like to also accept foo = ['a', 'b', 'c'] .我正在将文本文件中的foo = ["a", "b", "c"]解析为 Python 中的列表,并且还想接受foo = ['a', 'b', 'c'] . simplejson is convenient for making foo automatically into a list. simplejson便于将foo自动生成列表。

How can I get loads to accept single quotes, or automatically substitute double for single quotes without wrecking the input?如何在不破坏输入的情况下让loads接受单引号,或自动用双引号替换单引号? thanks.谢谢。

Use the proper tool for the job, you are not parsing JSON but Python, so use ast.literal_eval() instead:使用适当的工具来完成这项工作,您不是在解析 JSON 而是 Python,因此请改用ast.literal_eval()

>>> import ast
>>> ast.literal_eval('["a", "b", "c"]')
['a', 'b', 'c']
>>> ast.literal_eval("['a', 'b', 'c']")
['a', 'b', 'c']
>>> ast.literal_eval('["mixed", \'quoting\', """styles"""]')
['mixed', 'quoting', 'styles']
  • JSON documents always use double quotes for strings, use UTF-16 for \\uhhhh hex escape syntax, have {...} objects for key-value pairs with keys always strings and sequences are always [...] lists, and use null , true and false values; JSON 文档总是对字符串使用双引号,对\\uhhhh十六进制转义语法使用 UTF-16,对键值对使用{...}对象,键总是字符串,序列总是[...]列表,并使用nulltruefalse值; note the lowercase booleans.注意小写的布尔值。 Numbers come in integer and floating point forms.数字有整数和浮点形式。

  • In Python, string representations can use single and double quotes, Unicode escapes use \\uhhhh and \\Uhhhhhhhh forms (no UTF-16 surrogate pairs), dictionaries with {...} display syntax can have keys in many different types rather than just strings, sequences can be lists ( [...] ) but can also use tuples ( (...) ), or you could have other container types still.在 Python 中,字符串表示可以使用单引号和双引号,Unicode 转义使用\\uhhhh \\Uhhhhhhhh\\Uhhhhhhhh形式(没有 UTF-16 代理对),具有{...}显示语法的字典可以具有许多不同类型的键,而不仅仅是字符串, 序列可以是列表 ( [...] ),但也可以使用元组 ( (...) ),或者您仍然可以使用其他容器类型。 Python has None , True and False (Titlecase!) and numbers come in integers, floats, and complex forms. Python 有NoneTrueFalse (标题大写!),数字有整数、浮点数和复数形式。

Confusing one with the other can either lead to parse errors or subtle problems when decoding happened to succeed but the data has been wrongly interpreted, such as with escaped non-BMP codepoints such Emoji.当解码碰巧成功但数据被错误解释时,将一个与另一个混淆可能导致解析错误或微妙的问题,例如使用转义的非 BMP 代码点,例如表情符号。 Make sure to use the right method to decode them!确保使用正确的方法来解码它们! And in most cases when you do have Python syntax data someone actually used the wrong method of encoding and only accidentally produced Python representations.并且在大多数情况下,当您确实拥有 Python 语法数据时,有人实际上使用了错误的编码方法,并且只是意外地生成了 Python 表示。 See if the source needs fixing in that case;在这种情况下,查看源是否需要修复; usually the output was produced by using str(object) where json.dumps(obj) should have been used instead.通常输出是通过使用str(object)产生的,其中json.dumps(obj)应该被使用。

I started at this StackOverflow question when looking to solve this myself.当我想自己解决这个问题时,我从这个 StackOverflow 问题开始。

The solution to use ast.literal_eval() did not work for all my cases, as the text also occasionally had boolean constants true/false which are not recognised as Python tokens (which are capitalised.)使用ast.literal_eval()的解决方案不适用于我的所有情况,因为文本偶尔也有布尔常量 true/false,它们不被识别为 Python 标记(大写)。

To solve it for myself, I wrok a custom JSONDecoder which plugs into the standard json Python package.为了自己解决这个问题,我编写了一个自定义的 JSONDecoder ,它插入到标准的 json Python 包中。

pip install git+https://github.com/jpz/tolerantjsondecoder.git

Maybe this may be of use to the next person.也许这对下一个人有用。

Also, to note, after I completed this, I later found the demjson library which appears to be a more complete solution, however I've not evaluated it.另外,要注意的是,在我完成这个之后,我后来发现了demjson库,它似乎是一个更完整的解决方案,但是我没有对其进行评估。

Demjson does your work but it is extremely slow and i mean very slow compare to simplejson. Demjson 可以完成您的工作,但它非常慢,我的意思是与 simplejson 相比非常慢。 I don't recommend it for production environment.我不推荐它用于生产环境。

ast.literal_eval() and yaml also doesn't work on all json so you need a more stable solution like simplejson. ast.literal_eval() 和 yaml 也不适用于所有 json,因此您需要更稳定的解决方案,例如 simplejson。

If you tweak simplejson a little bit then it can do your work.如果您稍微调整 simplejson,那么它就可以完成您的工作。 I myself have done it and sharing this code.我自己已经完成并分享了这段代码。

I am telling it in 2 points我分2点说

1) Download simplejson from github and add it to your project. 1) 从 github 下载 simplejson 并将其添加到您的项目中。 2) Now simplejson has decoder.py python file. 2)现在simplejson有decoder.py python文件。 Replace that file code with this code用此代码替换该文件代码

"""Implementation of JSONDecoder
"""
from __future__ import absolute_import
import re
import sys
import struct
from .compat import u, text_type, binary_type, PY3, unichr
from .scanner import make_scanner, JSONDecodeError

def _import_c_scanstring():
    try:
        from ._speedups import scanstring
        return scanstring
    except ImportError:
        return None
c_scanstring = _import_c_scanstring()

# NOTE (3.1.0): JSONDecodeError may still be imported from this module for
# compatibility, but it was never in the __all__
__all__ = ['JSONDecoder']

FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL

def _floatconstants():
    if sys.version_info < (2, 6):
        _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
        nan, inf = struct.unpack('>dd', _BYTES)
    else:
        nan = float('nan')
        inf = float('inf')
    return nan, inf, -inf

NaN, PosInf, NegInf = _floatconstants()

_CONSTANTS = {
    '-Infinity': NegInf,
    'Infinity': PosInf,
    'NaN': NaN,
}

STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)

# Changed Code Here. Added These Two Lines

STRINGCHUNKUNQUOTED = re.compile(r'(.*?)([:\\\x00-\x1f])', FLAGS)
STRINGCHUNKSINGLEQUOTED = re.compile(r"(.*?)(['\\\x00-\x1f])", FLAGS)

BACKSLASH = {
    '"': u('"'), '\\': u('\u005c'), '/': u('/'),
    'b': u('\b'), 'f': u('\f'), 'n': u('\n'), 'r': u('\r'), 't': u('\t'),
}
# Changed Code Here.
SINGLE_QUOTE_BACKSLASH = {
    "'": u("'"), '\\': u('\u005c'), '/': u('/'),
    'b': u('\b'), 'f': u('\f'), 'n': u('\n'), 'r': u('\r'), 't': u('\t'),
}

DEFAULT_ENCODING = "utf-8"

# Changed Code Here.
def parse_single_quoted_string(s, end, encoding=None, strict=True):
    return py_scanstring(s, end, encoding, strict, SINGLE_QUOTE_BACKSLASH, STRINGCHUNKSINGLEQUOTED.match)


def py_scanstring(s, end, encoding=None, strict=True,
        _b=BACKSLASH, _m=STRINGCHUNK.match, _join=u('').join,
        _PY3=PY3, _maxunicode=sys.maxunicode):
    """Scan the string s for a JSON string. End is the index of the
    character in s after the quote that started the JSON string.
    Unescapes all valid JSON string escape sequences and raises ValueError
    on attempt to decode an invalid string. If strict is False then literal
    control characters are allowed in the string.

    Returns a tuple of the decoded string and the index of the character in s
    after the end quote."""
    if encoding is None:
        encoding = DEFAULT_ENCODING
    chunks = []
    _append = chunks.append
    begin = end - 1
    while 1:
        chunk = _m(s, end)
        if chunk is None:
            raise JSONDecodeError(
                "Unterminated string starting at", s, begin)
        end = chunk.end()
        content, terminator = chunk.groups()
        # Content is contains zero or more unescaped string characters
        if content:
            if not _PY3 and not isinstance(content, text_type):
                content = text_type(content, encoding)
            _append(content)
        # Terminator is the end of string, a literal control character,
        # or a backslash denoting that an escape sequence follows

        # Changed Code Here.

        if not is_not_quote(terminator):
            break
        elif terminator != '\\':
            if strict:
                msg = "Invalid control character %r at"
                raise JSONDecodeError(msg, s, end)
            else:
                _append(terminator)
                continue
        try:
            esc = s[end]
        except IndexError:
            raise JSONDecodeError(
                "Unterminated string starting at", s, begin)
        # If not a unicode escape sequence, must be in the lookup table
        if esc != 'u':
            try:
                char = _b[esc]
            except KeyError:
                msg = "Invalid \\X escape sequence %r"
                raise JSONDecodeError(msg, s, end)
            end += 1
        else:
            # Unicode escape sequence
            msg = "Invalid \\uXXXX escape sequence"
            esc = s[end + 1:end + 5]
            escX = esc[1:2]
            if len(esc) != 4 or escX == 'x' or escX == 'X':
                raise JSONDecodeError(msg, s, end - 1)
            try:
                uni = int(esc, 16)
            except ValueError:
                raise JSONDecodeError(msg, s, end - 1)
            end += 5
            # Check for surrogate pair on UCS-4 systems
            # Note that this will join high/low surrogate pairs
            # but will also pass unpaired surrogates through
            if (_maxunicode > 65535 and
                uni & 0xfc00 == 0xd800 and
                s[end:end + 2] == '\\u'):
                esc2 = s[end + 2:end + 6]
                escX = esc2[1:2]
                if len(esc2) == 4 and not (escX == 'x' or escX == 'X'):
                    try:
                        uni2 = int(esc2, 16)
                    except ValueError:
                        raise JSONDecodeError(msg, s, end)
                    if uni2 & 0xfc00 == 0xdc00:
                        uni = 0x10000 + (((uni - 0xd800) << 10) |
                                         (uni2 - 0xdc00))
                        end += 6
            char = unichr(uni)
        # Append the unescaped character
        _append(char)
    return _join(chunks), end


# Use speedup if available
scanstring = c_scanstring or py_scanstring

WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
WHITESPACE_STR = ' \t\n\r'

# Changed Code Here.
UNQUOTEDDICT = {'/': '/', '\\': '\\', ';': ';', '#': '#',
            '=': '=', '{': '{', '}': '}', '[': '[', ']': ']',
            ':': ':', ',': ',', ' ': ' ', '\t': '\t',
            '\f': '\f', '\r': '\r', '\n': '\n'}

# Changed Code Here.
QUOTE_DICT = {'"': '"', "'": "'"}

# Changed Code Here.
def is_literal(char):
    return not UNQUOTEDDICT.get(char, None)

# Changed Code Here.
def is_not_quote(char):
    return not QUOTE_DICT.get(char, None)


# Changed Code Here.
def nexUnquotedKey(s, end):
    chunk = STRINGCHUNKUNQUOTED.match(s,end)
    for i in range(chunk.end()):
        index = i+end
        if not is_literal(s[index]):
            return s[end:index], index




def JSONObject(state, encoding, strict, scan_once, object_hook,
        object_pairs_hook, memo=None,
        _w=WHITESPACE.match, _ws=WHITESPACE_STR):
    (s, end) = state
    # Backwards compatibility
    if memo is None:
        memo = {}
    memo_get = memo.setdefault
    pairs = []
    # Use a slice to prevent IndexError from being raised, the following
    # check will raise a more specific ValueError if the string is empty
    nextchar = s[end:end + 1]
    # Normally we expect nextchar == '"'

    literal_check = False

    # Changed Code Here.
    if is_not_quote(nextchar):
        if nextchar in _ws:
            end = _w(s, end).end()
            nextchar = s[end:end + 1]
        # Trivial empty object
        literal_check = is_literal(nextchar)
        if nextchar == '}':
            if object_pairs_hook is not None:
                result = object_pairs_hook(pairs)
                return result, end + 1
            pairs = {}
            if object_hook is not None:
                pairs = object_hook(pairs)
            return pairs, end + 1
        elif nextchar != '"' and not literal_check:  # Changed Code Here.
            raise JSONDecodeError(
                "Expecting property name enclosed in double quotes",
                s, end)

    # Changed Code Here.
    if not literal_check:
        end += 1

    while True:
        if literal_check:
            key, end = nexUnquotedKey(s,end)
        else:
            # Changed Code Here.
            if nextchar == "'":
                key, end = scanstring(s, end, encoding, strict, SINGLE_QUOTE_BACKSLASH, STRINGCHUNKSINGLEQUOTED.match)
            else:
                key, end = scanstring(s, end, encoding, strict)

        key = memo_get(key, key)

        # To skip some function call overhead we optimize the fast paths where
        # the JSON key separator is ": " or just ":".
        if s[end:end + 1] != ':':
            end = _w(s, end).end()
            if s[end:end + 1] != ':':
                raise JSONDecodeError("Expecting ':' delimiter", s, end)

        end += 1

        try:
            if s[end] in _ws:
                end += 1
                if s[end] in _ws:
                    end = _w(s, end + 1).end()
        except IndexError:
            pass

        value, end = scan_once(s, end)
        pairs.append((key, value))

        try:
            nextchar = s[end]
            if nextchar in _ws:
                end = _w(s, end + 1).end()
                nextchar = s[end]
        except IndexError:
            nextchar = ''
        end += 1

        if nextchar == '}':
            break
        elif nextchar != ',':
            raise JSONDecodeError("Expecting ',' delimiter or '}'", s, end - 1)

        try:
            nextchar = s[end]
            if nextchar in _ws:
                end += 1
                nextchar = s[end]
                if nextchar in _ws:
                    end = _w(s, end + 1).end()
                    nextchar = s[end]
        except IndexError:
            nextchar = ''

        # Changed Code Here.
        if not literal_check:
            end += 1
            # Changed Code Here.
            if is_not_quote(nextchar):
                raise JSONDecodeError(
                    "Expecting property name enclosed in double quotes",
                    s, end - 1)

    if object_pairs_hook is not None:
        result = object_pairs_hook(pairs)
        return result, end
    pairs = dict(pairs)
    if object_hook is not None:
        pairs = object_hook(pairs)
    return pairs, end

def JSONArray(state, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
    (s, end) = state
    values = []
    nextchar = s[end:end + 1]
    if nextchar in _ws:
        end = _w(s, end + 1).end()
        nextchar = s[end:end + 1]
    # Look-ahead for trivial empty array
    if nextchar == ']':
        return values, end + 1
    elif nextchar == '':
        raise JSONDecodeError("Expecting value or ']'", s, end)
    _append = values.append
    while True:
        value, end = scan_once(s, end)
        _append(value)
        nextchar = s[end:end + 1]
        if nextchar in _ws:
            end = _w(s, end + 1).end()
            nextchar = s[end:end + 1]
        end += 1
        if nextchar == ']':
            break
        elif nextchar != ',':
            raise JSONDecodeError("Expecting ',' delimiter or ']'", s, end - 1)

        try:
            if s[end] in _ws:
                end += 1
                if s[end] in _ws:
                    end = _w(s, end + 1).end()
        except IndexError:
            pass

    return values, end

class JSONDecoder(object):
    """Simple JSON <http://json.org> decoder

    Performs the following translations in decoding by default:

    +---------------+-------------------+
    | JSON          | Python            |
    +===============+===================+
    | object        | dict              |
    +---------------+-------------------+
    | array         | list              |
    +---------------+-------------------+
    | string        | str, unicode      |
    +---------------+-------------------+
    | number (int)  | int, long         |
    +---------------+-------------------+
    | number (real) | float             |
    +---------------+-------------------+
    | true          | True              |
    +---------------+-------------------+
    | false         | False             |
    +---------------+-------------------+
    | null          | None              |
    +---------------+-------------------+

    It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
    their corresponding ``float`` values, which is outside the JSON spec.

    """

    def __init__(self, encoding=None, object_hook=None, parse_float=None,
            parse_int=None, parse_constant=None, strict=True,
            object_pairs_hook=None):
        """
        *encoding* determines the encoding used to interpret any
        :class:`str` objects decoded by this instance (``'utf-8'`` by
        default).  It has no effect when decoding :class:`unicode` objects.

        Note that currently only encodings that are a superset of ASCII work,
        strings of other encodings should be passed in as :class:`unicode`.

        *object_hook*, if specified, will be called with the result of every
        JSON object decoded and its return value will be used in place of the
        given :class:`dict`.  This can be used to provide custom
        deserializations (e.g. to support JSON-RPC class hinting).

        *object_pairs_hook* is an optional function that will be called with
        the result of any object literal decode with an ordered list of pairs.
        The return value of *object_pairs_hook* will be used instead of the
        :class:`dict`.  This feature can be used to implement custom decoders
        that rely on the order that the key and value pairs are decoded (for
        example, :func:`collections.OrderedDict` will remember the order of
        insertion). If *object_hook* is also defined, the *object_pairs_hook*
        takes priority.

        *parse_float*, if specified, will be called with the string of every
        JSON float to be decoded.  By default, this is equivalent to
        ``float(num_str)``. This can be used to use another datatype or parser
        for JSON floats (e.g. :class:`decimal.Decimal`).

        *parse_int*, if specified, will be called with the string of every
        JSON int to be decoded.  By default, this is equivalent to
        ``int(num_str)``.  This can be used to use another datatype or parser
        for JSON integers (e.g. :class:`float`).

        *parse_constant*, if specified, will be called with one of the
        following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``.  This
        can be used to raise an exception if invalid JSON numbers are
        encountered.

        *strict* controls the parser's behavior when it encounters an
        invalid control character in a string. The default setting of
        ``True`` means that unescaped control characters are parse errors, if
        ``False`` then control characters will be allowed in strings.

        """
        if encoding is None:
            encoding = DEFAULT_ENCODING
        self.encoding = encoding
        self.object_hook = object_hook
        self.object_pairs_hook = object_pairs_hook
        self.parse_float = parse_float or float
        self.parse_int = parse_int or int
        self.parse_constant = parse_constant or _CONSTANTS.__getitem__
        self.strict = strict
        self.parse_object = JSONObject
        self.parse_array = JSONArray
        self.parse_string = scanstring
        self.parse_single_quoted_string = parse_single_quoted_string  # Changed Code Here.
        self.memo = {}
        self.scan_once = make_scanner(self)

    def decode(self, s, _w=WHITESPACE.match, _PY3=PY3):
        """Return the Python representation of ``s`` (a ``str`` or ``unicode``
        instance containing a JSON document)

        """
        if _PY3 and isinstance(s, binary_type):
            s = s.decode(self.encoding)
        obj, end = self.raw_decode(s)
        end = _w(s, end).end()
        if end != len(s):
            raise JSONDecodeError("Extra data", s, end, len(s))
        return obj

    def raw_decode(self, s, idx=0, _w=WHITESPACE.match, _PY3=PY3):
        """Decode a JSON document from ``s`` (a ``str`` or ``unicode``
        beginning with a JSON document) and return a 2-tuple of the Python
        representation and the index in ``s`` where the document ended.
        Optionally, ``idx`` can be used to specify an offset in ``s`` where
        the JSON document begins.

        This can be used to decode a JSON document from a string that may
        have extraneous data at the end.

        """
        if idx < 0:
            # Ensure that raw_decode bails on negative indexes, the regex
            # would otherwise mask this behavior. #98
            raise JSONDecodeError('Expecting value', s, idx)
        if _PY3 and not isinstance(s, text_type):
            raise TypeError("Input string must be text, not bytes")
        # strip UTF-8 bom
        if len(s) > idx:
            ord0 = ord(s[idx])
            if ord0 == 0xfeff:
                idx += 1
            elif ord0 == 0xef and s[idx:idx + 3] == '\xef\xbb\xbf':
                idx += 3
        return self.scan_once(s, idx=_w(s, idx).end())

2) simplejson has scanner.py python file . 2) simplejson 有scanner.py python 文件。 Replace that file code with this code用此代码替换该文件代码

 """JSON token scanner
"""
import re
from .errors import JSONDecodeError
def _import_c_make_scanner():
    try:
        from ._speedups import make_scanner
        return make_scanner
    except ImportError:
        return None
c_make_scanner = _import_c_make_scanner()

__all__ = ['make_scanner', 'JSONDecodeError']

NUMBER_RE = re.compile(
    r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
    (re.VERBOSE | re.MULTILINE | re.DOTALL))


def py_make_scanner(context):
    parse_object = context.parse_object
    parse_array = context.parse_array
    parse_string = context.parse_string
    parse_single_quoted_string = context.parse_single_quoted_string  # Changed Code Here.
    match_number = NUMBER_RE.match
    encoding = context.encoding
    strict = context.strict
    parse_float = context.parse_float
    parse_int = context.parse_int
    parse_constant = context.parse_constant
    object_hook = context.object_hook
    object_pairs_hook = context.object_pairs_hook
    memo = context.memo

    def _scan_once(string, idx):
        errmsg = 'Expecting value'
        try:
            nextchar = string[idx]
        except IndexError:
            raise JSONDecodeError(errmsg, string, idx)

        if nextchar == '"':
            return parse_string(string, idx + 1, encoding, strict)
        elif nextchar == "'":
            return parse_single_quoted_string(string, idx + 1, encoding, strict)  # Changed Code Here.
        elif nextchar == '{':
            return parse_object((string, idx + 1), encoding, strict,
                _scan_once, object_hook, object_pairs_hook, memo)
        elif nextchar == '[':
            return parse_array((string, idx + 1), _scan_once)
        elif nextchar == 'n' and string[idx:idx + 4] == 'null':
            return None, idx + 4
        elif nextchar == 't' and string[idx:idx + 4] == 'true':
            return True, idx + 4
        elif nextchar == 'f' and string[idx:idx + 5] == 'false':
            return False, idx + 5

        m = match_number(string, idx)
        if m is not None:
            integer, frac, exp = m.groups()
            if frac or exp:
                res = parse_float(integer + (frac or '') + (exp or ''))
            else:
                res = parse_int(integer)
            return res, m.end()
        elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
            return parse_constant('NaN'), idx + 3
        elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
            return parse_constant('Infinity'), idx + 8
        elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
            return parse_constant('-Infinity'), idx + 9
        else:
            raise JSONDecodeError(errmsg, string, idx)

    def scan_once(string, idx):
        if idx < 0:
            # Ensure the same behavior as the C speedup, otherwise
            # this would work for *some* negative string indices due
            # to the behavior of __getitem__ for strings. #98
            raise JSONDecodeError('Expecting value', string, idx)
        try:
            return _scan_once(string, idx)
        finally:
            memo.clear()

    return scan_once

make_scanner = c_make_scanner or py_make_scanner

Now you are all set.现在你已经准备好了。 Simplejson is the fastest and stable Library i have used. Simplejson 是我用过的最快、最稳定的库。

I have changed few lines of code in simplejson and now this awesome library works for我在 simplejson 中更改了几行代码,现在这个很棒的库适用于

  • Unquoted json keys and Single Quoted json strings and keys未加引号的 json 键和单引号的 json 字符串和键

I have changed python code only.我只更改了 python 代码。 So if you use C extension for speed up boost then this code will not work.因此,如果您使用 C 扩展来加速提升,那么此代码将不起作用。

Wherever I made changes I have added Comment # Changed Code Here无论我在哪里进行更改,我都会在此处添加注释 # 更改代码

I mistakenly answered previously as guest user and now I can not login to edit that answer so i posted in new thread.我之前错误地以访客用户身份回答,现在我无法登录编辑该答案,所以我在新线程中发布。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM