Python量化交易之类详解

发布于:2022-12-15 ⋅ 阅读:(319) ⋅ 点赞:(0)

面向对象编程:

1、类的特性:

把一定的数据和处理数据的方法封装在一起,成为一个整体,这个整体在计算机语言里称为对象封装数据和方法的过程称为面向对象编程

Python中,把有着相同属性和方法的对象集合称为类,而一个对象就是类的具体实例

比如,猫都有颜色、四肢等属性,也有跑步、跳跃等功能(方法),因此猫就是一个类,而某只具体的猫,是猫类的具体实例。

再比如,整型是一个类,某个具体的数值,比如“5”,就是整型的具体实例。

类type()可以查看一个对象是什么类型,例如:

>>> type(5)
<class 'int'>
>>> type(5.0)
<class 'float'>
>>> type('a')
<class 'str'>
>>> type(range(5))
<class 'range'>
>>>

上例可以看出,5是整型,5.0是浮点型,'a'是字符串,range(5)是range类型。

用函数isinstance(o, t)判断对象o是否是t类型或t的子类,例如:

>>> isinstance(5,int)
True
>>> isinstance(5,str)
False
>>> isinstance(5,object)
True
>>>

类有三个特性,封装继承多态

  • 封装:封装即是把数据和方法整合在一起,通过访问控制,可以隐藏内部数据,只允许外部对象访问自己的部分数据或方法。封装保证了对象的独立性,可以防止外部程序破坏对象的内部数据,同时便于程序的维护和修改。
  • 继承:继承是面向对象的程序设计中代码重用的主要方法,继承允许使用现有类的功能,并在无需重新改写原来的类的情况下,对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。被继承的类称为基类、父类或超类,通过继承创建的新类称为子类或派生类。
  • 多态:子类具有父类的所有非私有数据和方法,以及子类自己定义的所有其他数据或方法,即子类具有两个有效类型:子类的类型及其继承的基类的类型。对象可以表现多个类型的能力称为多态性。

例如,猫,可看成是大自然封装的一个对象,其颜色可以被看到(访问数据属性),猫也可以用来抓老鼠(访问其方法),但是猫体内的细胞无法被访问,DNA复制的过程无法被访问,这些无法被直接访问的称为对象的私有属性和私有方法。

所有的橘猫都是橘猫类,除了橘猫类,还有狸花猫类、波斯猫类等,这些猫类又组成了猫种,橘猫类具有自身的特征,比如毛是橘黄色的,橘猫类又有猫种的特征,比如可以撸,猫种又有猫科特征,比如有利爪,猫科又属于哺乳纲,等等。

在自然界,对物种的划分——种-属-科-目-纲-门-界——就是层层继承关系,类的继承也是如此,而且子类既具有自身特征,又继承了父类非私有特征,这便是类的多态性。

Python中的对象在调用时,不需要检查对象是什么类型的,只关注其有什么方法有什么属性,如果方法和属性可以被使用,则对象就可以被正常调用,这种特点又称为鸭子类型,鸭子类型的含义是“如果一个东西走起来像鸭子,叫起来也像鸭子,那它就是鸭子”,即不管这个东西的本来面目,只需要它表现出鸭子的特征就行,鸭子类型虽然给程序开发带来了便利,但也需要注意对象的内部逻辑,其原本的鸭子特征可能突然暴露出猪的面目而使程序崩溃。

2、类的定义:

函数是数据和流程控制语句的结合体,类可看成是数据、流程控制语句和函数的结合体。和函数一样,类也是代码重用的一种方式

定义类以关键字class引导,基本的定义形式为:

class 类名(父类名):
    pass

类名通常采用驼峰形式,即单词的首字母大写,比如“YellowCat”。父类名是该类需继承的类,不填表示不继承其他类,不继承其他类时小括号()也可以没有,但为了编程清晰建议写上。但即便不继承其他类,也会自动继承Python内建类object。

函数dir()可查看一个类的属性和方法,包括自身属性和方法以及继承到的属性和方法,例如:

>>> class Lei():
...     a=1
...
>>> dir(Lei)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', 
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', 
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', 
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', 
'__subclasshook__', '__weakref__', 'a']
>>>

上例定义了一个类Lei,其有一个属性“a=1”,函数dir()返回了一个列表,最后一项是属性a,前面的元素都是继承自Python内建类object的属性和方法。

对象也是与外界沟通的接口,通过对象可以获取对象的属性和方法,而不必知道对象内部具体的结构。

类定义后应该先实例化再使用,类的实例化和调用函数类似,例如:

