[Python自学] day-6 (编程范式、类、继承)

一、编程范式

    编程:程序员用特定的语法、数据结构和算法告诉计算机如何执行任务的过程。实现任务有很多不同的方式,根据编程方式的特点进行归纳总结出来的编程方式类别,就叫编程范式。大多数语言只支持一种编程范式,当然也有可以同时支持多种编程范式。
  • 面向过程编程:
    • 使用一系列指令来告诉计算机一步一步完成任务。是一种Top-down language。如果只是写一些简单的脚本,用面向过程是极好的,但如果你要处理的任务是复杂的,切需要不断迭代和维护,那还是使用面向对象最方便。
  • 面向对象编程:
    • OOP编程利用类和对象来创建各种模型来实现对真是世界的描述,使用面向对象的原因一方面是因为它可以使程序的维护和扩展变得简单,并且可以大大提高程序开发效率,另外基于面向对象的程序可以使他人更容易理解,是团队开发变得容易。
  • 函数式编程
 
    生活经验总结:
    世界万物,皆可分类
    世界万物,皆为对象
    只要是对象,肯定属于某个种类
    只要是对象,就肯定有属性

二、类和对象

    Class类:
    类是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象都具备的属性,共同的方法。
 
    Object对象:
    一个对象即使一个类的实例化后的实例,一个类必须经过实例化后才可以在程序中调用。一个类可以实例化多个对象,每个对象亦可以有不同的属性。
 
    Encapsulation封装:
    类是一个胶囊或容器,里面包含着类的属性和方法。
 
    Inheritance继承:
    一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承。
 
    Polymorphism多态:
    

1.类定义

类定义:
class Dog:
def __init__(self,name):
    self.name = name
def bulk(self):
    print("%s is bulking,wang wang wang!" % self.name)
print(Dog)    #返回<class '__main__.Dog'>
构造函数:在实例化时做一些类的初始化的工作
class Dog:
  def __init__(self,name):    #构造函数
      self.name = name
  
  def bulk(self):
      print("%s is bulking,wang wang wang!" % self.name)
实例化干了写什么:(参考 六-4-1h 讲解)
    在实例化的时候,dog1 = Dog("Alex")
    将Alex作为名字传递给类定义,类调用__init__()来初始化实例化对象。但是为什么init有self参数,是以为在执行dog1 = Dog("Alex")时,dog1也作为变量传递给了init构造函数,实际上就是self参数。所以在init中使用self.name=name来赋值,实际上就是dog1.name = name。
类的方法始终保存在类的内存中,而不是在对象内存中,对象调用公共的函数。
    dog1.bulk()相当于Dog.bulk(dog1),所以类中的每个方法都又一个self参数用于接受对象变量(dog1)。

2.实例变量和类变量

实例变量和类变量:
class Dog:
    n = 123    #类变量
    def __init__(self,name):
        self.name = name    #self.name为实例变量,也叫静态属性
    def bulk(self):    #类方法,也叫动态属性
        print("%s is bulking,wang wang wang!" % self.name) 
区别:
    在没有实例化时,就可以直接使用类名来调用。也可以通过实例调用。
print(Dog.n)    #通过类调用
print(dog1.n)    #通过实例调用
注意:如果类变量和实例变量名称一样,则使用实例变量调用时,实例变量先在自己实例中寻找该变量,再去类中寻找。如图,class Dog类中有类变量n和name,用name时,先在自己的内存中查到name,输出alex。如果使用类名Dog来调用name,则先查到位于类中的类变量name,直接打印123实例变量name和age。当dog1调。当dog1调用n时,查到自己内存发现没有n,则查找类中是否存在n,结果打印123。
    
为对象增加删除属性:
class Dog:
    n = 123
    def __init__(self,name):
        self.name = name
    def bulk(self):
        print("%s is bulking,wang wang wang!" % self.name)

dog1 = Dog("Alex")
dog2 = Dog("Jack")
dog1.age = 10    #给dog1对象增加属性age
del dog1.name    #删除dog1的name属性

