• python(四):面型对象--类的特殊方法


    一、跟实例创建和执行有关的

      __new__、__init__、__call__.

      类加括号调用了__init__方法来创建一个实例对象。这一过程分成了两步: 类调用__new__来创建实例对象,__new__调用__init__来初始化实例对象。

    class A:
        count = 0
        def __init__(self):
            print("__init__ has called. 2")
           
        def __new__(cls):
            print("__new__ has called. 1")
            return object.__new__(cls)
        def __call__(cls):
            print("__call__ has called. 3")
    a = A()
    a()
    
    """
    __new__ has called. 1
    __init__ has called. 2
    __call__ has called. 3
    """

      类的__new__()方法很少通过用户代码定义。如果定义了它,它通常是用原型__new__(cls, *args, **kwargs)编写的。其中args和kwargs与传递给__init__()的参数相同。__new__()始终是一个类方法,接受类对象作为第一个参数。尽管__new__()会创建一个实例,但它不会自动地调用__init__()。实例对象加括号会调用__call__方法,一般作为程序的入口,并且可以在这一步修改实例属性或调用实例方法。

    class Obj(object):
        def __init__(self, name, price):
            self.name = name
            self.__price = price
    
        def __say(self):
            print("{}, {}.".format(self.name, self.__price))
    
        def __call__(self, discount):
            self.__price = discount * self.__price
            self.__say()
    
    
    apple = Obj("apple", 10.0)
    apple(0.8)

    二、跟实例属性有关的

      __getattribute__,  __getattr__,  __setattr__, __delattr__、__getitem__、__setitem__、__delitem__、__missing__

      在__init__时,会调用__setattr__初始化实例属性,在__dict__添加键值对。

    class Sample:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __setattr__(self, key, value):
            print("__setattr__ has called.")
            try:
                self.__dict__[key] = value
            except:
                self.__dict__ = {key: value}
    
    sam = Sample("Li", 24)
    
    # __setattr__ has called.
    # __setattr__ has called.

      对实例属性的访问时,

      1.首先执行__getattribute__方法(这又是一个特殊方法),去调用object基类的__getattribute__来查询__dict__里的键值对。如果存在则直接返回,相当于执行了member.__dict__.get(obj),如果不存在则调用__getattr__方法。

      2.__getattr__方法会在实例内存空间中尽可能的搜索该属性值,如果搜索到则直接返回,搜索不到会抛出AttributeError。

      注意下面两段代码的区别:__getattribute__的return只是一个硬编码的字符串而不是属性值,__getattr__的return则是实例的属性值。因此,如果有需求,一般会在__getattr__里写需求代码。

    class MemberCounter:
        def __init__(self, name, age):
            self.name = name
            self.age = age
        
        def __getattribute__(self, obj):
            print("__getattribute__ is called.")
            return object.__getattribute__(self, obj)
        
        def __getattr__(self, obj):
            print("__getattr__ is called.")
            return "{} Not assgined.".format(obj)
    member = MemberCounter("An", 24)
    
    print(member.name)
    print("
    ------------------
    ")
    print(member.gender)
    
    """
    __getattribute__ is called.
    An
    
    ------------------
    
    __getattribute__ is called.
    __getattr__ is called.
    gender Not assgined.
    """
    class MemberCounter:
        def __init__(self, name, age):
            self.name = name
            self.age = age
        def __getattribute__(self, obj):
            if obj == "gender":
                return "male"
            else:
                return object.__getattribute__(self, obj)
    
    member = MemberCounter("An", 24)
    print(member.gender)
    member.gender = "female"
    print(member.gender)
    print(member.__dict__)
     
    """
    male
    male
    {'name': 'An', 'age': 24, 'gender': 'female'}
    """
    __getattribute__
    class MemberCounter:
        def __init__(self, name, age):
            self.name = name
            self.age = age
        def __getattr__(self, obj):
            if obj == "gender":
                return "male"
            else:
                raise AttributeError
    
    member = MemberCounter("An", 24)
    print(member.gender)
    member.gender = "female"
    print(member.gender)
    print(member.__dict__)
    
    """
    male
    female
    {'name': 'An', 'age': 24, 'gender': 'female'}
    """
    __getattr__

      当访问一个属性的时候:

        解释器首先在实例的字典中搜索,
        若找不到则去创建这个实例的类的字典中搜索,
        若还找不到就到类的基类中搜索,
        如果还不找不到最后会尝试调用类的__getattr__方法来获取属性值(若类中定义了该方法的话).
        如果这个过程也失败,则引发AttributeError异常

      在使用member.name = "Wei"和del member.age时,实际上调用了__setattr__和__delattr__。这两个函数没有类似__getattribute__的使用规则。即无论何时给属性赋值,都会调用 __setattr__() 方法;无论何时删除一个属性,都将调用 __delattr__() 方法。

    class MemberCounter:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __setattr__(self, old, new):
            print("33[1;36m {} = {} 33[0m has been created.".format(old, new))
            object.__setattr__(self, old, new)
    
        def __delattr__(self, key):
            print("{} is deleted.".format(key))
            del self.__dict__[key]
            
        def __getattr__(self, item):
            return self.__dict__[item]  # object.__getattr__(self, item)
    
    member = MemberCounter("An", 24)
    print(member.__dict__)
    del member.age
    print(member.__dict__)
    
    """
    name = An  has been created.
    age = 24  has been created.
    {'name': 'An', 'age': 24}
    age is deleted.
    {'name': 'An'}
    """
    class A:
        def __init__(self, *args):
            self.name, self.age = args
        
        def __getitem__(self, key):
            print("__getitem__ has called.")
            self.__dict__.get(key, ValueError)
        
        def __setitem__(self, key, value):
            print("__setitem__ has called.")
            self.__dict__.update({key: value})
        
        def __delitem__(self, key):
            print("__delitem__ has called.")
            del self.__dict__[key]
    
    a = A("Li", 27)
    print(a.__dict__)
    a["name"]
    print(a.__dict__)
    a["name"] = "Alex"
    print(a.__dict__)
    del a["age"]
    print(a.__dict__)
    
    """
    {'name': 'Li', 'age': 27}
    __getitem__ has called.
    {'name': 'Li', 'age': 27}
    __setitem__ has called.
    {'name': 'Alex', 'age': 27}
    __delitem__ has called.
    {'name': 'Alex'}
    """
    序号目的所编写代码Python 实际调用
    1 获取一个计算属性(无条件的) x.my_property x.__getattribute__('my_property')
    2 获取一个计算属性(后备) x.my_property x.__getattr__('my_property')
    3 设置某属性 x.my_property = value x.__setattr__('my_property', value)
    4 删除某属性 del x.my_property x.__delattr__('my_property')
    5 列出所有属性和方法 dir(x) x.__dir__()
    序号目的所编写代码Python 实际调用
    1 通过键来获取值 x[key] x.__getitem__(key)
    2 通过键来设置值 x[key] = value x.__setitem__(key, value)
    3 删除一个键值对 del x[key] x.__delitem__(key)
    4 为缺失键提供默认值 x[nonexistent_key] x.__missing__(nonexistent_key)

    三、实例属性的保存格式---__slots__和__dict__

      实例对象通常以字典的形式保存属性, 也即__dict__。这种格式可以被__slots__修改 。下面是从各个python书籍中摘下的关于__slots__描述。

     定义__slots__后,可以在实例上分配的属性名称将被限制为指定的名称,否则将引发AttributeError异常。
     这种限制可以阻止其他人向现有实例添加新属性,解决了用户将值分配给他们无法正确拼写的属性时出现的异常。
     在实际使用时,__slots__从未被当做一种安全的特性来实现。它实际上是对内存和执行速度的一种性能优化。  使用__slots__的类的实例不再使用字典来存储实例数据。相反,会使用基于数组的更加紧凑的数据结构。  在会创建大量对象的程序中,使用__slots__可以显著减少内存占用和执行时间。  __slots__与集成的配合使用需要一定的技巧。如果类继承自使用__slots__的基类,那么它也需要定义__slots__来存储自己的属性(即使它不会添加任何属性)。这样才能充分利用__slots__提供的优势。如果忘记了这一点,派生类的运行速度将更慢,占用的内存也比未在任何类上使用__slots__时多。  __slots__的使用也可以使代码不必要求实例具有底层__slots__属性。尽管这一点通常不适用于用户代码。但可以编写其他支持对象实用工具库和其他工具。依靠__dict__来调试、序列化对象并执行其他操作。  __slots__的存在不会对__getattribute__()、__getattr__()、和__setattr__()等方法的调用产生任何影响。因为这些方法应该在类中重新定义。但是,这些方法的默认行为将考虑到__slots__。
     此外,没用必要向__slots__添加方法或特性名称,因为它们存储在类中,而不是存储在每个实例中。
    class MemberCounter:
        __slots__ = ("name", "age")
        # 注意, ()指定存储格式为元组,"name"和"age"严格限制了实例化对象的参数,不可随意增加,但可以删除
        def __init__(self, name, age): # 不可再写*args, **kwargs
            self.name = name
            self.age = age
    member = MemberCounter("An", 24, )
    # member.__dict__ # 当__slots__被指定时,覆盖了__dict__
    
    print(member.__slots__)
    print(member.name, member.age)
    # member.gender = "female"  报错
    #('name', 'age')
    # An 24age"
     如何在定义了__slots__,来储存dict或者list等数据呢?以下面的例子为例:
    class Local(object):
        __slots__ = ('__dict__', '__list__')
    
        def __init__(self, name, age, gender):
            object.__setattr__(self, "__dict__", {})  # 注意,__dict__和__list__只是我随意写的,你可以通篇把__dict__改成dict,照样没有关系
            object.__setattr__(self, "__list__", [])  # 也就是说,__dict__只是一个属性链接,它从__slots__中链接到一个字典对象并提供相关操作
    
            self.name = name  # 假定你已经知道self.name = name 实际上式调用了__setattr__方法,也就是自动存到了self.__dict__里
            self.age = age
            self.gender = gender
    
        def __setattr__(self, key, value):
            try:
                self.__dict__[key] = value
            except KeyError:
                self.__dict__ = {key: value}
    
        def __getattr__(self, name):
            try:
                return self.__dict__[name]
            except KeyError:
                raise AttributeError(name)
    
        def __delattr__(self, name):
            try:
                del self.__dict__[name]
            except KeyError:
                raise AttributeError(name)
    
    
    local = Local("Li", 24, "famle")
    print(local.__slots__)
    print(local.__dict__)
    # 0('__dict__', '__list__')
    # {'name': 'Li', 'age': 24, 'gender': 'famle'

    四、跟运算有关的

      __eq__(==)、__ge__(>=)、__gt__(>)、__le__ (<=)、__lt__ (<)、__ne__ (!=)、__bool__

      参见: http://old.sebug.net/paper/books/dive-into-python3/special-method-names.html

    class A:
        def __init__(self, number):
            self.number = number
            
        def __gt__(self, obj):
            print("__gt__ has called.")
            return self.number > obj.number
        
        def __ge__(self, obj):
            print("__ge__ has called.")  # 既然执行了这个函数,你可以尽情的在return之前写想写的功能
            return self.number >= obj.number
    
    a = A(23)    # 当你写 23 > 45时,实际等同于 a = int(23), b = int(45); a > b
    b = A(45)    # 这里A就相当于int
    c = A(45)
    
    print(a > b)
    print(b >= c)
    
    """
    __gt__ has called.
    False
    __ge__ has called.
    True
    """
    序号目的所编写代码Python 实际调用
    1 加法 x + y x.__add__(y)
    2 减法 x - y x.__sub__(y)
    3 乘法 x * y x.__mul__(y)
    4 除法 x / y x.__truediv__(y)
    5 地板除 x // y x.__floordiv__(y)
    6 取模(取余) x % y x.__mod__(y)
    7 地板除 & 取模 divmod(x, y) x.__divmod__(y)
    8 乘幂 x ** y x.__pow__(y)
    9 左位移 x << y x.__lshift__(y)
    10 右位移 x >> y x.__rshift__(y)
    11 按位 and x & y x.__and__(y)
    12 按位 xor x ^ y x.__xor__(y)
    13 按位 or x | y x.__or__(y)
    此外,还有原地操作:
    序号目的所编写代码Python 实际调用
    1 原地加法 x += y x.__iadd__(y)
    2 原地减法 x -= y x.__isub__(y)
    3 原地乘法 x *= y x.__imul__(y)
    4 原地除法 x /= y x.__itruediv__(y)
    5 原地地板除法 x //= y x.__ifloordiv__(y)
    6 原地取模 x %= y x.__imod__(y)
    7 原地乘幂 x **= y x.__ipow__(y)
    8 原地左位移 x <<= y x.__ilshift__(y)
    9 原地右位移 x >>= y x.__irshift__(y)
    10 原地按位 and x &= y x.__iand__(y)
    11 原地按位 xor x ^= y x.__ixor__(y)
    12 原地按位 or x |= y x.__ior__(y)
    以及一些自身简单运算:
    序号目的所编写代码Python 实际调用
    1 负数 -x x.__neg__()
    2 正数 +x x.__pos__()
    3 绝对值 abs(x) x.__abs__()
    4 取反 ~x x.__invert__()
    5 复数 complex(x) x.__complex__()
    6 整数转换 int(x) x.__int__()
    7 浮点数 float(x) x.__float__()
    8 四舍五入至最近的整数 round(x) x.__round__()
    9 四舍五入至最近的 n 位小数 round(x, n) x.__round__(n)
    10 >= x 的最小整数 math.ceil(x) x.__ceil__()
    11 <= x的最大整数 math.floor(x) x.__floor__()
    12 x 朝向 0 取整 math.trunc(x) x.__trunc__()
    13 作为列表索引的数字 a_list[x] a_list[x.__index__()]
    
    

    五、跟字符串有关的

      __str__、__repr__、__format__、[__bytes__]

      这些都是跟字符串格式和字符串表示有关的特殊方法

    class Cls(object):
        def __init__(self):
            self.name = "alex"
            self.age = 27
            self.gender = "female"
        def __str__(self):
            print("__str__ has called.")
            return "Str: my name is {}, {}.".format(self.name, self.age)
        def __format__(self, special):
            print("__format__ has called.")
            if special == "":
                return self.__str__()   # 也可以写成str(self)
            for _, v in self.__dict__.items():  # 实际上是{"name": "alex", "age": 27}
                special = special.replace("%s", str(v), 1)
            return special

      观察下面二段代码打印的结果:

    cls = Cls()
    str1 = "{:%s, %s, %s}".format(cls)
    print("-------------")
    print(str1)
    
    
    """
    __format__ has called.
    -------------
    alex, 27, female
    """
    cls = Cls()
    str2 = format(cls, "my name is %s, %s, %s.")
    print("-------------")
    print(str2)
    
    """
    __format__ has called.
    -------------
    my name is alex, 27, female.
    """

      以上两端代码本质都是一样的,即format和一个字符串匹配,也就是__format__函数传递了special参数(字符串)。

      当format函数没有和字符串匹配,也就是__format__函数没有传入参数special时,会调用__str__函数。

    cls = Cls()
    
    str3 = format(cls)
    print("-------------")
    print(str3)
    
    """
    __format__ has called.
    __str__ has called.
    -------------
    Str: my name is alex, 27.
    """

      str(cls)调用了__str__方法。str(cls)调用__str__方法, string.format(cls)和format(cls)调用__format__方法。

    cls = Cls()
    print(str(cls))
    
    """
    __str__ has called.
    Str: my name is alex, 27.
    """

      再来看__str__和__repr__的区别和联系。当__str__存在时,print(obj)和str(obj)实际调用了__str__方法;当__str__不存在时,就会调用__repr__。但是当定义了__repr__时,obj本身会返回一个"合法"的字符串表达式,而不再返回一个描述符<'__main__.A object at XXX>,当然这个描述符也可以被__str__修改,只是会被__repr__覆盖。

    class A:
        def __str__(self):
            print("__str__ has called.")
            return self.__class__.__name__ + "__str__"
    
    a = A()
    
    a   # <__main__.A at 0x110a764e0>
    print(a)  
    # __str__ has called.
    # A__str__
    
    str(a)
    # __str__ has called.
    # A__str__
    class A:
            def __repr__(self):
            print("__repr__ has called.")
            return self.__class__.__name__ + "__repr__"
    a = A()
    
    a
    #__repr__ has called.
    #A__repr__
    
    print(a)
    #__repr__ has called.
    #A__repr__
    
    str(a)
    #__repr__ has called.
    #A__repr__
    class A:
        def __str__(self):
            print("__str__ has called.")
            return self.__class__.__name__ + "__str__"
        
        def __repr__(self):
            print("__repr__ has called.")
            return self.__class__.__name__ + "__repr__"
    a = A()
    
    a
    
    #__repr__ has called.
    #A__repr__
    
    print(a)
    #__str__ has called.
    #A__str__
    
    str(a)
    #__str__ has called.
    #'A__str__'
    序号目的所编写代码Python 实际调用
    1 初始化一个实例 x = MyClass() x.__init__()
    2 字符串的“官方”表现形式 repr(x) x.__repr__()
    3 字符串的“非正式”值 str(x) x.__str__()
    4 字节数组的“非正式”值 bytes(x) x.__bytes__()
    5 格式化字符串的值 format(x, format_spec) x.__format__(format_spec)

      通常__str__()__repr__()代码都是一样的,所以会这么写:

    class Student(object):
        def __init__(self, name):
            self.name = name
        def __str__(self):
            return 'Student object (name=%s)' % self.name
        __repr__ = __str__

    六、跟迭代器有关的

    序号目的所编写代码Python 实际调用
    1 遍历某个序列 iter(seq) seq.__iter__()
    2 从迭代器中获取下一个值 next(seq) seq.__next__()
    3 按逆序创建一个迭代器 reversed(seq) seq.__reversed__()

      iter内置函数对应的特殊方法是__iter_,它把一个序列变成迭代器。

    class Iter:
        def __init__(self, lis):
            self.iterate = lis
        def __iter__(self):
            for v in self.iterate:
                print("__iter__ has called.")
                yield v
                
    a = Iter(list(range(5)))
    b = iter(a)
    
    print(next(b))
    print(next(b))
    
    """
    __iter__ has called.
    0
    __iter__ has called.
    1
    
    """

      next内置函数对应的特殊方法是__iter_,对迭代器进行迭代(惰性行)。

    class Iter:
        count = 0
        def __init__(self, lis):
            self.iterate = lis  
        def __next__(self):
            print("__next__ has called.")
            try:
                value = self.iterate[Iter.count]
                Iter.count += 1
            except StopIteration:
                exit()
            return value
        
    a = Iter(list(range(5)))
    
    print(next(a))
    print(next(a))
    
    """
    __next__ has called.
    0
    __next__ has called.
    1
    
    """

      至此我们已了解了绝大部分特殊方法,在python中,类对象的顶层是type类。下面是object类和type类的源码。它们的源码是底层实现的。

    class object:
        """ The most base type """
        def __delattr__(self, *args, **kwargs): # real signature unknown
            """ Implement delattr(self, name). """
            pass
    
        def __dir__(self): # real signature unknown; restored from __doc__
            """
            __dir__() -> list
            default dir() implementation
            """
            return []
    
        def __eq__(self, *args, **kwargs): # real signature unknown
            """ Return self==value. """
            pass
    
        def __format__(self, *args, **kwargs): # real signature unknown
            """ default object formatter """
            pass
    
        def __getattribute__(self, *args, **kwargs): # real signature unknown
            """ Return getattr(self, name). """
            pass
    
        def __ge__(self, *args, **kwargs): # real signature unknown
            """ Return self>=value. """
            pass
    
        def __gt__(self, *args, **kwargs): # real signature unknown
            """ Return self>value. """
            pass
    
        def __hash__(self, *args, **kwargs): # real signature unknown
            """ Return hash(self). """
            pass
    
        def __init_subclass__(self, *args, **kwargs): # real signature unknown
            """
            This method is called when a class is subclassed.
            
            The default implementation does nothing. It may be
            overridden to extend subclasses.
            """
            pass
    
        def __init__(self): # known special case of object.__init__
            """ Initialize self.  See help(type(self)) for accurate signature. """
            pass
    
        def __le__(self, *args, **kwargs): # real signature unknown
            """ Return self<=value. """
            pass
    
        def __lt__(self, *args, **kwargs): # real signature unknown
            """ Return self<value. """
            pass
    
        @staticmethod # known case of __new__
        def __new__(cls, *more): # known special case of object.__new__
            """ Create and return a new object.  See help(type) for accurate signature. """
            pass
    
        def __ne__(self, *args, **kwargs): # real signature unknown
            """ Return self!=value. """
            pass
    
        def __reduce_ex__(self, *args, **kwargs): # real signature unknown
            """ helper for pickle """
            pass
    
        def __reduce__(self, *args, **kwargs): # real signature unknown
            """ helper for pickle """
            pass
    
        def __repr__(self, *args, **kwargs): # real signature unknown
            """ Return repr(self). """
            pass
    
        def __setattr__(self, *args, **kwargs): # real signature unknown
            """ Implement setattr(self, name, value). """
            pass
    
        def __sizeof__(self): # real signature unknown; restored from __doc__
            """
            __sizeof__() -> int
            size of object in memory, in bytes
            """
            return 0
    
        def __str__(self, *args, **kwargs): # real signature unknown
            """ Return str(self). """
            pass
    
        @classmethod # known case
        def __subclasshook__(cls, subclass): # known special case of object.__subclasshook__
            """
            Abstract classes can override this to customize issubclass().
            
            This is invoked early on by abc.ABCMeta.__subclasscheck__().
            It should return True, False or NotImplemented.  If it returns
            NotImplemented, the normal algorithm is used.  Otherwise, it
            overrides the normal algorithm (and the outcome is cached).
            """
            pass
    
        __class__ = None # (!) forward: type, real value is ''
        __dict__ = {}
        __doc__ = ''
        __module__ = ''
    object
    class type(object):
        """
        type(object_or_name, bases, dict)
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        """
        def mro(self): # real signature unknown; restored from __doc__
            """
            mro() -> list
            return a type's method resolution order
            """
            return []
    
        def __call__(self, *args, **kwargs): # real signature unknown
            """ Call self as a function. """
            pass
    
        def __delattr__(self, *args, **kwargs): # real signature unknown
            """ Implement delattr(self, name). """
            pass
    
        def __dir__(self): # real signature unknown; restored from __doc__
            """
            __dir__() -> list
            specialized __dir__ implementation for types
            """
            return []
    
        def __getattribute__(self, *args, **kwargs): # real signature unknown
            """ Return getattr(self, name). """
            pass
    
        def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
            """
            type(object_or_name, bases, dict)
            type(object) -> the object's type
            type(name, bases, dict) -> a new type
            # (copied from class doc)
            """
            pass
    
        def __instancecheck__(self): # real signature unknown; restored from __doc__
            """
            __instancecheck__() -> bool
            check if an object is an instance
            """
            return False
    
        @staticmethod # known case of __new__
        def __new__(*args, **kwargs): # real signature unknown
            """ Create and return a new object.  See help(type) for accurate signature. """
            pass
    
        def __prepare__(self): # real signature unknown; restored from __doc__
            """
            __prepare__() -> dict
            used to create the namespace for the class statement
            """
            return {}
    
        def __repr__(self, *args, **kwargs): # real signature unknown
            """ Return repr(self). """
            pass
    
        def __setattr__(self, *args, **kwargs): # real signature unknown
            """ Implement setattr(self, name, value). """
            pass
    
        def __sizeof__(self): # real signature unknown; restored from __doc__
            """
            __sizeof__() -> int
            return memory consumption of the type object
            """
            return 0
    
        def __subclasscheck__(self): # real signature unknown; restored from __doc__
            """
            __subclasscheck__() -> bool
            check if a class is a subclass
            """
            return False
    
        def __subclasses__(self): # real signature unknown; restored from __doc__
            """ __subclasses__() -> list of immediate subclasses """
            return []
    
        __abstractmethods__ = property(lambda self: object(), lambda self, v: None, lambda self: None)  # default
    
    
        __bases__ = (
            object,
        )
        __base__ = object
        __basicsize__ = 864
        __dictoffset__ = 264
        __dict__ = None # (!) real value is ''
        __flags__ = 2148291584
        __itemsize__ = 40
        __mro__ = (
            None, # (!) forward: type, real value is ''
            object,
        )
        __name__ = 'type'
        __qualname__ = 'type'
        __text_signature__ = None
        __weakrefoffset__ = 368
    type
  • 相关阅读:
    开放API接口安全处理!
    ant笔记
    并发调试
    IDEA 设置(中文乱码、svn、热部署、ideolog 、Jrebel )
    win10家庭版升级专业版
    org.json package
    'root'@'localhost'不能登录问题
    javascript之DOM选择符
    javascript之DOM(四其他类型)
    javascript之DOM(三Element类型)
  • 原文地址:https://www.cnblogs.com/kuaizifeng/p/9075797.html
Copyright © 2020-2023  润新知