>>> class Lei():
...     a=1
...
>>> L=Lei()  #类实例化
>>> L.a
1
>>>

用L=Lei()实例化类,就可以通过L.a获取类的属性,输出a的值1。

在第4章属性引用那一节,我们知道“.”点操作符可以获取对象的属性或调用对象的方法,属性或方法的返回值可以是任意类型的对象,而对象又可能包含其他属性或方法,因此可以根据需要使用多级嵌套的“.”操作符来存取特定的属性或方法。

3、类的一般定义:

一个类中通常包括属性、构造函数(初始化函数)、方法(其他函数),例如:

class Mao():
    #属于类的常量
    legs=4      #4条腿
    body="soft" #身体软
    print('创建了常量')
    def __init__(self,name,weight=3,color="yellow"):
        #初始化参数,需要外部赋值的,与常量的区别就是用于外部赋值
        self.name=name 
        self.weight=weight
        self.color=color
        print('执行初始化函数')    
    def nature(self): #方法中调用属性或方法前需要加self.
        print(self.legs)
        print(self.name)
        self.legs+=1 #修改之后,即修改了类实例的属性,类实例调用自身方法修改了自身的属性
        self.weight+=1
        a=5
        return a
    def run(self,speed=10):
        print(speed)
        a=self.nature()
        print(a)
        self.test()
    def test(a=3):
        print(a)

上例定义了一个类Mao,定义类时类中的代码都会被执行一遍,比如对变量赋值、定义初始化函数和方法。所以当执行定义类Mao时,会输出“创建了常量”。

属性和__init__():

开始的两个属性legs、body为类中的常量,常量通常是在类内部使用,开始的两个属性可以在类中任意地方被调用。常量也被称为类属性。

初始化函数__init__()是一种特殊函数,类在实例化时会自动调用该函数完成一定的初始化工作,__init__()的第一个参数是self,其是用来接收类本身,实例化时由Python自动传入,其他的参数需要在类实例化时由外部调用者传入,有的参数带有默认值。

初始化函数中定义的一些变量,以关键字self.开头,这些变量通常用来获取函数的参数值,这些变量又称为实例属性,在类实例化时会通过函数的参数传入值。

变量由关键字self.开头,其就可以在类中其他地方调用,也可以被外部获取。self.开头的变量名字按照规范也应和初始化函数的参数名一致,不用self.开头的变量属于函数内部变量,只能在函数内部使用。

以关键字self.开头但又不接收外部传入值的变量没必要写在初始化函数里,直接作为常量写在初始化函数外部就行了。

方法:

方法是类中定义的其他函数,方法在(用self.)调用时Python会自动把类本身传给方法的第一个参数,因此方法在定义时必须指定第一个参数以用来接收类本身,通常第一个参数用self表示,而且方法在调用时第一个参数(如self)需省略,否则会出现参数被赋了多个值的错误。

方法中调用方法以self.开头,后跟方法,例如self.test(),self指代类本身,表示类调用了自身的方法,初始化函数中以self.开头的变量也都可以在类中任意地方调用。

方法中定义的变量只在该方法内部有效,例a=5,只在方法内有效,但若以self.开头定义变量,例如self.x=100,便是类中的全局变量,当方法被调用时,其便创建了该全局变量,可在类中全局有效。事实上,当实例化类时,先调用了初始化函数,其创建了类中的全局变量,因此其用self.开头定义的变量可以被类中其他方法访问。

实例化类:

实例化类和调用函数类似,例如对上例的类Mao实例化一个对象:

>>> m=Mao(name='喵喵',weight=5)#实例化时会自动调用初始化函数
执行初始化函数
>>>

由于初始化函数__init__(self,name,weight=3,color="yellow")需要接收外部传入3个参数,因此在实例化时给参数name传入'喵喵',给weight传入5,color未传入值则使用其默认值"yellow"。实例化时会自动调用初始化函数完成初始化操作,所以会输出'执行初始化函数'。

实例化的对象m可以用操作符点“.”获取类中的属性及调用类中的方法,查看m都有哪些属性和方法,可用函数dir()获取,例如:

>>> dir(m)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__form
at__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_s
ubclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__',
 '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclas
shook__', '__weakref__', 'body', 'color', 'legs', 'name', 'nature', 'run', 'test
', 'weight']
>>>

从输出信息可知,前面的属性方法都是继承自Python内建类object,后面从'body'开始的都是类m自身的属性和方法。

用“.”操作符获取类中的属性,例如:

>>> m.legs;m.weight
4
5
>>>

m.legs获取的是开始定义的常量,m.weight获取的是初始化函数中定义的变量。