print(Dog.name)
print(dog1.name)    #报错,找不到该属性。如果类变量里也有一个name,则会打印类变量name
print(dog1.age)
使用对象修改类变量:只有通过类名修改类变量才是真的修改。使用对象修改都只是在对象内存中新添加了一个与类属性同名的对象属性。
class Dog:
    n = 123
    def __init__(self,name):
        self.name = name
    def bulk(self):
        print("%s is bulking,wang wang wang!" % self.name)

dog1 = Dog("Alex")
dog2 = Dog("Jack")

dog1.n = 234    #实际上是在dog1对象中添加了一个新的变量n=234,类中的变量n未改变

print(Dog.n)    #输出123
print(dog1.n)    #输出234
print(dog2.n)    #输出123
对象修改类变量(list):
class Dog:
    n_list = []
    def __init__(self,name):
       self.name = name
    def bulk(self):
        print("%s is bulking,wang wang wang!" % self.name)
    
dog1 = Dog("Alex")
dog2 = Dog("Jack")

dog1.n_list.append("from dog1")    #都操作的类属性的内存地址
dog2.n_list.append("from dog2")    #都操作的类属性的内存地址
Dog.n_list.append("from Dog")    #都操作的类属性的内存地址

print(dog1.n_list)    #输出["from dog1","from dog2","from Dog"]
print(dog2.n_list)    #输出["from dog1","from dog2","from Dog"]
print(Dog.n_list)    #输出["from dog1","from dog2","from Dog"]
类属性的用途:类属性是大家公用的属性,例如国籍,十四亿人都是中国国籍,但是有改国籍的人,则在对象中添加一个国籍变量。类属性用于节省内存空间。
class Person:
    nationality = "中国"    #大部分人国籍
    def __init__(self,name,age,id):
        self.name = name
        self.age = age
        self.id = id
    def sleep(self):
        print("sleep")

p1 = Person("Leo",32,"513902")
p2 = Person("Alex",22,"511027")
p3 = Person("Acala",40,"400910")
p3.nationality = "日本"    #p3修改国籍

print(p3.nationality)    #输出"日本"

3.析构函数

析构函数:
  在实例释放、销毁的时候执行的,通常用于做一些收尾工作,如关闭一些数据库连接、关闭打开的临时文件。
import time
class Person:
    def __init__(self):
        print("构造函数")
    def __del__(self):
        print("析构函数")

p = Person()    #执行构造函数
time.sleep(2)    
del p    #执行析构函数

4.私有方法和私有属性

    私有方法和私有属性:
    只能自己访问,别人不能访问。
class Person:
    def __init__(self,name,money):
        self.name = name
        self.__money = money    #__money私有属性

p = Person("Leo",10000)
print(p.name)
print(p.__money)    #无法直接访问

class Person: def __init__(self,name,money): self.name = name self.__money = money def buy_something(self,price): self.__money -= price print("cost RMB %d" % price) def get_money(self): #通过内部访问私有变量,提供给外部一个专用方法 return self.__money p = Person("Leo",10000) p.buy_something(4000) print(p.name) print(p.get_money()) #获取私有属性的值

二、类的继承(Inheritance)

1.继承的作用  

主要作用:减少代码量

class People:

    def __init__(self,name,age):
        self.name = name
        self.age = age

    def eat(self):
        print("{name} is eating",format(name=self.name))

    def sleep(self):
        print("{name} is sleeping".format(name=self.name))


class Man(People):  #继承People
    pass

man1 = Man("Leo",32)
man1.sleep()

2.子类添加新方法

class Man(People):  #继承People
    def swim(self):
        print("{name} is swimming".format(name=self.name))

man2 = Man("Lion",23)
man2.swim()

3.子类覆盖父类方法

class Man(People):  #继承People
    def swim(self):
        print("{name} is swimming".format(name=self.name))

    def sleep(self):
        print("{name} is not sleeping".format(name=self.name))

man1 = Man("Leo",32)
man1.sleep()

4.子类方法调用父类方法

class Man(People):  #继承People
    def swim(self):
        print("{name} is swimming".format(name=self.name))

    def sleep(self,time):
        People.sleep(self)   #使用父类名调用sleep,但必须将该对象self传入
        print("{name} is not sleeping...{time}".format(name=self.name,time=time))

man1 = Man("Leo",32)
man1.sleep(30)

另一种调用父类方法的写法:

