零基础搭建量化投资系统:以Python为工具
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

4.2 Python的类

面向对象的编程强调数据封装、继承等对象特性,即对象=数据+方法(数据处理程序)。

在面向过程的编程中,def语句是定义函数体。

Python类中方法的定义也使用了def语句,所不同的是参数表不同,方法定义形式参数表中第一个参数永远是“self”关键字。

面向对象编程的基础是类的编程,下面介绍一些关于类的概念。

类(Class):用来描述具有相同的属性和方法的对象集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

类变量:类变量在整个实例化的对象中是公用的,它定义在类中且在方法体之外。

数据成员:即类变量或者实例变量,用于处理类及其实例对象的相关数据。

方法:类中定义的数据处理或操作,定义和使用类似于函数。

方法重写:如果从父类继承的方法中不能满足子类的需求,就可以对其进行改写,这个过程叫作方法的覆盖(override),也称为方法的重写。

实例变量:定义在方法中的变量,只作用于当前实例的类。

继承:即一个派生类(derived class)继承基类(base class)的方法。继承也允许把一个派生类的对象作为一个基类的对象对待。

实例化:创建一个类的实例,类的具体对象。类是抽象的模板,实例化是根据类创建出来的不同名字的具体“对象”。每个对象都拥有相同的方法,但各自的数据可能不同。

对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

下面介绍Python中类的定义和使用。

1.创建类

使用class语句创建一个新类,class后面为类的名称并以冒号“:”结尾。

        class ClassName:
          '类的帮助信息’                         #类文档字符串
        class_suite                               #类体

类的帮助信息可以通过“ClassName.__doc__”查看。class_suite由类成员、方法、数据属性组成。

