Python之路(第二十三篇) 面向对象初级:静态属性、静态方法、类方法

时间:2022-05-22 19:30:43

 

一、静态属性

静态属性相当于数据属性。

用@property语法糖装饰器将类的函数属性变成可以不用加括号直接的类似数据属性。

可以封装逻辑,让用户感觉是在调用一个普通的数据属性。

例子

  class Room:
      def __init__(self,name,length,width,heigh):
          self.name = name
          self.length = length
          self.width = width
          self.heigh = heigh
  ​
      @property   #通过使用property语法糖装饰器,使类的函数属性返回类似数据属性,不用使用括号。
      def cacl_area(self):
          return self.length*self.width
  ​
  ​
  r1 = Room("nick",20,30,10)
  print(r1.cacl_area)

  

 

 

二、类方法

需求:类不通过实例(对象)直接调用类的函数属性。

类无法直接调用自己的函数属性,需要借助实例对象。

例子

  
  class Room:
      def __init__(self,name,length,width,heigh):
          self.name = name
          self.length = length
          self.width = width
          self.heigh = heigh
  ​
      def cacl_area(self):
          return self.length*self.width
  ​
      def tell_info(self):
          print("-----")
  ​
  Room.tell_info()  #这样直接用类调用函数属性会出错,需要实例对象
  r1=Room("nick",10,10,10)
  r1.tell_info()

  

 

需要用@classmethod ,将函数做成类方法,执行类方法时,自动将调用该方法的复制给cls

例子

  
  class Room:
      def __init__(self,name,length,width,heigh):
          self.name = name
          self.length = length
          self.width = width
          self.heigh = heigh
  ​
      def cacl_area(self):
          return self.length*self.width
  ​
      @classmethod
      def tell_info(cls,msg):
          print(cls)
          print("-----",msg)
  ​
  Room.tell_info("hello")  # 相当于Room.tell_info(Room,"hello"),classmethod自动将类传入了

  

 

三、静态方法

用@staticmethod 语法糖实现,不和具体的实例对象进行绑定,类可以调用实例也可以调用

例子

  
  class Room:
      def __init__(self,name,length,width,heigh):
          self.name = name
          self.length = length
          self.width = width
          self.heigh = heigh
  ​
      def cacl_area(self):
          return self.length*self.width
  ​
      @staticmethod
      def test(a,b,c):
          print(a,b,c)
  ​
  ​
  Room.test("hello","world","!!!")  #静态方法类可以调用,实例也可以调用
  r1 = Room("nick",20,30,10)
  r1.test("nick","hello","!!!")

  

 

输出结果

  
  hello world !!!
  nick hello !!!

  

 

静态方法只是名义上归属类管理,不能使用类变量和实例变量,是类的工具包。

 

 

补充:

绑定方法说明

绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入)

绑定到对象的方法:没有被任何装饰器装饰的方法。

绑定给类的方法(classmethod)

非绑定方法:用staticmethod装饰器装饰的方法:不与类或对象绑定,类和对象都可以调用,但是没有自动传值。

 

四、组合

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

用途:1、不同的类做关联

2、不同的类组合成大的整体。

例子1

  
  class School:
      def __init__(self,name,addr,type):
          self.name = name
          self.addr = addr
          self.type = type
  ​
  ​
      def enrol_students(self):
          print("%s正在招生"%self.name)
  ​
  ​
      def exam(self):
          print("%s正在考试"%self.name)
  ​
  ​
  class Course:
  ​
  ​
      def __init__(self,name,price,period,school):
          self.name = name
          self.price = price
          self.period = period
          self.school = school
  ​
  ​
  s1 = School("清华","北京","公立")
  s2 = School("北大","北京","公立")
  ​
  school_dic = {
      "1":s1,
      "2":s2
  }
  ​
  msg = """
  请选择学校
  1、清华
  2、北大
  ​
  """
  while True:
      print(msg)
      school_choice = input(">>>: ").strip()
      name = input("课程名:").strip()
      price = input("课程费用:").strip()
      period = input("课程周期:").strip()
      school_obj = school_dic[school_choice]
      c1 = Course(name,price,period,school_obj)  #直接传入学校的对象,不同类之间建立了关联
      print("%s属于%s"%(c1.name,c1.school.name))

  

 

 

例子2

  
  class Head():
      pass
  ​
  ​
  class Trunk:
      pass
  ​
  ​
  class Hand():
      pass
  ​
  ​
  class Foot():
      pass
  ​
  ​
  class People():
      def __init__(self, name, age, gerder, ):
          self.name = name
          self.age = age
          self.gerder = gerder
          self.head = Head()  # 人的头继承了head类这个类得实例,这里是一个对象
          self.trunk = Trunk()
          self.hand = Hand()
          self.foot = Foot()
  ​
  ​
  p1 = People("nick", '18', "man")
  print(p1.__dict__)  # 打印得到一个属性head ->>> key的值对应的head实例

  

 