class Man(People):  #继承People
    def __init__(self,name,age,money):
        #People.__init__(self,name,age)
        super(Man,self).__init__(name,age)  #另一种写法,好处是如果修改了父类名,不需要在每个调用父类方法的地方修改
        self.money=money

5.子类构造函数传更多参数

class Man(People):  #继承People
    def __init__(self,name,age,money):  #新增money参数
        People.__init__(self,name,age)  #调用父类构造函数
        self.money=money

    def get_money(self):
        print("{name} have {money} yuan!".format(name=self.name,money=self.money))

    def swim(self):
        print("{name} is swimming".format(name=self.name))

    def sleep(self,time):
        People.sleep(self)   #使用父类名调用sleep,但必须将该对象传入
        print("{name} is not sleeping...{time}".format(name=self.name,time=time))

man1 = Man("Leo",32,5000)
man1.get_money()

6.经典类和新式类

经典类和新式类:

class People:  #经典类
    pass

class People(object):  #新式类,必须这么写,前面的super方法也是新式类中的用法。
    pass

7.多继承

经典类和新式类的多继承是不同的。

class People(object):
    def __init__(self,name,age):
        self.name=name
        self.age=age
        print("init of People")
    def eat(self):
        print("%s is eating!" % (self.name))

class Relation(object):
    def make_friend(self,obj):
        print("%s make friend with %s" % (self.name,obj.name))

class Man(People,Relation):  #当Relation类中没有构造函数时,这里多继承的顺序调换没关系,因为super找最前面一个类的构造方法。同样找到的是People的构造方法
    def __init__(self,name,age):
        super(Man,self).__init__(name,age)

m1 = Man("Leo",32)

解释:多继承是按类的顺序来继承的,所以在Man(People,Relation)中,两个父类的位置是有影响的。假如Relation没有构造函数,People和Relation位置调换,子类中的super也能找到People的构造方法并执行。如果Relation中有自己的构造方法,并且参数不是name,age(例如def __init__(self,noney)),那就会报错,因为在初始化m1的时候传入的name和age与Relation的构造函数要求参数不一致。

class People(object):
    def __init__(self,name,age):
        self.name=name
        self.age=age
        print("init of People")
    def eat(self):
        print("%s is eating!" % (self.name))

class Relation(object):
    def __init__(self,n1,n2):   #用n1,n2来接收name,age
        print(self.name)
        
    def make_friend(self,obj):
        print("%s make friend with %s" % (self.name,obj.name))

class Man(Relation,People):  #先继承Relation,后继承People
    def __init__(self,name,age):
        super(Man,self).__init__(name,age)

m1 = Man("Leo",32)

解释:上面代码中Relation类有构造函数,并通过n1和n2来接收m1初始化时传入的"leo"和32。参数能够接收后,打印self.name报错,说'Man' object has no attribute 'name'。因为继承顺序是Relation->People,在Relation中调用self.name时,name还没有被初始化。

总结:多继承中,继承顺序是从左到右,如果子类没有构造方法,则会自动去寻找父类构造方法,从第一个继承的父类开始找,并且执行找到的第一个父类构造方法(参数必须与该父类构造方法参数一致,否则报错)。如果子类中有构造方法,并在构造方法中显示调用父类构造方法,执行方法同前者。

8.新式类和经典类区别

构造函数查找顺序问题:

有A B C D四个类,继承关系如下代码:

class A:
    pass
class B(A):
    pass
class C(A):
    pass
class D(B,C):
    pass

  继承关系如下图所示:

  通过测试,Python3.0+的构造函数查找顺序默认如下:(先找同级,都没有的话再找上一级,叫广度优先。Python3上经典和新式都是按广度优先的)

  Python2.0+的查询顺序是默认深度优先,如下图:(经典类默认深度优先,新式类按广度优先)

 总结:这个广度优先或深度优先顺序叫做MRO(方法解析顺序)。这个顺序不光使用与构造函数(构造函数只是经常自动寻找父类构造函数而已),还适用于所有想使用super调用父类方法的场景(例如我们手动调用父类方法)。

 

###

posted @ 2018-02-26 12:25  风间悠香  阅读(301)  评论(0编辑  收藏  举报