以下是一个简单的例子,示例4-9的部分代码。

        class dog(object):                         #定义小狗类
            '这是一个dog类的定义。'
            def __init__(self, dogname=’小虎’):  #类初始化
              self.name=dogname
              print('\n你的小狗叫’+self.name+'。')

           def SetName(self, dogname):
              self.name=dogname
              print(’你的小狗改名叫’+self.name+'。')

           def Run(self):
              print(self.name+’正在跑。')

           def CalltheDog(self):
              print(’你呼唤\"'+self.name+'\", '+self.name+’向你跑来。\n')
            def __del__(self):                     #类对象销毁
              class_name = self.__class__.__name__
              print(class_name+ " 销毁")

__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法。当创建了这个类的实例时,就会调用该方法。

self代表类的实例。虽然在调用时不必传入相应的参数,但在定义类的方法时它是必须有的。

2.实例化类对象

在其他编程语言中,实例化类一般用关键字new,但是在Python中没有这个关键字,因此其类的实例化的方式类似于函数调用。

以下使用类的名称(dog)来实例化,并通过__init__()方法接收参数。示例4-9的部分代码。

        #实例化第一个类MyDoga
        MyDoga=dog()                         #使用默认参数
        MyDoga.Run()
        #实例化第一个类MyDoga
        MyDogb=dog(’旺财’)                 #使用自定义参数
        MyDogb.CalltheDog()
        MyDogb.SetName(’小哈’)
        MyDogb.CalltheDog()

见示例4-9。

        #类演示代码
        class dog(object):                      #定义小狗类
            '这是一个dog类的定义。'
            def __init__(self, dogname=’小虎’):  #类初始化
              self.name=dogname
              print('\n你的小狗叫’+self.name+'。')
            def SetName(self, dogname):
              self.name=dogname
              print(’你的小狗改名叫’+self.name+'。')
            def Run(self):
              print(self.name+’正在跑。')
            def CalltheDog(self):
              print(’你呼唤\"'+self.name+'\", '+self.name+’向你跑来。\n')
            def __del__(self):                  #类对象销毁
              class_name = self.__class__.__name__
              print(class_name+ " 销毁")
        #实例化第一个类MyDoga
        MyDoga=dog()                           #使用默认参数
        MyDoga.Run()
        #实例化第一个类MyDoga
        MyDogb=dog(’旺财’)                     #使用自定义参数
        MyDogb.CalltheDog()
        MyDogb.SetName(’小哈’)
        MyDogb.CalltheDog()

程序运行结果:

        你的小狗叫小虎。
        小虎正在跑。
        你的小狗叫旺财。
        你呼唤"旺财",旺财向你跑来。
        你的小狗改名叫小哈。
        你呼唤"小哈",小哈向你跑来。

3.访问属性

可以使用点号“.”来访问对象的属性。

在上例中,可以通过SetName()方法来修改dog的名字,也可以直接修改类属性。

见示例4-10。

        class dog(object):                             #定义小狗类
            '这是一个dog类的定义。'
            def __init__(self, dogname=’小虎’):         #类初始化
              self.name=dogname
              print('\n你的小狗叫’+self.name+'。')
            def SetName(self, dogname):
              self.name=dogname
              print(’你的小狗改名叫’+self.name+'。')
            def Run(self):
              print(self.name+’正在跑。')
            def CalltheDog(self):
              print(’你呼唤\"'+self.name+'\", '+self.name+’向你跑来。\n')
            def __del__(self):                         #类对象销毁
              class_name = self.__class__.__name__
              print(class_name+ " 销毁")

       #实例化第一个类MyDoga
        MyDogc=dog(’小黑’)                          #使用自定义参数
        MyDogc.CalltheDog()
        MyDogc.name="旺旺财"
        MyDogc.CalltheDog()

程序运行结果:

        你的小狗叫小黑。
        你呼唤"小黑",小黑向你跑来。
        你呼唤"旺旺财",旺旺财向你跑来。

4.类的内置属性

Python类的内置属性的种类及含义见表4-1。

表4-1 Python类的内置属性

见示例4-11。

        class dog(object):                             #定义小狗类
            '这是一个dog类的定义。'
            def __init__(self, dogname=’小虎’):      #类初始化
              self.name=dogname
              print('\n你的小狗叫’+self.name+'。')
            def SetName(self, dogname):
              self.name=dogname
              print(’你的小狗改名叫’+self.name+'。')
            def Run(self):
              print(self.name+’正在跑。')
            def CalltheDog(self):
              print(’你呼唤\"'+self.name+'\", '+self.name+’向你跑来。\n')
            def __del__(self):   #类对象销毁
              class_name = self.__class__.__name__
              print(class_name+ " 销毁")

       print('dog.__name__ : ', dog.__name__)
        print('dog.__doc__ : ', dog.__doc__)
        print('dog.__bases__ : ', dog.__bases__)
        print('dog.__dict__ : ', dog.__dict__)
        print('dog.__module__ : ', dog.__module__)
        print('dog.__class__ : ', dog.__class__)

程序运行结果:

        dog.__name__ :  dog
        dog.__doc__ :  这是一个dog类的定义。
        dog.__bases__ : (<class 'object'>,)
        dog.__dict__ :  {'__module__': '__main__', '__doc__': ’这是一个dog类的
    定义。', '__init__': <function dog.__init__ at 0x0000000009C30048>, 'SetName':
    <function dog.SetName at 0x0000000009C30A60>, 'Run': <function dog.Run at
    0x0000000009C30B70>, 'CalltheDog': <function dog.CalltheDog at
    0x0000000009C301E0>, '__del__': <function dog.__del__ at
    0x0000000009C30D08>, '__dict__': <attribute '__dict__' of 'dog' objects>,
    '__weakref__': <attribute '__weakref__' of 'dog' objects>}
        dog.__module__ :  __main__
        dog.__class__ :  <class 'type'>dog.__module__ :  __main__

5.类对象销毁

Python使用了引用计数来跟踪和回收类对象。当类对象(类实例)被创建时,就已经创建了一个引用计数。当类对象不再使用,即这个对象的引用计数变为0时,它就要被销毁。但是销毁不是立即操作的,而是由Python解释器在适当的时机进行,并回收类对象占用的内存空间。用户也可以在程序中主动销毁类对象。

Python类有析构函数“__del__”。它在对象销毁时被调用,并且当对象不再被使用时,用户可以使用del语句销毁类对象。

        del MyDoga
        del MyDogb
        del MyDogc

见示例4-12。

        class dog(object):                       #定义小狗类
            '这是一个dog类的定义。'
            def __init__(self, dogname=’小虎’):#类初始化
                self.name=dogname
                print('\n你的小狗叫’+self.name+'。')
            def SetName(self, dogname):
                self.name=dogname
                print(’你的小狗改名叫’+self.name+'。')
            def Run(self):
                print(self.name+’正在跑。')
            def CalltheDog(self):
                print(’你呼唤\"'+self.name+'\", '+self.name+’向你跑来。\n')
            def __del__(self):                   #类对象销毁
                class_name = self.__class__.__name__
                print(class_name+ " 销毁")

       #实例化第1个类MyDoga
        MyDoga=dog()                            #使用默认参数
        MyDoga.CalltheDog()
        #实例化第2个类MyDoga
        MyDogb=dog(’旺财’)                      #使用自定义参数
        MyDogb.CalltheDog()
        #实例化第3个类MyDoga
        MyDogc=dog(’小黑’)                      #使用自定义参数
        MyDogc.CalltheDog()
        del MyDoga
        del MyDogb
        del MyDogc
        MyDogc.CalltheDog()                     #类示例销毁,再执行此句会出错

程序运行结果:

        你的小狗叫小虎。
        你呼唤"小虎",小虎向你跑来。
        你的小狗叫旺财。
        你呼唤"旺财",旺财向你跑来。
        你的小狗叫小黑。
        你呼唤"小黑",小黑向你跑来。
        dog销毁
        dog销毁
        dog销毁
        MyDogc.CalltheDog()
        NameError: name 'MyDogc' is not defined

6.类的继承

面向对象的编程的主要好处之一是代码的重用,而实现重用的方法之一是通过继承机制。

通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。

继承语法:

        class派生类名(基类名)
            ...

在Python中继承的一些特点:

(1)如果在子类中需要直接使用父类的构造方法,就需要显式调用或者不重写父类的构造方法。

(2)在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。而在类中调用普通方法时不需要带上self参数。

(3)Python会先在派生类中查找对应类型的方法,如果找不到,就会到基类中逐个查找。

如果在继承元组中罗列了一个以上的类,那么它就被称作多重继承。

派生类的声明与它们的父类类似,继承的基类列表跟在类名之后,如下所示:

        class SubClassName(ParentClass1[, ParentClass2, ...]):
            ...

7.方法重写

如果父类方法的功能不能满足需求,就可以在子类重写父类的方法。下面给出有关类继承和方法重新的演示代码,见示例4-13。

        class dog(object):                             #定义小狗类
            '这是一个dog类的定义。'
            def __init__(self, dogname=’小虎’):        #类初始化
              self.name=dogname
              print('\n你的小狗叫’+self.name+'。')
            def SetName(self, dogname):
              self.name=dogname
              print(’你的小狗改名叫’+self.name+'。')
            def Run(self):
              print(self.name+’正在跑。')
            def CalltheDog(self):
              print(’你呼唤\"'+self.name+'\", '+self.name+’向你跑来。\n')
            def __del__(self):
              class_name = self.__class__.__name__
              print(class_name+ " 销毁")

       class newdog(dog):
          '这是新dog类’
          def Eat(self):                           #定义子类新方法
              print(self.name+’正在吃东西。')
          def CalltheDog(self):                    #重构基类方法
              print(’你呼唤\"'+self.name+'\", '+self.name+’抬头看你。\n')

       #实例化第一个类MyDoga
        MyDoga=newdog()                            #使用默认参数
        MyDoga.Run()
        MyDoga.Eat()
        print('----------------')
        MyDoga.name=’宝贝’
        MyDoga.Run()
        MyDoga.Eat()
        MyDoga.CalltheDog()
        del MyDoga

程序运行结果:

        你的小狗叫小虎。
        小虎正在跑。
        小虎正在吃东西。
        ----------------
        宝贝正在跑。
        宝贝正在吃东西。
        你呼唤"宝贝",宝贝抬头看你。
        newdog销毁

8.类基础方法重载

类能重载的基础方法的种类及功能,见表4-2。

表4-2 类基础方法重载

Python除了支持基础方法重载,同样支持运算符重载,见表4-3。

表4-3 常用运算符重载方法

9.类的super()方法

在类的继承中,如果重定义某个方法,子类方法就会覆盖父类的同名方法。但如果我们希望能同时使用父类(基类)的同名方法,就可以通过super()方法来实现。

super()方法是用于调用父类(基类)的一个方法。

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

super()方法的语法:

        super(type[, object-or-type])

super()方法的参数见表4-4。

表4-4 super()方法的参数

事实上,对于定义的每一个类,Python都会计算出一个方法解析顺序(Method Resolution Order, MRO)列表,它代表了类继承的顺序。一个类的MRO列表就是合并所有父类的MRO列表,并遵循以下三条原则。

(1)子类永远在父类前面。

(2)如果有多个父类,就会根据它们在列表中的顺序被检查。

(3)如果下一个类存在两个合法的选择,就选择第一个父类。

super()方法的使用,见示例4-14。

        class A(object):
            def __init__(self):
              print("init A Class")
              super(A, self).__init__()
        class B(object):
            def __init__(self):
              print("init B Class")
              super(B, self).__init__()
        class C(object):
            def __init__(self):
              print("init C Class")
              super(C, self).__init__()
        class D(A, B, C):
            def __init__(self):
              print("init D class")
              super(D, self).__init__()
        class E(D):
            def __init__(self):
              print("init E class")
              super(E, self).__init__()
        class F(E):
          def __init__(self):
              print("init F class")
              super(F, self).__init__()

       F = F()

程序运行结果:

        init F class
        init E class
        init D class
        init A Class
        init B Class
        init C Class

10.类的属性与方法

类的私有属性如下:

__private_attrs:以两个连接的下画线开头,声明该属性为私有,不能在类的外部使用或直接访问。在类的内部使用时为self.__private_attrs。

类的方法:在类的内部使用def关键字可以为类定义一个方法。与一般函数定义不同,类方法必须包含参数self,且为第一个参数。

类的私有方法如下:

__private_method:以两个连接的下画线开头,声明该方法为私有方法,不能在类的外部调用。在类的内部调用时为self.__private_methods(self. __私有方法)。

Python不允许类实例访问私有属性,但可以使用object._className__attrName(对象名._类名__私有属性名)访问私有属性。见示例4-16的print(t._testPrivate__data)。

单下画线、双下画线、头尾双下画线的说明(foo为名字,可以是变量名或方法名):

_foo:以单下画线开头表示的是protected类型的变量,即保护类型,只允许其本身与子类进行访问,不能用于from module import *。

__foo:以两个连接的下画线开头表示的是私有类型private的变量,只允许这个类进行访问。

__foo__:其定义的是特殊方法,一般是系统定义名字,类似于__init__()。

见示例4-15。

        class testPrivate:
            def __init__(self):
                self.__data = []

           def add(self, item):
                self.__data.append(item)
            def printData(self):
                print(self.__data)

       t = testPrivate()
        t.add('dancingrain')
        t.add('hello')
        t.printData()
        print(t.__data)

程序运行结果:

        ['dancingrain', 'hello']
            print(t.__data)
        AttributeError: 'testPrivate' object has no attribute '__data'

在程序中,如果直接访问私有变量就会出现错误提示“AttributeError: 'testPrivate' object has no attribute '__data'”。

但是,这并不意味着我们就不能从外部访问这个变量了。前面说过Python在类的内部用_classname__foo替换了__foo,因此,我们可以在类的外面使用_testPrivate__data访问__data。代码见示例4-16。

        class testPrivate:
            def __init__(self):
                self.__data = []
            def add(self, item):
                self.__data.append(item)
            def printData(self):
                print(self.__data)
        t = testPrivate()
        t.add('testPrivate Var')
        t.add('hello')
        t.printData()
        print(t._testPrivate__data)

程序运行结果:

        ['testPrivate Var', 'hello']
        ['testPrivate Var', 'hello']

上面结果表明,我们获取到了私有变量的值。

使用私有变量是为了防止其他程序员或用户误改关键数据。如果一定要修改内部私有数据,就使用类内部方法间接修改。