例子3

  

  from math import pi
  ​
  class Circle:
      def __init__(self,radius):
          self.radius = radius
  ​
  ​
      def cacl_area(self):
          print("圆的面积是%s"%(pi*self.radius*self.radius))
          return pi*self.radius*self.radius
  ​
  ​
  class Ring:
      def __init__(self,outside_radius,inside_radius):
          self.outside_circle = Circle(outside_radius)  #获取外圈圆的对象
          self.inside_circle = Circle(inside_radius)    #获取内圈圆的对象
  ​
  ​
      def area(self):
          ring_area = self.outside_circle.cacl_area() - self.inside_circle.cacl_area()
          print("圆环的面积是%s"%ring_area)
  ​
  ​
  r1 = Ring(10,8)
  r1.area()

  

 

 

 

 

五、继承

什么是继承?

继承指的是类与类之间的关系,是一种什么“是”什么的关系,继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。

子类会“”遗传”父类的属性,从而解决代码重用问题。

例子1

  
  class Parent_class:
      money = 10
      def __init__(self,name):
          self.name = name
  ​
      def make_money(self):
          print("make good money")
  ​
  ​
  class Sub_class(Parent_class):
      pass
  ​
  s1 = Sub_class("nick")  #这里需要传入一个参数,虽然子类没有默认的__init__方法,子类找不到去父类找,父类需要一个参数
  print(s1.money) #这里直接调用父类的数据属性
  s1.make_money() #调用父类的函数属性

  

分析:子类继承了父类的数据属性和函数属性

派生

子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

 

例子2

  
  class Parent_class:
      money = 10
      def __init__(self,name):
          self.name = name
  ​
      def make_money(self):
          print("make good money")
  ​
  ​
  class Sub_class(Parent_class):
      money =  10000
  ​
  s1 = Sub_class("nick")
  print(s1.money) #这里子类有自己的与父类同名的属性,从子类的属性开始找,自己有就用自己的数据,不再用父类的数据
  print(Parent_class.money) #父类的属性任然存在,而不是被子类的同名属性覆盖了
  s1.make_money() #调用父类的函数属性

  

分析:子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。父类的属性任然存在。

 

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

  
  class Parent_class1:
      money = 10
      def __init__(self,name):
          self.name = name
  ​
      def make_money(self):
          print("from Parent_class1")
          print("make good money")
  ​
  ​
  class Sub_class(Parent_class1,):
      money =  10000
  ​
      def make_money(self):  #在自己这里定义新的make_money,不再使用父类的make_money,且不会影响父类
          Parent_class1.make_money(self)   #如果想调用父类的同名函数属性,这里要自己手写self。
          print("from Sub_class")
  ​
  ​
  s1 = Sub_class("nick")
  s1.make_money()

  

属性查找

属性引用查找,会先从对象中找,然后去类中找,然后再去父类中找...直到最*的父类。

例子

  
  class Parent_class1:
      def f1(self):
          print("from Parent_class f1")
  ​
      def f2(self):
          self.f1()
          print("from Parent_class f2")
  ​
  ​
  class Sub_class(Parent_class1,):
      def f1(self):
          print("from Sub_class")
  ​
  ​
  s1 = Sub_class()
  s1.f2()

  

输出结果

  
  from Sub_class
  from Parent_class f2

  

 

分析:对象s1自己的类本身没有f2()函数属性,到父类找,找到了执行self.f1(),这里的self是对象s1,所以s1.f1()函数属性执行结果是输出from Sub_class,然后执行print("from Parent_class f2")输出from Parent_class f2

 

查看继承

__base__、__bases__方法

  
  class Parent_class1:
      money = 10
      def __init__(self,name):
          self.name = name
  ​
      def make_money(self):
          print("make good money")
  ​
  class Parent_class2:
      pass
  ​
  class Sub_class(Parent_class1,Parent_class2):
      money =  10000
  ​
  ​
  print(Sub_class.__base__)
  print(Sub_class.__bases__)
  ​

  

输出结果

  
  <class '__main__.Parent_class1'>
  (<class '__main__.Parent_class1'>, <class '__main__.Parent_class2'>)

  

分析:

  
#__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类

 

什么时候用继承?

  • 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好。

    通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。

    比如老师是人,学生是人。

  • 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。

    用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3。

 

 

六、接口继承和统一化设计

 

继承同时具有两种含义

  • 继承基类的方法,并且做出自己的改变或者扩展(解决代码重用问题)。

  • 声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法。

继承的第二种含义非常重要。它又叫“接口继承”。

封装:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,或者叫接口。

真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在。

 

总结

 (1)实践中继承的第一种含义意义并不是很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。

 (2)继承的第二种含义非常重要。接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”,这在程序设计上叫做归一化。

归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕。

​ 接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数。归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。

 

抽象类

抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。

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

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

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

  从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。

 

抽象类与接口

抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。

*抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计*

 