用“.”操作符调用类中的方法,例如:

>>> m.nature()
4
喵喵
>>> m.legs;m.weight
5
6
>>>

方法nature()除了默认参数self没有定义其他参数,所以直接用m.nature()调用,nature()中调用了常量和初始化函数中的变量,所以打印输出4和'喵喵'。

类Mao中的常量并非是不可修改的,常量只是相对于初始化函数里的变量作的区分,初始化函数里的变量可以通过实例化时传入值,而常量无法通过传入值修改,但常量可能被类内的方法修改,nature()内便用语句self.legs+=1修改了常量legs,同时也用self.weight+=1修改了初始化变量weight,所以再用m.legs;m.weight获取属性时输出了改变后的值。nature()还定义了变量a=5,但a属于函数内部变量,只能在函数内使用。

由于文章已出版,部分内容需省略...

4、类的继承:

先看一个例子:

>>> class Kiki(Mao):
...     pass
...
>>> n=Kiki(name='喵喵',weight=5)
执行初始化函数
>>>

定义的子类Kiki(Mao),继承了上节的类Mao,子类Kiki中只有关键字pass,表示空语句,没有需要处理的其他语句,对子类实例化时,完全继承了父类Mao的初始化函数,输入参数也都一致,用dir()函数查看实例n的属性和方法,如下:

>>> dir(n)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__form
at__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_s
ubclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__',
 '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclas
shook__', '__weakref__', 'body', 'color', 'legs', 'name', 'nature', 'run', 'test
', 'weight']
>>>

从输出可以看出,子类Kiki的实例n继承了父类Mao的全部属性和方法。

我再看下一般继承的例子,如下:

>>> class Kitty(Mao):
...     body="fat" #身体肥
...     tail=1     #尾巴长度1
...     print('创建了子类常量')
...     def __init__(self,name,color="orange",cry='loud'):
...         self.name=name
...         self.color=color
...         self.cry=cry
...         print('执行子类初始化函数')
...     def nature(self): #方法中调用属性或方法前需要加self.
...         print(self.name)
...         print(self.color)
...     def yell(self,voice='旺旺'):
...         print(voice)
...
创建了子类常量
>>> k=Kitty(name='咪咪')
执行子类初始化函数
>>>

上例定义子类Kitty(Mao),继承了父类Mao,子类Kitty定义了自己的常量、初始化函数和方法,子类的常量和方法有和父类重名的,实际上初始化函数__init__()也是重名的,我们先看子类都有哪些属性和方法,如下:

>>> dir(k)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__form
at__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_s
ubclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__',
 '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclas
shook__', '__weakref__', 'body', 'color', 'cry', 'legs', 'name', 'nature', 'run'
, 'tail', 'test', 'yell']
>>>

从输出可以看到,子类继承了父类的常量legs,父类的方法run和test,子类还有自身定义的方法、常量以及初始化属性,有的与父类的常量、方法重名。

子类的实例k并没有继承父类的初始化属性weight,也没有打印“执行初始化函数”,而是打印了“执行子类初始化函数”,似乎子类并没有执行父类的初始化函数,但执行了子类自身的初始化函数。事实确实如此。

我们再看调用子类的属性,如下:

>>> k.legs;k.body
4
'fat'
>>>

从属性输出可知,子类继承了父类的legs,但输出的body值是子类的'fat'而不是父类的"soft",说明常量同名时,子类的值覆盖了父类的值。

我们再看调用子类的方法,如下:

>>> k.nature()
咪咪
orange
>>>

子类的方法nature()和父类的重名,但调用方法时执行的是子类方法中的语句,而不是父类中的语句,说明方法同名时,子类的方法覆盖了父类的方法。

我们再调用父类的方法,如下:

>>> k.run()
10
咪咪
orange
None
<__main__.Kitty object at 0x0000000002966D68>
>>>

在父类方法run()中,又调用了同名的方法nature(),从输出结果可知,nature()执行的仍是子类方法的语句,run()中又调用了父类的方法test(),其输出值是子类本身<__main__.Kitty object at 0x0000000002966D68>,说明父类方法test传入的self值是子类本身。

综上可知,类在继承时,子类会继承父类的常量和方法,但当子类的常量和方法与父类的同名时,会覆盖父类的定义,等价于子类对同名的常量和方法重新定义了。子类的初始化函数和父类的初始化函数也是同名的,同理可知,子类的初始化函数也覆盖定义了父类的初始化函数。子类方法覆盖父类方法的定义,又叫方法的重载。


网站公告

今日签到

点亮在社区的每一天
去签到