python——面向对象
编程思想:
- 面向过程:问题比较简单,可以用线性的思维解决
- 面向对象:问题较为复杂,使用简单的线性思维无法解决
两种编程思想都是解决问题的方式,并不对立,通过面向对象的方式便于我们从宏观上把握事物之间的复杂关系、便于我们分析整个系统。本质仍然使用面向过程的方式来处理。
面向对象的程序设计强调把数据和操作结合为一个不可分割的系统单位(即对象),对象的外部只需要知道它在做什么,而不需要知道怎么做。
类和对象:
类是抽象的模板,而实例是根据类创建出来的一个个具体的 ”对象“ ,每个对象都拥有相同的方法,但各自的数据可能不相同。
类:
类是多个类似事物组成的群体的总称。能够帮助我们快速理解和判断事物的性质。
1 class 类名( object ): # 类名有一个或多个单词构成,每个单词首字母大写其余小写,用下划线连接 2 pass
Tip : object 表示该类是从哪个类继承(后补充)下来的,通常,如果没有合适的继承类,就使用 object 类,这是所有类都会继承的类。
数据类型:
- 不同的数据类型属于不同的类。
- 可以使用 type() 函数来查看变量的数据类型。
对象:
对象是类的具体实例(instance),python 中一切皆对象。
类是一种数据结构,类定义数据类型的数据(属性 )和行为(方法)。对象是类的具体实体,也可以称为类的实例。需要注意,类的实例和实例对象有时不那么容易区分但又不是同一个东西。在在用class 关键字创建自定义的类对象的时,创建的是类的实例;而后者是用类对象创建实例对象。
Python中一切皆对象;类定义完成后,会在当前作用域中定义一个以类名为名字,指向类对象的变量(名字)。
属性:
Generally speaking, instance variables are for data unique to each instance and class variables are for attributes and methods shared by all instances of the class:
类数据属性:
类属性是某个类所有实例共享的属性,在创建实例对象时,每个实例都会拥有类数据属性的一个拷贝。
实例变量属性:
由类中__ init __() 方法定义的属性(变量)。通过 “ . ”来访问。在每个实例对象初始化时都会赋予独有的数据。
举几个栗子:
1 class Student: 2 3 school = JXNU # class variable shared by all instances 4 5 def __init__(self, name): 6 self.name = name # instance variable unique to each instance
1 class Student: 2 school = 'JXNU' 3 4 def __init__(self, name): 5 self.name = name 6 7 8 stu1 = Student('Zhang') 9 stu2 = Student('Li') 10 11 print(stu1.school) # =>JXNU 12 print(stu2.school) # =>JXNU 13 14 print('-'*20) 15 Student.school = 'SDJU' 16 print(stu1.school) # =>SDNU 17 print(stu2.school) # =>SDNU 18 19 print('-'*20) 20 stu1.school = 'ZJNU' 21 print(stu2.school) # =>SDNU 22 print(stu1.school) # =>ZJNU 23 24 print('-'*20) 25 Student.school = 'PJNU' 26 print(stu1.school) # =>ZJNU 27 print(stu2.school) # =>PJNU
- 所有的实例初始化时都创建了一个指向同一个类属性的指针。用类名Student修改类属性school时,实例中的school也会发生改变。而用实例 stu1 修改对应的 school 时,指针将不指向原来的类属性了,而是指向其他的变量。
- 类 Student 中,类属性 school 为所有实例共享;实例属性 name 为每个 Student 的实例独有。
- 实例属性是不能用类名访问的属性,必须由实例名 + ‘ . ' 来访问。且每个实例对应一套。
私有属性和公有属性:
通常约定以两个下划线开头而不以两个下划线结尾的变量为私有变量,其他的为公有变量。不能直接访问私有变量,但可以在方法中访问。
特殊属性:
类属性 |
含义 |
__name__ |
类的名字(字符串) |
__doc__ |
类的文档字符串 |
__bases__ |
类的所有父类组成的元组 |
__dict__ |
类的属性组成的字典 |
__module__ |
类所属的模块 |
__class__ |
类对象的类型 |
方法:
实例方法:
一般需要传递 self 参数用于调用实例的实例属性(变量),且只能由实例访问。没有矛盾和冲突点不做赘述。
类方法:
- 用 @classmethod 修饰的方法,可以用类名直接访问。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法)。
- 原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法。
静态方法:
- 用 @staticmethod 修饰的方法,可以用类名直接访问。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法)。
- 静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。
- 静态方法更像是可以写在类外的函数,但这样是为了在逻辑上与类保持一定联系,不破坏类的逻辑性。
类方法和静态方法的用途有时会混淆。
举几个栗子:
* 类方法栗子
1 情景: 2 假设我有一个学生类和一个班级类,想要实现的功能为: 3 执行班级人数增加的操作、获得班级的总人数; 4 学生类继承自班级类,每实例化一个学生,班级人数都能增加; 5 最后,我想定义一些学生,获得班级中的总人数。 6 7 class ClassTest(object): 8 __num = 0 9 10 @classmethod 11 def addNum(cls): 12 cls.__num += 1 13 14 @classmethod 15 def getNum(cls): 16 return cls.__num 17 18 # 这里我用到魔术方法__new__,主要是为了在创建实例的时候调用累加方法。 19 def __new__(self): 20 ClassTest.addNum() 21 return super(ClassTest, self).__new__(self) 22 23 24 class Student(ClassTest): 25 def __init__(self): 26 self.name = '' 27 28 a = Student() 29 b = Student() 30 print(ClassTest.getNum())
* 静态方法栗子
1 import time 2 3 class TimeTest(object): 4 def __init__(self, hour, minute, second): 5 self.hour = hour 6 self.minute = minute 7 self.second = second 8 9 @staticmethod 10 def showTime(): 11 return time.strftime("%H:%M:%S", time.localtime()) 12 13 14 print(TimeTest.showTime()) 15 t = TimeTest(2, 10, 10) 16 nowTime = t.showTime() 17 print(nowTime)
实例属性和方法的动态绑定:
话不多说,直接上代码:
1 class Student: 2 def __init__(self, name): 3 self.name = name 4 5 6 stu1 = Student('Zhang') 7 stu2 = Student('Li') 8 9 stu1.age = 20 10 print(stu1.age) # =>20 11 print(stu2.age) # AttributeError: 'Student' object has no attribute 'age' 12 13 ### 追加的属性或者方法只属于这一实例不能用于共享。 ###
面向对象的三大特征:
封装:
- 将数据(属性)和方法打包到类对象中。在方法内部对属性进行操作,在类对象的外部调用方法。这样就无需关心方法内部的复杂实现,从而将实现和使用相分离。由类和对象来实现。
- 调用封装内容可以直接用类名也可以创建一个类的实例。
继承:
- 提高代码的复用性。
- 将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个
1 class 父类(object): 2 def 父类中的方法(self): 3 # doing sth 4 5 6 class 子类(父类): # 子类继承父类,即继承了父类中的所有方法 7 pass 8 9 10 zi = 子类() # 创建子类的实例对象 11 12 zi.父类中的方法() # 执行从父类中继承的方法
- 不同于Java、C++,Python允许多继承。类继承了多个类时寻找方法的方式有两种,分别是深度优先和广度优先。经典类多继承是会按深度优先搜索方法;新式类则是按广度优先搜索。这样区分经典类和新式类呢?从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。搜索有先后顺序,所以不会产生多个同名方法的冲突。
多态:
- Pyhon不支持Java和C#这一类强类型语言中多态的写法,但是原生多态,Python 本身就是一种多态语言。Python崇尚“鸭子类型”(duck typing)。
在程序设计中,鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭的对象,并调用它的走和叫方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的走和叫方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的走和叫方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。从静态类型语言转向动态类型语言的用户通常试图添加一些静态的(在运行之前的)类型检查,从而影响了鸭子类型的益处和可伸缩性,并约束了语言的动态特性。
1 class F1: 2 pass 3 4 5 class S1(F1): 6 7 def show(self): 8 print 'S1.show' 9 10 11 class S2(F1): 12 13 def show(self): 14 print 'S2.show' 15 16 def Func(obj): 17 print obj.show() 18 19 s1_obj = S1() 20 Func(s1_obj) 21 22 s2_obj = S2() 23 Func(s2_obj) 24 25 ### Python “鸭子类型” ###
小结:
- 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用
- 类 是一个模板,模板中包装了多个“函数”供使用
- 对象,根据模板创建的实例(即:对象),实例用于调用被包装在类中的函数
- 面向对象三大特性:封装、继承和多态