第八章 Python 对象和类

摘要:
类就是一个模板,模板里可以包含多个函数和变量(属性),函数里实现一些功能(方法)对象则是根据模板创建的实例,通过实例化过的对象可以执行类中的函数图中没有指明的地方下面一一阐述:__init__是Python中一个特殊的函数名,是一个类的初始化实例的一个方法,用于根据类的定义去创建实例对象;self参数也是python的一个特殊参数的名称,固定格式,self实际上指的就是正在被创建的对象本身;它是在定义方法的时候的第一个参数,这是必须的。

一、什么是对象

在 Pyth 中,对象就是经过实例化的,具体可以操作的一组代码的组合;

对象一般包含数据(变量,更习惯称之为属性 attribute),也包含代码(函数,也称之为方法)

当你想要创建一个别人从来都没有创建过的新对象时,首先必须定义一个类,用以指明该类型的对象所包含的内容(属性和方法)

可以把对象想象成 名词 ,那么方法就是动词。对象代表着一个独立的实物,它的方法则定义了它是如何和其他事物互相作用的。

和模块不同,你可以同时创建许多属于同一个类的对象,但每个对象又可以有各自的独特的属性。

编程的集中方式

  • 面向过程:根据业务逻辑从上到下写代码
  • 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
  • 面向对象:对函数进行分类和封装,让开发“更快更好更强...”

面向对象编程

  • 面向对象是一种编程方式,此编程方式的实现是基于对 对象 的使用
  • 类 是一个模板,模板中包装了多个“函数”供使用(可以讲多函数中公用的变量封装到对象中)
  • 对象,根据模板创建的实例(即:对象),实例用于调用被包装在类中的函数
  • 面向对象三大特性:封装、继承和多态

二、使用 class 定义类 & 创建(实例化)一个对象

面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,所以,面向对象编程其实就是对 “类” 和 “对象” 的使用。

类是对现实世界的某些对象的共性的抽象化。比如球就是对蓝球,足球,乒乓球的抽象化,大部分都有圆形的特征,都是体育用具。

类就是一个模板,模板里可以包含多个函数和变量(属性),函数里实现一些功能(方法)

对象则是根据模板创建的实例,通过实例化过的对象可以执行类中的函数(即对象的方法)

image

图中没有指明的地方下面一一阐述:

__init__ 是Python中一个特殊的函数名,是一个类的初始化实例的一个方法,用于根据类的定义去创建实例对象;

self 参数也是python的一个特殊参数的名称,固定格式,self 实际上指的就是正在被创建的对象本身;它是在定义方法的时候的第一个参数,这是必须的。详解见下图:

image

上图中:

doctor=Person('shark') 就是实例化一个对象,这个对象就是 doctor

当创建这个对象时候,Python做了以下几件事:

  • 查看 Person 类的定义;
  • 在内存中实例化(创建)一个新的对象 doctor;
  • 调用对象的 __init__ 方法,将这个新创建的对象 doctor 作为形参 self 的实参传进去,并将 'shark' 作为形参 name 的实参传入;
  • 将 name 的值 'shark' 赋值给 这个对象 doctor的 Name 变量(即属性);
  • 将以上的操作结果返回给这个新对象;
  • 给这个新对象赋值给 dockor ;

这个新对象和其他的对象一样,可以把它当做列表、元组、字典的一个元素,也可以当做参数传给函数,或者当做函数的返回值

当创建了一个实例的对象后,可以用下面的方式访问到它的属性或者方法

>>> doctor.Name
'shark'

特性(property)

  • 什么是 property

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

下面的例子中首先定义一个Duck类,它仅包含一个 hidden_name 属性,我们不希望比人直接访问到这个属性,因此需要定义两个方法,

在每个方法中添加一个 print() 函数。最后把这些方法设置为 name 属性:

>>> class Duck():
...     def __init__(self,input_name):
...          self.hidden_name = input_name
...     def get_name(self):
...          print('inside the getter')
...          return self.hidden_name
...     def set_name(self,input_name):
...          print('inside the setter')
...          self.hidden_name = input_name
...     name = property(get_name,set_name)
... 
>>> fowl = Duck('Howard')
>>> fowl.name        # 当调用 name 属性时,get_name() 方法会自动被调用
inside the getter
'Howard'
>>> fowl.get_name()  # 当然这里可以显示调用
inside the getter
'Howard'
>>> fowl.name = 'Daffy'  # 当对 name 进行设置时,set_name() 方法会自动被调用
inside the setter
>>> fowl.set_name('Daffy')  # 当然也这里可以显示调用
inside the setter
>>> fowl.name
inside the getter
'Daffy'
>>> 

