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,对象-关系映射,把关系数据库的行为映射为一个对象,即一个类对应一个表)框架中,类只能动态定义,因为类的定义取决于表的结构,只有使用者才能根据表的结构定义出对应的类。

posted @ 2020-07-06 16:33  编程驴子  阅读(320)  评论(0编辑  收藏  举报