• python学习笔记25:基础语法之class


    1. 基本语法

    名词 解释
    类创建一个新类型;是一个抽象的模板;
    对象/实例 类的实例;每个对象拥有相同的方法,但数据可能不同;
    属于一个类或对象的变量,用于存储数据;有两种类型:实例变量、类变量;
    方法 属于一个类的函数;
    属性 域和方法合称为属性;
    实例变量 属于每个实例(类的对象)的域;
    类变量 属于类本身的域;
    class Dog(object): # 类,创建一个新类型,是一个抽象的模板
        LEG_CNT = 4    # 类变量,该类的所有实例共享
    
        def __init__(self, a): # 构造函数,在类实例化时自动调用, 定义需要一个额外的self参数。
            self.a = a         # 实例变量,每个实例单独赋值,不共享
    
        def func0(self):       # 实例方法,需要一个self参数,表示实例本身。
            print(f'Calling Func0, a = {self.a}')           # 可以访问实例变量
            print(f'Calling Func0, LEG_CNT={self.LEG_CNT}') # 可以访问类变量
    
        @classmethod     # 这是个装饰器,它装饰的方法会变成类方法
        def func1(cls):  # 类方法需要cls参数,为类本身
            print(f'Calling Func1, LEG_CNT={cls.LEG_CNT}')  # 可以访问类变量
            cls.LEG_CNT += 1                                # 可以修改类变量
            print(f'Calling Func1, LEG_CNT={cls.LEG_CNT}')
    
            #cls.func0() # 调用实例方法,直接调用会报错: 需要self参数, 所以需要加上实例参数才能调用
            #cls.a       # 不能访问实例变量,报告无此属性
    
        @staticmethod    # 这是个装饰器,它装饰的方法会变成静态方法
        def func2():     # 静态方法不需要额外的self/cls参数.
            # 静态方法不需要(也不能)访问类对象或类实例的其它成员和方法(因为没有cls或self参数),
            # 只是把函数嵌入到了类中,以方便继承或组织代码 
            print('Calling Static Func2')
    
    >>> d0 = Dog(0)
    >>> d0.func0()  # 通过实例名调用实例方法
    Calling Func0, a = 0
    Calling Func0, LEG_CNT=4
    >>> 
    >>> Dog.func1()   # 通过class名调用类方法
    Calling Func1, LEG_CNT=4
    Calling Func1, LEG_CNT=5
    
    >>> Dog.func0(d0) # 通过class名,传入实例,调用实例方法
    Calling Func0, a = 0
    Calling Func0, LEG_CNT=5
    >>> 
    >>> Dog.func2() # 通过class名调用静态方法
    Calling Static Func2
    >>> d0.func2()  # 通过实例名调用静态方法
    Calling Static Func2
    

    变量访问

    命名方法 举例 访问限制
    双下划线开头 __name 私有变量,外部不能访问。
    双下划线开头、结尾 __name__ 特殊变量,外部可以访问。
    单下划线开头 _name 私有变量,但外部可以访问。

    2. 多重继承

    多重继承时,super()的执行顺序
    例1:
    class继承关系

    graph LR A[class C20 <br/> from C10 C11] -- 继承 --> B[class C10 <br/> from C00] A --> C[class C11 <br/> from C00] B --> D[class C00] C --> D
    class C00():
        def run(self):
            print('run C00')
            
    class C10(C00):
        def run(self):
        	super(C10, self).run()
            print('run C10')        
                    
    class C11(C00):
        def run(self):
        	super(C11, self).run()
            print('run C11')
            
    class C20(C10, C11):
        def run(self):
        	super(C20, self).run()
            print('run C20')        
                    
    

    执行:

    >>> C20.__mro__  
    (<class ‘__main__.C20’>, <class ‘__main__.C10’>, <class ‘__main__.C11’>, <class ‘__main__.C00’>, <class ‘object’>)  
    >>> c = C20()  
    >>> c.run()  
    run C00  
    run C11  
    run C10  
    run C20  
    

    解释:
    C20.__mro__是一个元组,采用广度优先原则(同一层级的优先,不同于深度优先),每个super函数调用的都是C20.__mro__中下一个元素对应类的函数;

    >>> C20.__mro__  
    (  
        <class '__main__.C20'>,  
        <class '__main__.C10'>,  
        <class '__main__.C11'>,  
        <class '__main__.C00'>,  
        <class 'object'>,  
    )  
    
                             /----------------       /----------------       /----------------       /----------------          
                             |                |       |C20.__mro__[1]: |       |C20.__mro__[2]: |       |C20.__mro__[3]: |          
                           ->|                |     ->|cls __main__.C10|     ->|cls __main__.C11|     ->|cls __main__.C00|          
                          /  |C20.run()       |    /  |C10.run()       |    /  |C11.run()       |    /  |C00.run()       |          
                        /    ----------------/  /    ----------------/  /    ----------------/  /    ----------------/          
                      /              |         /              |         /              |         /              |                   
    /----------------       /----------------       /----------------       /----------------               |                   
    |    c = C20()   |       |super(C20, self)|       |super(C10, self)|       |super(C11, self)|               |                   
    |    c.run()     |       |    .run()      |       |    .run()      |       |    .run()      |               |                   
    |      (1)       |       |      (2)       |       |      (3)       |       |      (4)       |               |                   
    ----------------/       ----------------/       ----------------/       ----------------/               |           #output 
                                     |                        |                        |             (5) print('run C11')--"run C00"
                                     |                        |             (6) print('run C11')---------------------------"run C11"
                                     |             (7) print('run C10')----------------------------------------------------"run C10"
                          (8) print('run C20')---------------------------------------------------------------------------- "run C20"
    
    
    1. c.run(), 调用C20.run()
    2. C20.run()第一行,super(C20, self).run(),mro[1],调用C10.run()
    3. C10.run()第一行,super(C10, self).run(),mro[2],调用C11.run()
    4. C11.run()第一行,super(C11, self).run(),mro[3],调用C00.run()
    5. C00.run()没有super,打印’run C00’, C00.run()执行完毕
    6. C11.run()第二行,打印‘run C11’,C11.run()执行完毕
    7. C10.run()第二行,打印‘run C10’,C10.run()执行完毕
    8. C20.run()第二行,打印‘run C20’,C20.run()执行完毕

    3. 魔术方法

    3.1. __str__()

    str:改变 print(类实例)时显示的内容。

    普通class

    >>> class CTest0():  
    ...     pass  
    ...  
    >>> t0 = CTest0()  
    >>> t0  
    <__main__.CTest0 object at 0x2b***>  
    >>> print(t0)  
    <__main__.CTest0 object at 0x2b***>  
    

    重构__str__()

    >>> class CTest1():  
    ...     def __str__(self):  
    ...         return ‘Class {} obj’.format(self.__class__.__name__)  
    ...  
    >>> t1 = CTest1()  
    >>> t1 # 直接输出对象,与默认情况相同  
    <__main__.CTest1 object at 0x2b***>  
    >>> print(t1) # 打印类实例,输出__str__()方法的返回值 
    <Class CTest1 obj>  
    

    3.2. __repr__()

    repr:改变直接输出对象和 print(类实例) 时显示的内容
    重构__repr__()

    >>> class CTest2():  
    ...     def __repr__(self):  
    ...         return ‘Class {} obj’.format(self.__class__.__name__)  
    ...  
    >>> t2 = CTest2()  
    >>> t2 # 直接输出对象,输出__repr__()方法的返回值  
    <Class Ctest2 obj>  
    >>> print(t2) # 打印类实例,输出__repr__()方法的返回值  
    <Class CTest2 obj>  
    

    3.3. __iter__()

    iter()方法:返回一个迭代对象;
    next()方法:迭代__iter__()返回的对象时,会调用__next__()方法拿到循环的下一个值,直到遇到StopIteration错误;

    class Fib():  
        def __init__(self):  
            self.a, self.b = 0, 1  
      
        def __iter__(self):  
            return self  
      
        def __next__(self):  
            self.a, self.b = self.b, self.a+self.b  
            if self.a > 100:  
                raise StopIteration()  
            return self.a  
      
    >>> #对class 实例进行循环  
    >>> for i in Fib():  
    ...     print(i, end=‘ ’)  
    ...  
    1 1 2 3 5 8 13 21 34 55 89  
    

    3.4. __getitem__()

    使用__iter__()方法虽然可以对类实例做循环,但不能用下标取元素,
    使用__getitem__()方法可以实现“用下标取元素“;

    class Fib():  
        def __getitem__(self, n):  
            a, b = 1, 1 # 如果n==0,则不进入for循环,直接返回1,保证[0]==1  
            for i in range(n):  
                a, b = b, a+b  
       
        return a  
      
    >>> #对类实例进行取下标  
    >>> Fib()[8]  
    34  
    >>> f = Fib()  
    >>> f[8]  
    34  
    

    使用__getitem__()可实现“按下标取元素“,但不能处理切片、不能“按下标赋值”、不能删除元素,这些都可以通过添加相应的方法来完成。

    拦截对[‘’]方式的属性调用

    3.5. __setitem__()

    class A:  
      def __init__(self, cfg={}):  
        self.cfg = cfg  
      
      def __setitem__(self, k, v):  
        self.cfg[k] = v  
      
      def __getitem__(self, k):  
        return self.cfg[k]  
    

    3.6. __getattr__()

    当调用不存在的属性时,正常情况下会报告AttributeError: ‘XX’ object has no attribute ‘YY’;
    可以通过__getattr__()方法动态返回一个属性,这时如果调用不存在的属性时,Python就会调用__getattr__(self, ‘attr’)来获取属性.

    class Student():  
        def __init__(self):  
            self.name = ‘Jim’  
       
        def __getattr__(self, attr):  
            if attr == ‘score’:  
                return 99  
            raise AttributeError(‘’Student’ object has no attribute xx’)  
      
    >>> # 尝试调用属性score:  
    >>> s = Student()  
    >>> s.name  
    ‘Jim’  
    >>> s.score # 本没有score这个属性,但通过__getattr__()方法获取了返回值  
    99  
    

    拦截属性取值语句, 即 a = obj.xx

    3.7. __setattr__()

    拦截属性的赋值语句, 即 obj.xx = yy

    class F(object):
    def setattr(self, key, value):
    self.dict[key] = value

    3.8. __call__()

    使用__call__()方法可以使类实例变得“可调用”,类实例就象一个函数一样;

    class Student():  
        def __init__(self, name):  
            self.name = name  
       
        def __call__(self):  
            print(f’My name is {self.name}’)  
      
    >>> #尝试调用类实例  
    >>> s = Student(‘Jim’)  
    >>> s() # 此句会调用类的__call__()方法  
    My name is Jim  
    

    3.9. __new__()

    new()与__init__()的区别:

    1. new()方法先调用, init()方法后调用.
    2. new()是class级别的方法, 用于控制一个新instance的生成过程.
    3. new()需要一个参数cls, (但又不需要声明它是@classmethod).
    4. new()必须返回实例化出来的实例.
    5. init()是instance级别的方法, 用于初始化一个新实例.
    class Person(object):  
        def __new__(cls, *args, **kwargs):  
            print(f'__new__() called. {args} {kwargs}')  
            return super(Person, cls).__new__(cls) #NOTE, there is no args  
      
        def __init__(self, name, age):  
            print('__init__() called.')  
            self.name = name  
            self.age  = age  
      
        def __str__(self):  
            return f'<Person: {self.name}({self.age})>'  
      
    p = Person('n0', age=24)  
    print(p)  
    

    执行结果如下:

    __new__() called. ('n0',) {'age': 24} # new方法先执行  
    __init__() called.                    # init方法后执行  
    <Person: n0(24)>  
    

    对于p = Person(name, age),

    1. 首先使用参数name和age来执行Person.new(name, age), new()会返回Person类的一个实例, 通常是使用这种方式: super().new(cls, …).
    2. 然后使用__new__()返回的实例调用__init__()方法.

    new()方法的使用场景:

    1. 继承不可变的class(比如str, int, tuple等)时. 如永远是正数的整型.
    class PosInt(int):  
        def __init__(self, i):  
            super().__init__() #NOTE: here takes no parameters.  
      
    i0 = PosInt(-3)  
    print(i0) # -3  
      
    class PosInt(int):  
        def __new__(cls, i):  
            return super().__new__(cls, abs(i))  
      
    i0 = PosInt(-3)  
    print(i0) # 3  
    
    1. 实现单例模式, 通过__new__()方法返回实例.

    3.10. __eq__()

    重新定义类的==行为.

    class CTest():  
        def __init__(self, i_value):  
            self.i_value = i_value  
      
        def __eq__(self, other):  
            #当两个实例的value相差<4时, 认为相等  
            if abs(self.i_value-other.i_value)<4:  
                return True # 返回True表示相等  
            else:  
                return False # 返回False表示不等  
      
    if __name__ == '__main__':  
        t1 = CTest(1)  
        t2 = CTest(2)  
        t7 = CTest(7)  
      
        print(t1 == t2) # True  
        print(t1 == t7) # False  
    

    4. 控制class的创建

    4.1. type()

    作用:可以动态创建类。

    以下代码定义了一个类:

    class Hello(object):  
        def hello(self, name=‘world’):  
            print(‘Hello, %s’%(name))  
    

    等价于以下代码,使用type(),Python解释器遇到class定义时,就是调用type()创建class的。

    def fn(self, name=‘world’):  
        print(‘Hello, %s’%(name))
    
    Hello=type(‘Hello’, (object,), dict(hello=fn)) #创建Hello class  
    

    创建class对象时,type的3个参数:

    1. class名称;
    2. 继承的父类集合(如果只有一个父类,tuple单元素需要一个逗号);
    3. class的method名称与函数绑定(这里把函数fn绑定到method hello上);

    对于上面两种class定义的方式,以下测试结果相同

    >>> h = Hello()  
    >>> h.hello()  
    Hello, world  
    >>> print(type(Hello)) # Hello是通过type创建的,所以它的type是type。  
    <class ‘type’>  
    >>> print(type(h)) # h是通过Hello创建的,所以它的type是Hello。  
    <class ‘__main__.Hello’>  
    

    4.2. metaclass

    metaclass可以控制类的创建行为(创建类或修改类)
    metaclass、类、实例,三者的关系:根据metaclass创建类、根据类创建实例;

    定义metaclass:metaclass是类的模板,所以从type类派生;

    class ListMetaclass(type): # metaclass的类名总以Metaclass结尾,以方便识别  
        def __new__(cls, name, bases, attrs):  
            attrs[‘add’] = lambda self, value: self.append(value)  
            return __new__(cls, name, bases, attrs)  
    

    new()方法的参数:

    1. 待创建的类对象;
    2. 类名字;
    3. 类的父类集合;
    4. 类的方法集合;

    利用metaclass来定制类(指导类的创建方式)

    class MyList(list, metaclass=ListMetaclass):  
        pass 
    

    使用metaclass参数,Python创建MyList时,会通过ListMetaclass.new()来创建,所以MyList这个类多了一个方法:add()

    测试:

    >>> L = MyList()  
    >>> L  
    []  
    >>> L.add(1) # L有add()方法  
    >>> L  
    [1]  
    >>> L2 = list()  
    >>> L2.add(1) # L2没有add()方法  
    AttributeError: ‘list’ object has no attribute ‘add’  
    

    在ORM(Object Relational Mapping,对象-关系映射,把关系数据库的行为映射为一个对象,即一个类对应一个表)框架中,类只能动态定义,因为类的定义取决于表的结构,只有使用者才能根据表的结构定义出对应的类。

  • 相关阅读:
    Java实现 蓝桥杯VIP 算法训练 黑色星期五
    Java实现 蓝桥杯VIP 算法训练 比赛安排
    Java实现 蓝桥杯VIP 算法训练 比赛安排
    Java实现 蓝桥杯VIP 算法训练 斜率计算
    Java实现 蓝桥杯VIP 算法训练 斜率计算
    Java实现 蓝桥杯VIP 算法训练 整数平均值
    Java实现 蓝桥杯VIP 算法训练 整数平均值
    控件动态产生器(使用RegisterClasses提前进行注册)
    Delphi编写自定义控件以及接口的使用(做了一个TpgDbEdit)
    Log4delphi使用心得
  • 原文地址:https://www.cnblogs.com/gaiqingfeng/p/13255480.html
Copyright © 2020-2023  润新知