# 下面来使用Python应有的风格来实现,就是用装饰器的方式:
#@property, 用于指示getter方法
#@name.setter, 用于指示setter方法

>>> class Duck():
...     def __init__(self,input_name):
...          self.hidden_name = input_name
...     @property
...     def name(self):
...          print('inside the getter')
...          return self.hidden_name
...     @name.setter
...     def name(self,input_name):
...          print('inside the setter')
...          self.hidden_name = input_name
... 
>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'
>>> fowl.name = 'Donald'
inside the setter
>>> fowl.name
inside the getter
'Donald'
>>> 
# 这里显然就没有显示的调用了

使用 property 的一个巨大优势:如果你改变某个属性的定义,只需要在类的定义里修改即可,不需要再每一个调用处修改

下面还是接着上面的例子来操作一下

>>> fowl.hidden_name
'Donald'
>>> 
发生了什么?你本来是知道有这个属性的,还是可以直接访问到的。假如被人也知道这个属性,也同样能访问到,是不是就没有起到最初隐藏某些属性的目的了。其实在Python中有专门的方法来定义一个需要隐藏的属性,就是在变量名前加两个下划线(__),看下面我们改下过的 Duck 类
>>> class Duck():
...     def __init__(self,input_name):
...         self.__name = input_name
...     @property
...     def name(self):
...         print('inside the getter')
...         return self.__name
...     @name.setter
...     def name(self,input_name):
...         print('inside the setter')
...         self.__name = input_name
... 
>>> fowl = Duck('Howard')
>>> fowl.name            # 代码同样有效
inside the getter
'Howard'
>>> fowl.name = 'Donald'
inside the setter
>>> fowl.name
inside the getter
'Donald'
>>> fowl.__name   # 这时访问不到 __name 属性了
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Duck' object has no attribute '__name'
>>> 
其实这种命名规范并没有把属性变成真正的私有,但Python确实将它重整了,让外部的代码无法使用。其实还是可以访问到的
>>> fowl._Duck__name
'Donald'
>>> 
  • 上面的情况有时候也叫数据的封装

封装不但有上面提到的数据封装,也有关于方法的封装

为啥要用封装呢?

封装数据的主要原因是:保护隐私

封装方法的主要原因是:隔离复杂度,就是把复杂的代码逻辑实现过程封装起来,对于使用者是透明的;给用户用到的只是一个简单的接口。

封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)

第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装

>>> fowl.name

inside the getter 'Donald'

>>>

注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口

第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。

同上面提到的 隐藏属性的方法一样,在python中用双下划线的方式实现隐藏属性(设置成私有的)

在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#把fa定义成私有的,即__fa
>>> class A:
...     def __fa(self): #在定义时就变形为_A__fa
...         print('from A')
...     def test(self):
...         self.__fa() #只会与自己所在的类为准,即调用_A__fa
... 
>>> class B(A):
...     def __fa(self):
...         print('from B')
... 
>>> b=B()
>>> b.test()
from A

python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的

其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点

接口与归一化设计

继承有两种用途:

一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)

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

class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。
    def read(self): #定接口函数read
        pass

    def write(self): #定义接口函数write
        pass


class Txt(Interface): #文本,具体实现read和write
    def read(self):
        print('文本数据的读取方法')

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

class Sata(Interface): #磁盘,具体实现read和write
    def read(self):
        print('硬盘数据的读取方法')

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

class Process(All_file):
    def read(self):
        print('进程数据的读取方法')

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

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

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

归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。

在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块:

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

twisted的twistedinternetinterface.py里使用zope.interface

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

  • 为何要用接口

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

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

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

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

比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。

再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样

抽象类

  • 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)
  • 4. 抽象类与接口

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

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

三、继承

在你编写代码解决实际问题的时候,经常能找到一些已有的类,这些类可以帮你实现大部分功能,但不是全部。这时该怎么办?

对这个已有的类进行修改,但这么做,会使代码变的更加复杂,一不小心就可能破坏原来类的可用的功能。

这时就可以用到类的一个特性: 继承

类的继承就是可以从一个已有的类中衍生出以个新的类,这个新的类可以不需要再做任何代码的复制重写,就可以拥有原有类的所有属性和方法。

