• Python(16):Python面向对象进阶


    一、对象的继承

    Python中支持一个类同时继承多个父类

    class Parent1:
        pass
    
    
    class Parent2:
        pass
    
    
    class Sub1(Parent1, Parent2):
        pass

    使用__bases__方法可以获取对象继承的类

    print(Sub1.__bases__)
    # (<class '__main__.Parent1'>, <class '__main__.Parent2'>)

    在Python3中如果一个类没有继承任何类,则默认继承object类。

    print(Parent1.__bases__)
    #(<class 'object'>,)

    1、类的构造函数继承__init__():

    1. 子类需要自动调用父类的方法:子类不重写__init__()方法,实例化子类后,会自动调用父类的__init__()的方法。
    2. 子类不需要自动调用父类的方法:子类重写__init__()方法,实例化子类后,将不会自动调用父类的__init__()的方法。
    3. 子类重写__init__()方法又需要调用父类的方法:需要使用super关键词。

    2、继承关系中,对象查找属性的顺序

    对象自己——>对象的类——>父类——>父类。。。

    class OldboyPeople:
        """由于学生和老师都是人,因此人都有姓名、年龄、性别"""
        school = 'oldboy'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    
    class OldboyStudent(OldboyPeople):
       def choose_course(self):
            print('%s is choosing course' % self.name)
    
    
    class OldboyTeacher(OldboyPeople):
        def score(self, stu_obj, num):
            print('%s is scoring' % self.name)
            stu_obj.score = num
    
    
    stu1 = OldboyStudent('tank', 18, 'male')
    tea1 = OldboyTeacher('nick', 18, 'male')
    
    
    print(stu1.school)
    # oldboy
    
    print(tea1.school)
    # oldboy
    
    print(stu1.__dict__)
    # {'name': 'tank', 'age': 18, 'gender': 'male'}
    
    tea1.score(stu1, 99)
    # nick is scoring
    
    print(stu1.__dict__)
    # {'name': 'tank', 'age': 18, 'gender': 'male', 'score': 99}

    二、类的派生

    子类中新定义的属性的这个过程叫做派生,子类在使用派生的属性时始终以自己的为准

    1、派生方法一(类调用)

    指名道姓访问某一个类的函数:该方式与继承无关

    class OldboyPeople:
        """由于学生和老师都是人,因此人都有姓名、年龄、性别"""
        school = 'oldboy'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    
    class OldboyStudent(OldboyPeople):
        """由于学生类没有独自的__init__()方法,因此不需要声明继承父类的__init__()方法,会自动继承"""
    
        def choose_course(self):
            print('%s is choosing course' % self.name)
    
    
    class OldboyTeacher(OldboyPeople):
        """由于老师类有独自的__init__()方法,因此需要声明继承父类的__init__()"""
    
        def __init__(self, name, age, gender, level):
            OldboyPeople.__init__(self, name, age, gender)
            self.level = level  # 派生
    
        def score(self, stu_obj, num):
            print('%s is scoring' % self.name)
            stu_obj.score = num
    
    
    stu1 = OldboyStudent('tank', 18, 'male')
    tea1 = OldboyTeacher('nick', 18, 'male', 10)
    
    
    print(stu1.__dict__)
    # {'name': 'tank', 'age': 18, 'gender': 'male'}
    
    print(tea1.__dict__)
    # {'name': 'nick', 'age': 18, 'gender': 'male', 'level': 10}

    2、派生方法二(super)

    • 严格以继承属性查找关系
    • super()会得到一个特殊的对象,该对象就是专门用来访问父类中的属性的(按照继承的关系)
    • super().__init__(不用为self传值)
    • super的完整用法是super(自己的类名,self),在python2中需要写完整,而python3中可以简写为super()。
    class OldboyPeople:
        school = 'oldboy'
    
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
        def choose_course(self):
            print('%s is choosing course' % self.name)
    
    
    class OldboyStudent(OldboyPeople):
        def __init__(self, name, age, sex, stu_id):
            # OldboyPeople.__init__(self,name,age,sex)
            # super(OldboyStudent, self).__init__(name, age, sex)
            super().__init__(name, age, sex)
            self.stu_id = stu_id
    
        def choose_course(self):
            print('%s is choosing course' % self.name)
    
    
    stu1 = OldboyStudent('tank', 19, 'male', 1)
    super(OldboyPeople,stu1).choose_course() #用子类对象调用父类已被覆盖的方法
    print(stu1.__dict__)
    # {'name': 'tank', 'age': 19, 'sex': 'male', 'stu_id': 1}

    三、类的组合

    类对象可以引用/当做参数传入/当做返回值/当做容器元素,类似于函数对象。

    • 组合是用来解决类与类之间代码冗余的问题

    组合可以理解成多个人去造一个机器人,有的人造头、有的人造脚、有的人造手、有的人造躯干,大家都完工后,造躯干的人把头、脚、手拼接到自己的躯干上,因此一个机器人便造出来了

    class Course:
        def __init__(self, name, period, price):
            self.name = name
            self.period = period
            self.price = price
    
    
    class OldboyPeople:
        school = 'oldboy'
    
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
    
    class OldboyStudent(OldboyPeople):
        def __init__(self, name, age, sex, stu_id):
            OldboyPeople.__init__(self, name, age, sex)
            self.stu_id = stu_id
    
        def choose_course(self):
            print('%s is choosing course' % self.name)
    
    
    class OldboyTeacher(OldboyPeople):
        def __init__(self, name, age, sex, level):
            OldboyPeople.__init__(self, name, age, sex)
            self.level = level
    
        def score(self, stu, num):
            stu.score = num
            print('老师[%s]为学生[%s]打分[%s]' % (self.name, stu.name, num))
    
    
    # 创造课程
    python = Course('python全栈开发', '5mons', 3000)
    linux = Course('linux运维', '5mons', 800)
    
    
    # 创造学生与老师
    stu1 = OldboyStudent('tank', 19, 'male', 1)
    tea1 = OldboyTeacher('nick', 18, 'male', 10)
    
    # 组合
    # 将学生、老师与课程对象关联/组合
    stu1.course = python
    tea1.course = linux
    
    

    四、多父类继承问题

    Python同样有限的支持多继承形式。多继承的类定义形如下例:

    class DerivedClassName(Base1, Base2, Base3):
        <statement-1>
        .
        .
        .
        <statement-N>

    需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 。即方法在子类中未找到时,从左到右查找父类中是否包含方法。

    1、新式类(MRO)列表

    • 继承了object的类以及该类的子类,都是新式类
    • Python3中所有的类都是新式类
    • 广度优先, 老祖宗最后找。
    class G(object):
        # def test(self):
        #     print('from G')
        pass
    
    
    class E(G):
        # def test(self):
        #     print('from E')
        pass
    
    
    class B(E):
        # def test(self):
        #     print('from B')
        pass
    
    
    class F(G):
        # def test(self):
        #     print('from F')
        pass
    
    
    class C(F):
        # def test(self):
        #     print('from C')
        pass
    
    
    class D(G):
        # def test(self):
        #     print('from D')
        pass
    
    
    class A(B, C, D):
        def test(self):
            print('from A')
    
    
    obj = A()
    obj.test()  # A->B->E-C-F-D->G-object
    # from A

    92-菱形继承问题-新式类.png?x-oss-process=style/watermark

    python计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,来实现继承的。

    为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。

    print(A.mro())  # A.__mro__
    # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
    
    for i in A.mro():
        print(i)
    
    # <class '__main__.A'>
    # <class '__main__.B'>
    # <class '__main__.E'>
    # <class '__main__.C'>
    # <class '__main__.F'>
    # <class '__main__.D'>
    # <class '__main__.G'>
    # <class 'object'>

    2、super()方法详解

    super() 函数是用于调用父类(超类)的一个方法。

    super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。

    下面的例子可以看到:

    • 每个类开始调用是根据MRO顺序进行开始,然后逐个进行结束的。
    • 由于因为需要继承不同的父类,参数不一定,所有的父类都应该加上不定参数*args , **kwargs ,不然参数不对应是会报错的。
    # 胖子老板的父类
    class FatFather(object):
        def __init__(self, name, *args, **kwargs):
            print()
            print("=============== 开始调用 FatFather  ========================")
            print('FatFather的init开始被调用')
            self.name = name
            print('调用FatFather类的name是%s' % self.name)
            print('FatFather的init调用结束')
            print()
            print("=============== 结束调用 FatFather  ========================")
    
    
    # 胖子老板类 继承 FatFather 类
    class FatBoss(FatFather):
        def __init__(self, name, hobby, *args, **kwargs):
            print()
            print("=============== 开始调用 FatBoss  ========================")
            print('胖子老板的类被调用啦!')
            # super().__init__(name)
            # 因为多继承传递的参数不一致,所以使用不定参数
            super().__init__(name, *args, **kwargs)
            print("%s 的爱好是 %s" % (name, hobby))
            print()
            print("=============== 结束调用 FatBoss  ========================")
    
    
    # 胖子老板的老婆类 继承 FatFather类
    class FatBossWife(FatFather):
        def __init__(self, name, housework, *args, **kwargs):
            print()
            print("=============== 开始调用 FatBossWife  ========================")
            print('胖子老板的老婆类被调用啦!要学会干家务')
            # super().__init__(name)
            # 因为多继承传递的参数不一致,所以使用不定参数
            super().__init__(name, *args, **kwargs)
            print("%s 需要干的家务是 %s" % (name, housework))
            print()
            print("=============== 结束调用 FatBossWife  ========================")
    
    
    # 胖子老板的女儿类 继承 FatBoss FatBossWife类
    class FatBossGril(FatBoss, FatBossWife):
        def __init__(self, name, a, b):
            print('胖子老板的女儿类被调用啦!要学会干家务,还要会帮胖子老板斗地主')
            super().__init__(name, a, b)
    
    
    def main():
        print("打印FatBossGril类的MRO")
        print(FatBossGril.__mro__)
        # (<class '__main__.FatBossGril'>, <class '__main__.FatBoss'>, <class '__main__.FatBossWife'>, <class '__main__.FatFather'>, <class 'object'>)
        print("=========== 下面按照 MRO 顺序执行super方法 =============")
        gril = FatBossGril("胖子老板", "打斗地主", "拖地")
    
    
    if __name__ == "__main__":
        main()
    
    # =========== 下面按照 MRO 顺序执行super方法 =============
    # 胖子老板的女儿类被调用啦!要学会干家务,还要会帮胖子老板斗地主
    # 
    # =============== 开始调用 FatBoss  ========================
    # 胖子老板的类被调用啦!
    #
    # =============== 开始调用 FatBossWife  ========================
    # 胖子老板的老婆类被调用啦!要学会干家务
    #
    # =============== 开始调用 FatFather  ========================
    # FatFather的init开始被调用
    # 调用FatFather类的name是胖子老板
    # FatFather的init调用结束
    #
    # =============== 结束调用 FatFather  ========================
    # 胖子老板 需要干的家务是 拖地
    #
    # =============== 结束调用 FatBossWife  ========================
    # 胖子老板 的爱好是 打斗地主
    #
    # =============== 结束调用 FatBoss  ========================

    五、抽象类

    多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)

    import abc
    
    
    class Animal(metaclass=abc.ABCMeta):  # 同一类事物:动物
        @abc.abstractmethod  # 上述代码子类是约定俗称的实现这个方法,加上@abc.abstractmethod装饰器后严格控制子类必须实现这个方法
        def talk(self):
            raise AttributeError('子类必须实现这个方法')
    
    
    class People(Animal):  # 动物的形态之一:人
        def talk(self):
            print('say hello')
    
    
    class Dog(Animal):  # 动物的形态之二:狗
        def talk(self):
            print('say wangwang')
    
    
    class Pig(Animal):  # 动物的形态之三:猪
        def talk(self):
            print('say aoao')
    
    
    peo2 = People()
    pig2 = Pig()
    d2 = Dog()
    
    peo2.talk()
    pig2.talk()
    d2.talk()
    
    # say hello
    # say aoao
    # say wangwang

    六、类的封装

    类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。

    1、私有属性:双下划线的方式__x

    在python中用双下划线的方式__x实现隐藏属性(设置成私有的),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了。

    class Site:
        def __init__(self, name, url):
            self.name = name       # public
            self.__url = url   # private
     
        def who(self):
            print('name  : ', self.name)
            print('url : ', self.__url)
     
        def __foo(self):          # 私有方法
            print('这是私有方法')
     
        def foo(self):            # 公共方法
            print('这是公共方法')
            self.__foo()
     
    x = Site('菜鸟教程', 'www.runoob.com')
    x.who()        # 正常输出
    x.foo()        # 正常输出
    x.__foo()      # 报错

    2、外部使用变形访问:_类名__x

    类中所有双下划线开头的名称如__x都会自动变形成: _类名__x的形式:

    这种自动变形的特点:

    • 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
    • 这种变形其实正是针对内部的变形,在外部是无法通过__x这个名字访问到的。
    • 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的

    这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N。

    class A:
        __N = 0  # 把类的数据属性设置成私有的如__N,会变形为_A__N
    
        def __init__(self):
            self.__X = 10  # 变形为self._A__X
    
        def __foo(self):  # 变形为_A__foo
            print('from A')
    
        def bar(self):
            self.__foo()  # 只有在类内部才可以通过__foo的形式访问到.
    
    
    # 对象测试
    a = A()
    print(a._A__N)  # 0
    print(a._A__X)  # 10
    
    
    # 类测试
    print(A._A__N)  # 0
    print(A._A__X)  # 对象私有的属性# type object 'A' has no attribute '_A__X'

    注意:变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形。

    a = A()
    print(a.__dict__)  # {'_A__X': 10}
    
    a.__Y = 1
    print(a.__dict__)  # {'_A__X': 10, '__Y': 1}

    3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

    # 正常情况
    class A:
        def fa(self):
            print('from A')
    
        def test(self):
            self.fa()
    
    
    class B(A):
        def fa(self):
            print('from B')
    
    
    b = B()
    b.test()  # from B
    
    
    # 把fa定义成私有的,即__fa
    class A:
        def __fa(self):  # 在定义时就变形为_A__fa
            print('from A')
    
        def test(self):
            self.__fa()  # 只会与自己所在的类为准,即调用_A__fa
    
    
    class B(A):
        def __fa(self): #子类无法覆盖
            print('from B')
    
    
    b = B()
    b.test()  # from A

    python模块也遵循这种约定,如果模块中的变量名_amodule以单下划线开头,那么from module import *时不能被导入该变量,但是你from module import _amodule依然是可以导入该变量的。

    如果遇到下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部也是可以用的。严格控制属性的访问权限,只能借助内置方法如__getattr__。

    七、类的属性(property)

    1、装饰器方式 (推荐使用)

    property装饰器用于将被装饰的方法伪装成一个数据属性,在使用时可以不用加括号而直接使用。

    • 1. 定义时,在实例方法的基础上添加 @property 装饰器,并且仅有一个self参数
    • 2. 调用时,无需括号

    property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。

    分页的功能包括:

    1. 根据用户请求的当前页和总数据条数计算出 m 和 n
    2. 根据m 和 n 去数据库中请求数据
    class Pager:
        def __init__(self, current_page):
            # 用户当前请求的页码(第一页、第二页...)
            self.current_page = current_page
            # 每页默认显示10条数据
            self.per_items = 10
    
        @property
        def start(self):
            val = (self.current_page - 1) * self.per_items
            return val
    
        @property
        def end(self):
            val = self.current_page * self.per_items
            return val
    
    
    # ############### 调用 ###############
    p = Pager(2)
    print(p.start)  # 就是起始值,即:m
    # 10
    
    print(p.end)  # 就是结束值,即:n
    # 20

    2、经典类和新式类的属性方式:

    • 经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法。

    • 新式类(如果类继object,那么该类是新式类 )中的属性有三种访问方式,并分别对应了三个被 @property、@方法名.setter、@方法名.deleter 修饰的方法,对同一个属性:获取、修改、删除

    class Goods(object):
        def __init__(self):
            # 原价
            self.original_price = 100
            # 折扣
            self.discount = 0.8
    
        @property
        def price(self):
            # 实际价格 = 原价 * 折扣
            new_price = self.original_price * self.discount
            return new_price
    
        @price.setter
        def price(self, value):
            self.original_price = value
    
        @price.deleter
        def price(self):
            print('del')
            del self.original_price
    
    
    obj = Goods()
    print(obj.price)  # 获取商品价格
    # 80.0
    
    obj.price = 200  # 修改商品原价
    print(obj.price)
    # 160.0
    
    del obj.price  # 删除商品原价
    # del

    3、类属性方式

    注意:当使用类属性的方式创建property属性时,经典类和新式类无区别。

    property方法中有个四个参数

    1. 第一个参数是调用 对象.属性 时自动触发执行方法
    2. 第二个参数是调用 对象.属性 = XXX 时自动触发执行方法
    3. 第三个参数是调用 del 对象.属性 时自动触发执行方法
    4. 第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息
    class Goods(object):
        def __init__(self):
            # 原价
            self.original_price = 100
            # 折扣
            self.discount = 0.8
    
        def get_price(self):
            # 实际价格 = 原价 * 折扣
            new_price = self.original_price * self.discount
            return new_price
    
        def set_price(self, value):
            self.original_price = value
    
        def del_price(self):
            del self.original_price
    
        PRICE = property(get_price, set_price, del_price, '价格属性描述...')
    
    
    obj = Goods()
    print(obj.PRICE)  # 获取商品价格
    # 80.0
    
    obj.PRICE = 200  # 修改商品原价
    print(obj.PRICE)
    # 160.0
    
    del obj.PRICE  # 删除商品原价

    3、实例

    实现一个属性的设置和读取方法,可做边界判定

    class Money(object):
        def __init__(self):
            self.__money = 0
    
        # 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
        @property
        def money(self):
            return self.__money
    
        # 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
        @money.setter
        def money(self, value):
            if isinstance(value, int):
                self.__money = value
            else:
                print("error:不是整型数字")
    
    
    a = Money()
    a.money = 100
    print(a.money)# 100

    八、实例方法及非绑定实例方法

    1、实例方法

    在类中没有被任何装饰器修饰的方法就是绑定到对象的实例方法,这类方法专门为对象定制。

    class Person:
        country = "China"
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def speak(self):
            print(self.name + ', ' + str(self.age))
    
    
    p = Person('Kitty', 18)
    print(p.__dict__)
    # {'name': 'Kitty', 'age': 18}
    
    print(Person.__dict__['speak'])
    # <function Person.speak at 0x10f0dd268>

    speak即为绑定到对象的方法,这个方法不在对象的名称空间中,而是在类的名称空间中。

    绑定到对象的方法:

    1. 通过对象调用,会有一个自动传值的过程,即自动将当前对象传递给方法的第一个参数(self,一般都叫self,也可以写成别的名称);
    2. 使用类调用,则第一个参数需要手动传值。
    p = Person('Kitty', 18)
    p.speak()  # 通过对象调用
    # Kitty, 18
    
    Person.speak(p)  # 通过类调用
    # Kitty, 18

    2、类方法(@classmethod )

    类中使用 @classmethod 修饰的方法就是绑定到类的方法。这类方法专门为类定制。

    1. 通过类名调用绑定到类的方法时,会将类本身当做参数传给类方法的第一个参数。类方法默认有个 cls 参数。
    2. 通过对象也可以调用,只是默认传递的第一个参数还是这个对象对应的类。
    class Operate_database():
        host = '192.168.0.5'
        port = '3306'
        user = 'abc'
        password = '123456'
    
        @classmethod
        def connect(cls):  # 约定俗成第一个参数名为cls,也可以定义为其他参数名
            print(cls)
            print(cls.host + ':' + cls.port + ' ' + cls.user + '/' + cls.password)
    
    
    Operate_database.connect()
    # <class '__main__.Operate_database'>
    # 192.168.0.5:3306 abc/123456
    
    
    Operate_database().connect()  # 输出结果一致
    # <class '__main__.Operate_database'>
    # 192.168.0.5:3306 abc/123456

    3、静态方法(@staticmethod )

    在类内部使用 @staticmethod 修饰的方法即为非绑定的静态方法,这类方法和普通定义的函数没有区别,不与类或对象绑定,谁都可以调用,且没有自动传值的效果。

    import hashlib
    
    
    class Operate_database():
        def __init__(self, host, port, user, password):
            self.host = host
            self.port = port
            self.user = user
            self.password = password
    
        @staticmethod
        def get_passwrod(salt, password):
            m = hashlib.md5(salt.encode('utf-8'))  # 加盐处理
            m.update(password.encode('utf-8'))
            return m.hexdigest()
    
    
    hash_password = Operate_database.get_passwrod('lala', '123456')  # 通过类来调用
    print(hash_password)
    # f7a1cc409ed6f51058c2b4a94a7e1956
    
    p = Operate_database('192.168.0.5', '3306', 'abc', '123456')
    hash_password = p.get_passwrod(p.user, p.password)  # 也可以通过对象调用
    print(hash_password)
    # 0659c7992e268962384eb17fafe88364

    九、类的专有方法:

    • __init__ : 构造函数,在生成对象时调用
    • __del__ : 析构函数,释放对象时使用
    • __repr__ : 打印,转换
    • __setitem__ : 按照索引赋值
    • __getitem__: 按照索引获取值
    • __len__: 获得长度
    • __cmp__: 比较运算
    • __call__: 函数调用
    • __name__:模块名称,一段程序作为主线运行程序时其内置名称就是 __main__

    1、运算符重载

    • __add__: 加运算
    • __sub__: 减运算
    • __mul__: 乘运算
    • __truediv__: 除运算
    • __mod__: 求余运算
    • __pow__: 乘方

    对类的专有方法进行重载实例如下:

    class Vector:
       def __init__(self, a, b):
          self.a = a
          self.b = b
     
       def __str__(self):
          return 'Vector (%d, %d)' % (self.a, self.b)
       
       def __add__(self,other):
          return Vector(self.a + other.a, self.b + other.b)
     
    v1 = Vector(2,10)
    v2 = Vector(5,-2)
    print (v1 + v2)
    
    #Vector(7,8)
  • 相关阅读:
    steam
    node 循序渐进
    node 常用指令 node 扩展链接
    window 常用指令
    web API
    SHAREPOINT
    div设置边框黑框显示
    sharepoint更新多行文本webparth
    sharepoint读取启用了追加功能的多行文本的历史版本记录
    JS实现多附件上传(asp.net)
  • 原文地址:https://www.cnblogs.com/springsnow/p/11985145.html
Copyright © 2020-2023  润新知