第八章 Python 对象和类

一、什么是对象

在 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): #定接口函数readpassdef write(self): #定义接口函数writepassclass Txt(Interface): #文本,具体实现read和writedef read(self):print('文本数据的读取方法')def write(self):print('文本数据的读取方法')class Sata(Interface): #磁盘,具体实现read和writedef 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的twisted\internet\interface.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 = nameself.brand = brandself.date = datedef run(self):print('The {} is running'.format(self.name) )# 再定义两个子类,对父类车进行继承
class Car(Vehicle):   # 汽车类passclass 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__() 但是内部是实现一定不同。

 

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

class Vehicle():def __init__(self,name,brand,date):self.name = nameself.brand = brandself.date = datedef 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_brandself.num_wheel = num_wheeldef 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 = nameself.age = ageclass MDPerson(Person):def __init__(self,name,age):self.name = "Doctor" + nameself.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 = nameself.age = agedef 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 = leveldef 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 = nameself.age = agedef 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 = leveldef 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=yearself.month=monthself.day=day@staticmethoddef now(): #用Date.now()的形式去产生实例,该实例用的是当前时间t=time.localtime() #获取结构化的时间格式return Date(t.tm_year,t.tm_mon,t.tm_mday)        #上面的意思是 新建一个实例,实例名没有起,但是返回了,也就是,当调用 now() 时,就会得到一个新的实例     @staticmethoddef 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=yearself.month=monthself.day=day@classmethoddef now(cls): #用Date.now()的形式去产生实例,该实例用的是当前时间t=time.localtime() #获取结构化的时间格式return cls(t.tm_year,t.tm_mon,t.tm_mday)        #上面的意思是 新建一个实例,实例名没有起,但是返回了,也就是,当调用 now() 时,就会得到一个新的实例     @staticmethoda=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=yearself.mon=monself.day=day@classmethoddef 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 继承顺序

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.如果对下一个类存在两个合法的选择,选择第一个父类

转载于:https://www.cnblogs.com/xiguatian/p/6392141.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/259617.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring WebSocket初探2 (Spring WebSocket入门教程)

2019独角兽企业重金招聘Python工程师标准>>> WebSocket前端准备 SockJS&#xff1a; SockJS 是一个浏览器上运行的 JavaScript 库&#xff0c;如果浏览器不支持 WebSocket&#xff0c;该库可以模拟对 WebSocket 的支持&#xff0c;实现浏览器和 Web 服务器之间低延迟…

String类的使用 Part2

StringBuilder 类的使用 属性&#xff1a; namespace StringBuilderTest {class Program{static void Main(string[] args){StringBuilder s new StringBuilder("hello,world!");Console.WriteLine(s);//Length属性Console.WriteLine("s.Length{0}", s.Le…

JAVA项目怎么不是蓝色_解决IDEA创建maven项目时pom.xml没有变蓝的问题

如下所示&#xff1a;选中pom.xml&#xff0c;右键点击add as maven project&#xff0c;稍等片刻后就可以了补充知识&#xff1a;Idea导入maven项目不自动识别pom.xml*Idea导入maven项目不自动识别pom.xml*当在idea中导入maven项目时&#xff0c;不能自动识别pom文件解决方法&…

C# 6.0:Expression – Bodied Methods

Expression-bodied 方法是C# 6.0 中另一个能简化代码的特性。我们已经对lambda表达式将funciton和delegation关联起来的这种用法很熟悉了。Expression-bodied 将lambda 表达式的这种用法扩展到了方法上。 像下面代码所示&#xff0c;我们有一个GetTime() 方法返回一个格式化的时…

POJ 1228 Grandpa's Estate --深入理解凸包

题意&#xff1a; 判断凸包是否稳定。 解法&#xff1a; 稳定凸包每条边上至少有三个点。 这题就在于求凸包的细节了&#xff0c;求凸包有两种算法&#xff1a; 1.基于水平序的Andrew算法 2.基于极角序的Graham算法 两种算法都有一个类似下面的语句&#xff1a; for(int i0;i&…

赵强老师免费公开课第一季:Hadoop的背景起源

标签&#xff1a;免费直播课 Hadoop 大数据 赵强原创作品&#xff0c;允许转载&#xff0c;转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://51edu.blog.51cto.com/8899635/1897555 Hadoop大数据免费公开课招募啦~~~赵强…

.NET Windows服务应用程序

此文旨在记录个人对windows服务的理解以及学习记录&#xff0c;高人可以直接绕行。 1.Windows 服务体系结构 http://technet.microsoft.com/zh-cn/library/aa998749(vexchg.65).aspx Windows 服务&#xff08;也称服务应用程序&#xff09;是无论用户是否登录都运行在 Windows …