并且你也可以对这个新的类进行添加新属性和你需要的新方法;甚至把原来类的方法进行重写,即重写实现,并不改变原来方法的名称,这种重写方法,我们习惯称为覆盖方法,下面会一一介绍。

我们习惯把新类称为子类,把原来的类称为基类、父类或者超类。

具体实现的方法就是,在定义新类时,在类的名称后面的小括号中写入要继承的父类的名称即可

比如说,汽车(Car),自行车(Bicycle)的共性,大部分都是有品牌、出厂日期等属性(变量)的,都可以行驶(方法)。

这些都是属于车(Vehicle)这个类的属性和方法。下面我们就来演示一下如何实现继承的。

#  先定义一个父类 :车
class Vehicle():
    def __init__(self,name,brand,date):
        self.name = name
        self.brand = brand
        self.date = date

    def run(self):
        print('The {} is running'.format(self.name) )

# 再定义两个子类,对父类车进行继承
class Car(Vehicle):   # 汽车类
    pass

class Bicycle(Vehicle):  # 自行车类
    pass

# 现在都继承了父类,但是在子类中什么代码也没写,
# 但是会有服了的所以属性和方法

my_car = Car('x6','bmw','20170106')         # 实例化一个汽车对象
my_bicycle = Bicycle('roam_xr2','giant','20170305')   # 实例化一个自行车对象

# 直接通过实例化的对象对其属性和方法进行调用
print(my_car.name)
my_car.run()

print(my_bicycle.name)
my_bicycle.run()

# 输出结果
x6
The x6 is running
roam_xr2
The roam_xr2 is running

四、多态和多态性

有很多人可能会把二者混为一谈,然后就会容易乱,甚至懵逼。其实只要分开看,就会很明朗

  • 多态

多态就是一类事物有多种形态的表现形式。(一个抽象类有多个子类,因而多态的概念依赖于继承,就像上面的例子一样)

车是有多重形态存在于这个世界上的,如,自行车、汽车、火车等;

在 Python 中序列就有多重形态:字符串、列表、元组

这就不写代码了,参考上面的即可

  • 多态性

那么什么优势多态性哪?

多态性就是在现实生活中具有不同功能的多个事物(对象),也就是每个对象去实现的方法不一样,而对这些功能的叫法,即名称是一样的,比如自行和汽车都能行驶,但是自行车是通过人力为驱动力,2个轮子去行驶;而汽车是使用发动机为驱动力,至少4个轮去行驶(不要给我提摩托车!!!)。

再比如,人都会说话,但是,中国人说的是普通话,而美国人和英国人说的英语;但这并不能妨碍他们同一种语言之间的正常交流,都叫说话。

多态性在面向对象编程(OOP)中指的是不同的对象可以使用相同的函数名,但这些函数体却不一样,去实现的方法也自然不一样了;这样就可以用一个函数名调用不同内容的函数,从而去实现不同的方法。

在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

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

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

如下图总所实现的一样,字符串、列表、和元组都有一样的方法名称 __len__() 但是内部是实现一定不同。

第八章 Python 对象和类第3张

我们可以对之前车的例子进行稍微的改动一下

class Vehicle():
    def __init__(self,name,brand,date):
        self.name = name
        self.brand = brand
        self.date = date
    def run(self):
        print('The {} is running'.format(self.name) )
class Car(Vehicle):   # 汽车类
    def run(self):
        print('{}正在用四个轮子行驶,平均速度是 80km/h'.format(self.name))
class Bicycle(Vehicle):  # 自行车类
    def run(self):
        print('{}正在用两个轮子行驶,平均速度是 20km/h'.format(self.name))

my_car = Car('x6','bmw','20170106')         # 实例化一个汽车对象
my_bicycle = Bicycle('roam_xr2','giant','20170305')   # 实例化一个自行车对象

# 为了很好的展示多态性,还有再借助一个函数
def func(obj):
    obj.run()           #这里明确的调用了一个函数,函数名就是:run
#将不同的对象传进这个函数
func(my_car)
func(my_bicycle)
# 输出了不同的结果
x6正在用四个轮子行驶,平均速度是 80km/h
roam_xr2正在用两个轮子行驶,平均速度是 20km/h
  • 为什么要用多态性(多态性的好处)

其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?

1.增加了程序的灵活性和使用者的透明性或者易用性

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

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

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

