首页 > 技术文章 > python类

wangzicheng 2018-08-25 12:42 原文

创建和使用类

 

创建Dog类,赋予dog蹲下(sit())和打滚(roll_over())的能力

class Dog():
    def __init__(self,name,age):
        self.name=name;
        self.age=age;
    def sit(self):
        print(self.name.title()+" is now sitting.")
    def roll_over(self):
        print(self.name.title()+" rolled over!")

 

方法_init_()是一个特殊的方法,相当于构造方法,每当创建新实例时,Python都会自动运行它
_init_()中的形参self必不可少,还必须位于其他形参的前面。创建实例时不用给self传递值
后面的两个方法由于不需要额外的信息,因此只有一个形参self,变量都有前缀self,以self为前缀的变量
可供类中的所有方法使用。self.name=name获取存储在形参name中的值,并将其存储到变量name中,然后
该变量被关联到当前创建的实例。

根据类创建实例
my_dog=Dog('stupy',3)
这里使用上面的Dog类创建了一个名为‘stupy’、年龄为3的my_dog实例
访问属性
my_dog.name获取名字,my_dog.age获取年龄
调用方法
my_dog.sit()     my_dog.roll_over()

给属性指定默认值
比如上例默认为公狗,则在_init_()函数中创建一个性别属性并设置初始值,self.sex='gong'

修改属性的值
1.直接修改属性的值
接上例,使用句点表示法来直接访问并设置小狗的属性name。
my_dog.name='clever'

2.通过方法修改属性的值
相当于java中的set方法
例:def update_name(self,newname)
              self.name=newname
       my_dog.update_name('wuwu')
这样就无需直接访问属性,而可以将值传递给一个方法,由它在内部进行更新

继承
如下例:
  1. class Car():  
        def __init__(self, make, model, year):  
            self.make = make  
            self.model = model  
            self.year = year  
            self.odometer_reading = 0  
        def get_descriptive_name(self):  
            long_name = str(self.year) + ' ' + self.make + ' ' + self.model  
            return long_name.title()  
        def read_odometer(self):  
            print("This car has " + str(self.odometer_reading) + " miles on it.")  
        def update_odometer(self, mileage):  
            if mileage >= self.odometer_reading:  
                 self.odometer_reading = mileage  
            else:  
                print("You can't roll back an odometer!")  
        def increment_odometer(self, miles):  
            self.odometer_reading += miles  
    class ElectricCar(Car):  
        def __init__(self, make, model, year):  
            super().__init__(make, model, year)  
    my_tesla = ElectricCar('tesla', 'model s', 2016)  
    print(my_tesla.get_descriptive_name())

     

      

     

定义子类时,在括号里指定父类的名称。super()帮助Python将父类和子类关联起来,调用父类的方法_init_(),让ElectricCar实例包含父类的所有属性。
上面ElectricCar实例的行为与Car实例一样,现在定义电动汽车特有的属性和方法
在_init_()函数內定义一个电动汽车特有的属性,self.battery_size=70
编写一个打印电瓶描述的方法

def describe_battery(self):
     print("This car has a " + str(self.battery_size) + "-kWh battery.")

 

派生
在子类中重新定义父类的方法,运行时将忽略父类中的方法,转而运行子类中重新定义的方法。
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值
接口

接口提取了一群类共同的函数,可以把接口当做一个函数的集合。

然后让子类去实现接口中的函数。

这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。

归一化的好处在于:

1. 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。

2. 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合

在python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念

可以借助第三方模块:

http://pypi.python.org/pypi/zope.interface

twisted的twisted\internet\interface.py里使用zope.interface

文档https://zopeinterface.readthedocs.io/en/latest/

设计模式:https://github.com/faif/python-patterns

 

也可以使用继承: 

继承的两种用途

一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。

二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能

 

抽象类

1 什么是抽象类

    与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类本质也是类,只是加了装饰器的类,它的特殊之处在于只能被继承,不能被实例化,

2 为什么要有抽象类

    如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。

  比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。

    从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。

  从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案

3. 在python中实现抽象类

import abc #利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):
    all_type='file'
    @abc.abstractmethod #定义抽象方法,无需实现功能
    def read(self):
        '子类必须定义读功能'
        pass

    @abc.abstractmethod #定义抽象方法,无需实现功能
    def write(self):
        '子类必须定义写功能'
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #报错,子类没有定义抽象方法

class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的读取方法')

class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的读取方法')

class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('进程数据的读取方法')

    def write(self):
        print('进程数据的读取方法')

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

 

1 继承顺序

在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)

如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先

class A(object):
    def test(self):
        print('from A')

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性

#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类

 

2 继承原理(python如何实现的继承)

python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如

>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类

子类中调用父类的方法

方法一:指名道姓,即父类名.父类方法()

class Vehicle: #定义交通工具类
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print('开动啦...')

class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        Vehicle.__init__(self,name,speed,load,power)
        self.line=line

    def run(self):
        print('地铁%s号线欢迎您' %self.line)
        Vehicle.run(self)

line13=Subway('中国地铁','180m/s','1000人/箱','',13)
line13.run()

 

方法二:super(),推荐用这种方法

class Vehicle: #定义交通工具类
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print('开动啦...')

class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
        super().__init__(name,speed,load,power)
        self.line=line

    def run(self):
        print('地铁%s号线欢迎您' %self.line)
        super(Subway,self).run()

class Mobike(Vehicle):#摩拜单车
    pass

line13=Subway('中国地铁','180m/s','1000人/箱','',13)
line13.run()

 

多态与多态性

多态

多态指的是一类事物有多种形态

动物有多种形态:人,狗,猪

多态性

多态性是指在不考虑实例类型的情况下使用实例

在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同

多态性分为静态多态性和动态多态性

静态多态性:如任何类型都可以用运算符+进行运算

动态多态性:如下

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更进一步,我们可以定义一个统一的接口来使用
def func(obj):      #obj这个参数没有类型限制,可以传入不同类型的值
    obj.talk()      #调用的逻辑都一样,执行的结果不一样

 

为什么要用多态性(多态性的好处):

1.增加了程序的灵活性

  以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

2.增加了程序额可扩展性

  通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用   

class Cat(Animal): #属于动物的另外一种形态:猫
    def talk(self):
    print('say miao')

def func(animal): #对于使用者来说,自己的代码根本无需改动
    animal.talk()

cat1=Cat() #实例出一只猫
func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能

 

推荐阅读