bootstrap-代码-内联代码

说明通过 <code> 标签包裹内联样式的代码片段示例<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"utf-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"view…

java类似php魔术方法_PHP与类有关的几个魔术方法

与类有关的其他魔术方法序列化与反序列化技术含义&#xff1a;序列化&#xff1a;就是将一个变量所代表的“内存”数据&#xff0c;转换为“字符串”形式并持久保存在硬盘上的一种做法。反序列化&#xff1a;就是将序列化之后保存在硬盘上的“字符串数据”&#xff0c;恢复为其…

IIS ARR设置HTTP跳转到HTTPS

GUI Version - Select the website you wish to configure- In the “Features View” panel, double click URL Rewrite You will notice there are currently no rules configured for this site. Click “Add Rules…” in the Actions menu to the right of the “Features…

AutoCAD.NET API 最新(2012)教程下载及在线视频教程DevTV

Autodek最近发布了基于最新版的AutoCAD 2012的.net API开发教程。基本内容包括&#xff1a; Overview of .NETPlugin BasicsUser InteractionDatabase fundamentalsDictionariesUser InterfaceEventsInputPoint MonitorJigs现在就可以从AutoCAD开发者中心下载&#xff0c; 看图…

install python_python install on windows 10

图 1官网2、下载安装包&#xff1a;图 2 Downloads – windows图 3选择版本图 4选择离线安装版本图 5保存下载文件—文件名自己可以修改可能有人看到windows 几个版本&#xff0c;对几个版本有迷惑的地方&#xff0c;解释图 6几个版本的差异说明文字解释&#xff1a;An e…

iOS开发常见错误

错误1&#xff1a; 1.1这种错误都是storyboard有问题 解决&#xff1a;当前storyboard的Custom Class是MJViewController&#xff0c;代码中MJViewController继承自UITableViewController。 而storyboard目前提供的是UIViewController&#xff0c;并没有为MJViewController提供…

windows bat 批处理 !vm 合并快播文件

今天简单的写了一个bat批处理文件 用来处理快播的p2p的文件&#xff0c;一般回事这样的目录 你可以下载如下代码 echo off for /r %%a in (.) do (echo %%acd %%acopy /b *.!mv test.rmvb ) pause保存为merge.bat&#xff0c;就可以执行了 代码解释&#xff1a; 扫描该目录下的…

sso接口的调用

之前一直想sso接口已经写好了&#xff0c;登录注册功能是怎么调用的呢&#xff1f;原来在登录注册的jsp页面实现的接口的调用&#xff0c;页面的校验和验证功能在jsp页面即可实现。 注册页面&#xff1a; <% page language"java" contentType"text/html; cha…

[逆向][Writeup]ISG2015 flagfinder - .NET程序逆向

这个题目同样是一道.NET的逆向题&#xff0c;.NET的逆向方法在之前的博文中已经介绍过&#xff0c;这里不做重复的说明。本题的源程序可以在我的github上下载&#xff1a;https://github.com/gsharpsh00ter/reverse 0x01 逆向 flagfinder为.NET编译的PE文件&#xff0c;用dnSpy…

java 编译原理 字符串_Java编译原理(javac)

Java中的编译分为两个部分&#xff1a;源码文件编译成字节码文件(前端编译)字节码文件被虚拟机加载以后编译成机器码(后端编译)对于开发来说接触的一般都是第一个步骤也就是源码编译成字节码文件(class文件)&#xff0c;第二个步骤开发几乎不会接触&#xff0c;因为这是虚拟机在…

sql2012一段时间无法连接报53错误

2019独角兽企业重金招聘Python工程师标准>>> 解决方案 在sqlserver网络配置下的msqlserver协议下改将ip3改成如下图所示 转载于:https://my.oschina.net/u/2511906/blog/840373

php生成图片验证码代码,PHP生成图片验证码以及点击切换的代码

这篇文章主要介绍了PHP生成图片验证码实例,同时介绍了点击切换(看不清&#xff1f;换一张)效果实现方法,需要的朋友可以参考下这里来看下效果:现在让我们来看下 PHP 代码<?php session_start();function random($len) {$srcstr "1a2s3d4f5g6hj8k9qwertyupzxcvbnm&quo…

php 字符型转变为数值,php怎么把字符串转换为数值?

php怎么把字符串转换为数值&#xff1f;下面本篇文章给大家介绍一下PHP把字符串转换为数值的方法。有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对大家有所帮助。PHP中的字符串可以很容易地转换成数值(float / int / double类型)。在大多数用例中&am…