# 开发者,修改代码,衍生的新子类:火车
class Train(Vehicle):
    def run(self):
        print('{}正在用多个轮子行驶,平均速度是 150km/h'.format(self.name))
# 实例化一个新的火车对象 t1
t1 = Train('hexiehao','China CSR','20000706')
# 给使用者的函数代码不变
def func(obj):
    obj.run()           
# 使用者使用同样的方式去调用
func(t1)

# 输出结果
hexiehao正在用多个轮子行驶,平均速度是 150km/h

组合

如果你想创建的子类在大多数情况下的行为都和父类相似的话,使用基础是非常不错的选择。

它们之间的关系是属于的关系。但有些时候是有组合 (composition)更加符合现实的逻辑。比如 x 含有 y ,他们之间是 has-a 的关系。

汽车是(属于)车的一种(is-a),它有(含有)四个轮子(has-a),轮子是汽车的组成部分,但不是汽车的一种。

class Vehicle():
    def __init__(self,brand):
        self.brand = brand
class Wheel():
    def __init__(self,num):
        self.num = num
class Car():
    def __init__(self,car_brand,num_wheel):
        self.car_brand = car_brand
        self.num_wheel = num_wheel
    def run(self):
        print('The %s car is running on %s wheels' %(v1.brand,w1.num))
v1 = Vehicle('BMW')
w1 = Wheel(4)
car1 = Car(v1,w1)
car1.run()
# 输出结果
The BMW car is running on 4 wheels

五、对象相关知识补充

  • 对象/实例只有一种作用:属性引用(变量引用)

>>> class Person():
...     def __init__(self,name,age):
...         self.name = name
...         self.age = age
...     def run(self):
...         print('{} is running'.format(self.name))
... 
>>> person = Person('Fudd',23) #实例化一个人的对象
# 对象调用了自己的数据属性
>>> print('name:',person.name)
name: Fudd
>>> print('age :',person.age)
age : 23
>>> 

对象本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法

>>> person.run      #对象的绑定方法
<bound method Person.run of <__main__.Person object at 0x7f80cc575c50>>

>>> Person.run    # 对象的绑定方法 run 本质就是调用类的函数 run 的功能,二者是一种绑定关系   
<function Person.run at 0x7f80cc571620>
>>> 
对象的绑定方法的特别之处在于:
obj.func()会把obj传给func的第一个参数。
也就是通常见到的在类里定义的任何函数,self 都是第一个参数,这是 Python的机制所必需的。
  • 类的初始化实例流程图

类和实例化过程及其名称空间 (2)

根据上图我们得知,再次指明了其实self,就是实例本身!你实例化时python会自动把这个实例本身通过self参数传进去

六、覆盖方法

类的覆盖方法,就上上面的例子中的 run() 方法一样,子类可以父类里的这个方法继续完全覆盖。

其实子类可以覆盖父类的所以方法,包括__init__()本身,下面就开展示一个覆盖父类__init__()的例子

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

class MDPerson(Person):
    def __init__(self,name,age):
        self.name = "Doctor" + name
        self.age = age
person = Person('Fudd',23)
doctor = MDPerson('Fudd',23)

print('name:',person.name ,'age :',person.age)
print('name:',doctor.name ,'age:',doctor.age)
# 输出内容
name: Fudd age : 23
name: DoctorFudd age: 23

七、添加新方法

添加新方法很简单

比如还拿上面的 人 这个类来说,现在衍生一个老师类,并且在这个新的类里,添加一个新的属性 老师的认证级别和一个新的方法讲课

class Person():
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def run(self):
        print('{} is running'.format(self.name))
class Teacher(Person):
    def __init__(self,name,age,level):   #添加了新的属性
        Person.__init__(self,name,age)   # 上面重构了__init__(),再要使用父类的属性,就需要这样写
        self.level = level
    def lecture(self):  # 添加的新方法
        print('%s teacher =>%s teacher is lecturing' %(self.level,self.name))
t1 = Teacher('shark',23,'Senior ')
print(t1.level)
t1.lecture()
# 输出结果
Senior 
Senior  teacher =>shark teacher is lecturing

o

八、子类里使用 super 调用父类的属性

其实对于上面的例子中已经用到了父类属性,只是方法看着有点 low,下面就演示一下,稍微高逼格的方法

class Person():
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def run(self):
        print('{} is running'.format(self.name))