接口继承的使用例子(抽象类)

 

  
  import abc
  class All_file(metaclass=abc.ABCMeta):   # 接口类(基类),接口类的方法不需要具体实现,也不需要实例化
  ​
      @abc.abstractmethod    #引入第三方模块abc,调用abc下的abstractmethod,强制子类下必须有read方法
      def read(self):
          pass
  ​
      @abc.abstractmethod   #强制子类下必须有write方法
      def write(self):
          pass
  ​
  ​
  class Text_file(All_file):
      def read(self):
          print("from text-file read")
  ​
      def write(self):
          print("from text-file write")
  ​
  ​
  class Disk(All_file):
      def read(self):
          print("from disk read")
  ​
      def write(self):
          print("from disk write")
  ​
  class Cdrom(All_file):
      def read(self):
          print("from cdrom read")
  ​
      def write(self):
          print("from cdrom write")
  ​
  f1 = Disk()
  f1.read()

  

 

七、继承顺序

经典类与新式类

在python2中,新式类基类继承object类,经典类不继承任何类

  
默认都是经典类,只有显式继承了object才是新式类,即:
class Person(object):pass 新式类写法
class Person():pass 经典类写法
class Person:pass 经典类写法

 

在Python 3.x中取消了经典类,默认都是新式类,并且不必显式的继承object

  
class Person(object):pass
class Person():pass
class Person:pass
三种写法并无区别

 

继承顺序

Python中子类可以同时继承多个父类,如A(B,C,D)

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

深度优先和广度优先

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

Python之路(第二十三篇) 面向对象初级:静态属性、静态方法、类方法

 Python之路(第二十三篇) 面向对象初级:静态属性、静态方法、类方法

 

Python之路(第二十三篇) 面向对象初级:静态属性、静态方法、类方法

 

 

 

Python之路(第二十三篇) 面向对象初级:静态属性、静态方法、类方法

例子

 

  
  class G:
      def test(self):
          print("from G")
  ​
  class F(G):
      def test(self):
          print("from F")
  ​
  class E(G):
      def test(self):
          print("from E")
  ​
  ​
  class D(G):
      def test(self):
          print("from D")
  ​
  class C(F):
      def test(self):
          print("from C")
  ​
  class B(E):
      def test(self):
          print("from B")
      pass
  ​
  class A(B,C,D):
      def test(self):
          print("from A")
      # pass
  ​
  c1 = A()
  c1.test()
  ​
  # python3环境中,新式类继承的寻找的顺序是A-B-E-C-F-D-G
  #在python2环境中,这个为经典类,继承寻找的顺序为A-B-E-G-C-F-D
  #python3中统一都是新式类,全部按照广度优先继承
  #pyhon2中才分新式类与经典类
  ​

  

 

 

继承原理

对于定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。

C3线性化算法实际上就是合并所有父类的MRO列表并遵循如下三条准则:

  • 子类会先于父类被检查

  • 多个父类会根据它们在列表中的顺序被检查

  • 如果对下一个类存在两个合法的选择,选择第一个父类

例子(python3)

  
  class G:
      def test(self):
          print("from G")
  ​
  class F(G):
      def test(self):
          print("from F")
  ​
  class E(G):
      def test(self):
          print("from E")
  ​
  class D(G):
      def test(self):
          print("from D")
  ​
  class C(F):
      def test(self):
          print("from C")
  ​
  class B(E):
      def test(self):
          print("from B")
      pass
  ​
  class A(B,C,D):
      def test(self):
          print("from A")
  ​
  print(A.mro())

  

输出结果

 
 [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]

  

分析:#python2中的新式类也和python3一样,都有mro方法,python2中经典类没有mro方法。

 

 

八、子类调用父类方法

在子类派生出的新方法中,往往需要重用父类的方法,我们有两种方式实现

第一种方式:直接调用

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

  
  class Vehicle:  #定义父类
  ​
      def __init__(self,name,speed,power):
          self.name = name
          self.speed = speed
          self.power = power
  ​
      def run(self):
          print("running !")
  ​
  class Subway(Vehicle):  #定义子类
  ​
      def __init__(self,name,speed,power,line):
          Vehicle.__init__(self,name,speed,power)  #这里调用父类的构造方法,直接用父类名.构造方法名(),
                                                   # 需要自己传入self和其他父类的参数
          self.line = line
  ​
      def run(self):
          Vehicle.run(self)  #这里调用父类的run()方法也是采用父类名.父类方法()的方式,也是需要自己传入self
          print("%s %s号线开动了"%(self.name,self.line))
  ​
  s = Subway("深圳地铁","70km/h","电力",1)
  s.run()

  

 

 

第二种方式:super()

用super().父类方法(),这种方式不用写父类的名字,不用再传入self这个参数了。

  
  class Vehicle:  #定义父类
  ​
      def __init__(self,name,speed,power):
          self.name = name
          self.speed = speed
          self.power = power
  ​
      def run(self):
          print("running !")
  ​
  class Subway(Vehicle):  #定义子类
  ​
      def __init__(self,name,speed,power,line):
          super().__init__(name,speed,power)  #这里用super().父类的方法名调用父类的方法,这种方式不用传入self参数
          self.line = line
  ​
      def run(self):
          super().run()  #这里调用父类的run()方法也是采用super().父类方法()的方式,不需要自己传入self参数
          print("%s %s号线开动了"%(self.name,self.line))
  ​
  s = Subway("深圳地铁","70km/h","电力",1)
  s.run()