class Teacher(Person):
    def __init__(self,name,age,level):
        super().__init__(name,age)  # 注意这里使用super() 替代了父类名,并且参数中没有 self        # 上面是 Pyhon3 的方式,Python2 中的方式是:super(Teacher,self).__init__(name,age)
        self.level = level
    def lecture(self):
        print('%s teacher =>%s teacher is lecturing' %(self.level,self.name))
t1 = Teacher('shark',23,'Senior ')
print(t1.level)
t1.lecture()
# 输出结果
Senior 
Senior  teacher =>shark teacher is lecturing 

九、静态方法 & 类的方法

通常情况下,在类中定义的所有函数都是对象的绑定方法。
(注意了,这里说的就是所有,跟self啥的没关系,self也只是一个再普通不过的参数而已)
在类的定义中,以self作为第一个参数的方法都是实例方法(instance method)。
这种在创建自定义类是最常用,实例方法的首个参数是 self ,当这种方法被调用时,
Python 会把调用此方法的对象作为 self 参数传入。
除此之外还有两种常见的方法:静态方法和类方法,二者是为类量身定制的,
但是实例非要使用,也不会报错,后续将介绍。

  • 静态方法

静态方法是一种普通函数,位于类定义的命名空间中,不会对任何实例类型进行操作,

python为我们内置了函数staticmethod来把类中的函数定义成静态方法

class Foo:
    def spam(x,y,z): #类中的一个函数,千万不要懵逼,self和x啥的没有不同都是参数名
        print(x,y,z)
    spam=staticmethod(spam) #把spam函数做成静态方法基于之前所学装饰器的知识,@staticmethod 等同于spam=staticmethod(spam),于是

class Foo:
    @staticmethod #装饰器
    def spam(x,y,z):
        print(x,y,z)使用演示

print(type(Foo.spam)) #类型本质就是函数
Foo.spam(1,2,3) #调用函数应该有几个参数就传几个参数

f1=Foo()
f1.spam(3,3,3) #实例也可以使用,但通常静态方法都是给类用的,实例在使用时丧失了自动传值的机制

'''
<class 'function'>
1 2 3
3 3 3
'''

应用场景:编写类时需要采用很多不同的方式来创建实例,而我们只有一个__init__函数,此时静态方法就派上用场了

class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day
    @staticmethod
    def now(): #用Date.now()的形式去产生实例,该实例用的是当前时间
        t=time.localtime() #获取结构化的时间格式
        return Date(t.tm_year,t.tm_mon,t.tm_mday)        #上面的意思是 新建一个实例,实例名没有起,但是返回了,也就是,当调用 now() 时,就会得到一个新的实例     @staticmethod
    def tomorrow():#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间
        t=time.localtime(time.time()+86400)
        return Date(t.tm_year,t.tm_mon,t.tm_mday)

a=Date('1987',11,27) #自己定义时间
b=Date.now() #采用当前时间
c=Date.tomorrow() #采用明天的时间

print(a.year,a.month,a.day)
print(b.year,b.month,b.day)
print(c.year,c.month,c.day)
  • 类方法

    与之实例方法相对,类方法(class method)会作用于整个类,
    在类定义的内部,用装饰器 @classmethod 修饰的方法都是类方法。
    与实例方法类似,类方法第一个参数是类本身。在Python中,这个参数常被写作 clsimport time

class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day
    @classmethod
    def now(cls): #用Date.now()的形式去产生实例,该实例用的是当前时间
        t=time.localtime() #获取结构化的时间格式
        return cls(t.tm_year,t.tm_mon,t.tm_mday)        #上面的意思是 新建一个实例,实例名没有起,但是返回了,也就是,当调用 now() 时,就会得到一个新的实例     @staticmethod

a=Date(1983,'07',28) #自己定义时间
b=Date.now() #采用当前时间

print(a.year,a.month,a.day) # 输出自定义的实例化时间
print(b.year,b.month,b.day) # 输出调用类方法 now()即当前的时间的实例化时间
# 输出结果
1983 07 28
2017 3 5 
  • 静态方法和类方法的区别

1. staticmethod 与类只是名义上的归属关系
2. classmethod 只能访问类变量,不能访问实例变量

  • 应用场景
import time
class Date:
    def __init__(self,year,mon,day):
        self.year=year
        self.mon=mon
        self.day=day

    @classmethod
    def now(cls):
        t=time.localtime()
        return cls(t.tm_year,t.tm_mon,t.tm_mday)
    # 下面是使用静态方法去实现的情况,请注意看后面的图片说明
    # @staticmethod    
    # def now():
    #     t=time.localtime()
    #     return Date(t.tm_year,t.tm_mon,t.tm_mday)

class EuroDate(Date):
    def __str__(self):
        return 'year:%s mon:%s day:%s' %(self.year,self.mon,self.day)

e = EuroDate.now()
print(e) # 打印这个对象,当打印这个对象是会自动调用对象的类的 __str__()方法

QQ截图20170305224609

类的继承顺序和原理

  • 首先来说一下什么是经典类和新式类

pyth2.x中默认都是经典类,Python3.x不是默认,是都是新式类

# Python3.6 定义类
>>> class A:
...     pass
... 
>>> class B():
...     pass
... 
>>> class C(object):
...     pass
... 
# 打印类以及其类型
>>> print(A,type(A))
<class '__main__.A'> <class 'type'>
>>> print(B,type(B))
<class '__main__.B'> <class 'type'>
>>> print(C,type(C))
<class '__main__.C'> <class 'type'>
>>> 

# Python2.7 定义类
>>> class A:
...     pass
... 
>>> class B():
...     pass
... 
>>> class C(object):  # 在Python2.x 中定义新式类必须显式的定义
...     pass
... 
# 打印类以及其类型
>>> print(A,type(A))
(<class __main__.A at 0x7f5e2344c258>, <type 'classobj'>)   # 经典类
>>> print(B,type(B))
(<class __main__.B at 0x7f5e2344c2c0>, <type 'classobj'>)
>>> print(C,type(C))
(<class '__main__.C'>, <type 'type'>)  # 新式类
>>> 
  • 1 继承顺序

第八章 Python 对象和类第7张

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中才分新式类与经典类
  • 继承原理(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.如果对下一个类存在两个合法的选择,选择第一个父类

免责声明:文章转载自《第八章 Python 对象和类》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇docker学习笔记(1)Springboot配置https访问下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

C++ 继承、派生、多态

继承就是在一个已存在的类的基础上建立一个新的类。 已存在的类称为基类,又称父类;新建立类称为派生类,又称为子类。 继承允许我们依据另一个类来定义一个类,不需要重新编写一部分的数据成员和成员函数,达到了重用代码功能和提高执行效率的效果。 基类与派生类 一个类可以派生自多个类,从多个基类继承数据和函数。我们使用一个类派生列表来指定基类。 class deriv...

Android编译系统环境过程初始化分析【转】

本文转载自:http://blog.csdn.net/luoshengyang/article/details/18928789  Android源代码在编译之前,要先对编译环境进行初始化,其中最主要就是指定编译的类型和目标设备的型号。Android的编译类型主要有eng、userdebug和user三种,而支持的目标设备型号则是不确定的,它们由当前的源码...

【python标准库学习】thread,threading(一)多线程的介绍和使用

在单个程序中我们经常用多线程来处理不同的工作,尤其是有的工作需要等,那么我们会新建一个线程去等然后执行某些操作,当做完事后线程退出被回收。当一个程序运行时,就会有一个进程被系统所创建,同时也会有一个线程运行,这个线程就是主线程main,在主线程中所创建的新的线程都是子线程,子线程通常都是做一些辅助的事。python中提供了thread和threading两...

转载:堆栈溢出(Stack overflow)问题

一,堆栈溢出堆栈溢出就是不顾堆栈中分配的局部数据块大小(在栈中分配的局部数据块大小和局部变量的声明的大小有关),向该数据块写入了过多的数据,导致数据越界,结果覆盖了老的堆栈数据(包括函数的返回地址)。 或者解释为在长字符串中嵌入一段代码,并将过程的返回地址覆盖为这段代码的地址,这样当过程返回时,程序就转而开始执行这段自编的代码了.这东西很像病毒。 基础知识...

Spring框架第一天(搭建项目)

Spring框架 1.简介 1.1 Spring是什么 一个开源的框架,是JavaEE开源框架 Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核。 提供了展现层 Spring...

1. python跨目录调用模块

快速镜像安装第三方库 :  pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy (三方库名字)         同目录下,我们可以直接调用模块,但是不同目录下调用模块却有些许不同。 假设我们的目录结构如下,我们需要在test.py中调用calultater.py:  一. 通过绝对路...