第 6 章 (1)面向对象初步

简介

	面向对象(Object oriented Programming,OOP)编程的思想主要是针对大型 软件设计而来的。
面向对象编程使程序的扩展性更强、可读性更好,使的编程可以像搭 积木一样简单。 面向对象编
程将数据和操作数据相关的方法封装到对象中,组织代码和数据的方式 更加接近人的思维,从而
大大提高了编程的效率。 
	Python 完全采用了面向对象的思想,是真正面向对象的编程语言,完全支持面向 对象的基本功
能,例如:继承、多态、封装等。 
Python 中,一切皆对象。我们在前面学习的数据类型、函数等,都是对象。 
**注:Python 支持面向过程、面向对象、函数式编程等多种编程范式。**

面向对象和面向过程区别

1、面向过程(Procedure Oriented)思维

	面向过程编程更加关注的是“程序的逻辑流程”,是一种“执行者”思维,适合编写小 规模的程序。 
面向过程思想思考问题时,我们首先思考“怎么按步骤实现?”并将步骤对应成方法, 一步一步,
最终完成。 这个适合简单任务,不需要过多协作的情况下。

2、面向对象(Object Oriented)思维

	面向对象更加关注的是“软件中对象之间的关系”,是一种“设计者”思维,适合编写 大规模的程序。 
面向对象(Object)思想更契合人的思维模式。我们首先思考的是“怎么设计这个事物?”而不是“怎么按
步骤处理这个事物”。这 就是思维方式的转变。

面向对象思考方式

	遇到复杂问题,先从问题中找名词(面向过程更多的是找动词),然后确立这些名词哪 些可以作为
类,再根据问题需求确定的类的属性和方法,确定类之间的关系。

面向对象和面向过程的总结

1. 都是解决问题的思维方式,都是代码组织的方式。 
2. 解决简单问题可以使用面向过程 
3. 解决复杂问题:宏观上使用面向对象把握,微观处理上仍然是面向过程。

对象的进化

	随着编程面临的问题越来越复杂,编程语言本身也在进化,从主要处理简单数据开始, 随着数据变多
进化“数组”; 数据类型变复杂,进化出了“结构体”; 处理数据的方式和逻辑变复杂,进化出了“对象”。
1、对象
将不同类型的数据、方法(即函数)放到一起,就是对象。

示例代码:

class Student:

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

    def say_score(self):
        print('name:{0},score:{1}'.format(self.name, self.score))


s1 = Student('Jack', 99)
s1.say_score()
=================运行结果====================
name:Jack,score:99
=================运行结果====================

类的定义

我们通过类定义数据类型的属性(数据)和方法(行为),也就是说,“类将行为和状态打 包在一起”。

注:Python 中,“一切皆对象”。类也称为“类对象”,类的实例也称为“实例对象”。

定义类的语法格式如下: 
	class 类名: 
		类体 
要点如下: 
	1. 类名必须符合“标识符”的规则;一般规定,首字母大写,多个单词使用“驼峰原则”。 
	2. 类体中我们可以定义属性和方法。 
	3. 属性用来描述数据,方法(即函数)用来描述这些数据相关的操作。

示例代码:

class Student:

    def __init__(self, name, score):   # 构造方法第一个参数必须为 self
        self.name = name   # 实例属性
        self.score = score   # 实例属性

    def say_score(self):    # 实例方法
        print('name:{0},score:{1}'.format(self.name, self.score))


s1 = Student('Jack', 99)  # s1是实例对象,自动调用__init__()方法
s1.say_score()
=================运行结果====================
name:Jack,score:99
=================运行结果====================
1、__init__构造方法和__new__方法
	类是抽象的,也称之为“对象的模板”。我们需要通过类这个模板,创建类的实例对象,然后
才能使用类定义的功能。 
	我们前面说过一个 Python 对象包含三个部分:id(identity 识别码)、type(对象类型)、
value(对象的值)。 
	现在,我们可以更进一步的说,一个 Python 对象包含如下部分: 
				1. id(identity 识别码) 
				2. type(对象类型) 
				3. value(对象的值) 
						(1) 属性(attribute) 
						(2) 方法(method) 
	创建对象,我们需要定义构造函数__init__()方法。构造方法用于执行“实例对象的初始化工 作”,
即对象创建后,初始化当前对象的相关属性,无返回值。

init()的要点如下:

1. 名称固定,必须为:__init__() 
2. 第一个参数固定,必须为:self, self 指的就是刚刚创建好的实例对象。 
3. 构造函数通常用来初始化实例对象的实例属性,如下代码就是初始化实例属性:name 和 score。
			def __init__(self, name, score):   # 构造方法第一个参数必须为 self
    			self.name = name   # 实例属性
    			self.score = score   # 实例属性
4. 通过“类名(参数列表)”来调用构造函数。调用后,将创建好的对象返回给相应的变量。
	比如:s1 = Student('Jack', 99)
5. __init__()方法:初始化创建好的对象,初始化指的是:“给实例属性赋值” 
6. __new__()方法: 用于创建对象,但我们一般无需重定义该方法。 
7. 如果我们不定义__init__方法,系统会提供一个默认的__init__方法。如果我们定义了带参的
__init__方法,系统不创建默认的__init__方法。

**注:Python 中的 self 相当于 C++中的 self 指针,JAVA 和 C#中的 this 关键字。Python 中,self必须为
构造函数的第一个参数,名字可以任意修改。但一般遵守惯例,都叫做 self。**

一、实例属性和实例方法

1、实例属性

实例属性是从属于实例对象的属性,也称为“实例变量”。他的使用有如下几个要点: 
	1. 实例属性一般在__init__()方法中通过如下代码定义: 
			self.实例属性名 = 初始值 
	2. 在本类的其他实例方法中,也是通过self进行访问: 
			self.实例属性名 
	3. 创建实例对象后,通过实例对象访问: 
			obj01 = 类名()    # 创建对象,调用__init__()初始化属性
			obj01.实例属性名 = 值     # 可以给已有属性赋值,也可以新加属性

2、实例方法

实例方法是从属于实例对象的方法。实例方法的定义格式如下: 
	def 方法名(self [, 形参列表]): 
		函数体
方法的调用格式如下: 
	对象.方法名([实参列表]) 
**要点:** 
	1. 定义实例方法时,第一个参数必须为self。和前面一样,self 指当前的实例对象。 
	2. 调用实例方法时,不需要也不能给self传参。self 由解释器自动传参。
1、 函数和方法的区别
1. 都是用来完成一个功能的语句块,本质一样。 
2. 方法调用时,通过对象来调用。方法从属于特定实例对象,普通函数没有这个特点。 
3. 直观上看,方法定义时需要传递self,函数不需要。
2、实例对象的方法调用本质:

实例对象的调用本质

3、其他操作:
1. dir(obj)可以获得对象的所有属性、方法 
2. obj.__dict__ 对象的属性字典 
3. pass 空语句 
4. isinstance(对象,类型) 判断“对象”是不是“指定类型”

二、类对象、类属性、类方法、静态方法

1、类对象

我们在执行类定义格式中,“class 类名:”。实际上,当解释器执行 class 语句时, 就会创建一个类对象。

示例代码:

# 测试类对象的生成
class Student:
    pass   # 空语句
# pass为空语句。就是表示什么都不做,只是作为一个占位符存在。当你写代码时, 遇到暂时
# 不知道往方法或者类中加入什么时,可以先用pass占位,后期再补上。


print(type(Student))
print(id(Student))
s1 = Student
s2 = s1
print(s1)
=================运行结果====================
<class 'type'>
2146572830760
<class '__main__.Student'>
=================运行结果====================
# 我们可以看到实际上生成了一个变量名就是类名“Student”的对象。我们通过赋值给新变量s2,也能
# 实现相关的调用。说明,确实创建了“类对象”。

2、类属性

类属性是从属于“类对象”的属性,也称为“类变量”。由于,类属性从属于类对象,可以 被所有实例对象共享。
类属性的定义方式:
		class 类名: 
			类变量名= 初始值 
在类中或者类的外面,我们可以通过:“类名.类变量名”来读写。

示例代码:

class Student:
    school_name = '清华' # 类属性
    stu_count = 0

    def __init__(self, name, address):
        self.name = name
        self.address = address
        Student.stu_count += 1

    def stu_info(self):
        print('{0}是{1}的学生,他的国家是{2}'.format(self.name, Student.school_name, self.address))

s1 = Student('Jack', 'America')
s1.stu_info()
s2 = Student('LiSi', 'China')
s2.stu_info()
print('一共有{0}个学生'.format(Student.stu_count))
=================运行结果====================
Jack是清华的学生,他的国家是America
LiSi是清华的学生,他的国家是China
一共有2个学生
=================运行结果====================

3、类方法

类方法是从属于“类对象”的方法。类方法通过装饰器@classmethod 来定义,格式如下: 
		@classmethod 
		def 类方法名(cls [,形参列表]) : 
			函数体 
**要点如下:** 
	1. @classmethod 必须位于方法上面一行 
	2. 第一个 cls 必须有;cls 指的就是“类对象”本身; 
	3. 调用类方法格式:“类名.类方法名(参数列表)”。 参数列表中,不需要也不能给 cls 传值。
	4. 类方法中访问实例属性和实例方法会导致错误
	5. 子类继承父类方法时,传入 cls 是子类对象,而非父类对象

示例代码:

class Student:
    school_name = '清华' # 类属性

    def __init__(self, name, score):
        self.name = name    # 实例属性
        self.score = score   # 实例属性

    def say_info(self):    # 实例方法
        print('name:{0} -- score:{1}'.format(self.name, self.score))

    @classmethod  # @classmethod 必须位于方法上面一行
    def print_school(cls):   # 类方法,cls 指的就是“类对象”本身
        print(cls.school_name) # 类方法可以访问类属性
		'''
        # print('测试类方法中访问实例属性和实例方法是否会导致错误?')
        # 类方法中访问实例属性 -> 此时明显报错
        # print('name:{0} -- score:{1}'.format(self.name, self.score))   
        # print('--------------华丽的分隔符--------------------')
        # 类方法中访问实例方法 -> 此时明显报错
        # self.say_info()
        '''

s1 = Student('Jack', 99)
s1.say_info()
s1.print_school()  # 调用类方法格式:“类名.类方法名(参数列表)”。 参数列表中,不需要也不能给 cls 传值。
==================================运行结果=====================================
name:Jack -- score:99
清华
==================================运行结果=====================================
**注:从上面的代码我们可以看到,类方法中访问实例属性和实例方法时会导致错误。**

4、静态方法

	Python 中允许定义与“类对象”无关的方法,称为“静态方法”。
 	静态方法 和在模块中定义普通函数没有区别,只不过“静态方法”放到了“类的名字空 间里面”,
 需要通过“类调用”。 静态方法通过装饰器@staticmethod 来定义,格式如下: 
 		@staticmethod 
 		def 静态方法名([形参列表]) : 
 			函数体 
 	**要点如下:** 
 		1. @staticmethod 必须位于方法上面一行 
 		2. 调用静态方法格式:“类名.静态方法名(参数列表)”。 
 		3. 静态方法中访问实例属性和实例方法会导致错误

示例代码:

class Student:
    school_name = '清华' # 类属性

    def __init__(self, name, score):
        self.name = name    # 实例属性
        self.score = score   # 实例属性

    def say_info(self):    # 实例方法
        print('name:{0} -- score:{1}'.format(self.name, self.score))

    @staticmethod  #  @staticmethod 必须位于方法上面一行
    def get_age(name, age):  # 静态方法
        print('{0}的年龄:{1}岁'.format(name, age))
		'''
        # print('测试静态方法中访问实例属性和实例方法是否会导致错误')
        # 静态方法中访问实例属性 -> 此时明显报错
        # print('name:{0} -- score:{1}'.format(self.name, self.score))
        # print('--------------华丽的分隔符--------------------')
        # 静态方法中访问实例方法 -> 此时明显报错
        # self.say_info()
        '''
        return age


s1 = Student('Jack', 99)
s1.say_info()
stu_age = s1.get_age('LiSi', 20)
print('stu_age:', stu_age)
==================================运行结果=====================================
name:Jack -- score:99
LiSi的年龄:20岁
stu_age: 20
==================================运行结果=====================================
**注:从上面的结果我们可以看到,静态方法中访问实例属性和实例方法时会导致错误。**

三、内存分析实例对象和类对象创建过程(重要)

示例代码:

class Student:
    school_name = '清华' # 类属性

    def __init__(self, name, score):
        self.name = name    # 实例属性
        self.score = score   # 实例属性

    def say_info(self):    # 实例方法
        print('name:{0} -- score:{1}'.format(self.name, self.score))

    @staticmethod  #  @staticmethod 必须位于方法上面一行
    def get_age(name, age):  # 静态方法
        print('{0}的年龄:{1}岁'.format(name, age))
        return age

s1 = Student('Jack', 99)
s1.say_info()
stu_age = s1.get_age('LiSi', 20)
print('stu_age:', stu_age)

画图分析:
内存分析实例对象和类对象创建过程

四、__del__方法(析构函数)和垃圾回收机制

	__del__方法称为“析构方法”,用于实现对象被销毁时所需的操作。比如:释放对象 占用的资源,
例如:打开的文件资源、网络连接等。 
	Python 实现自动的垃圾回收,当对象没有被引用时(引用计数为 0),由垃圾回收器 调用__del__方法。 		
	我们也可以通过 del 语句删除对象,从而保证调用__del__方法。 
	系统会自动提供__del__方法,一般不需要自定义析构方法

示例代码:

# 析构函数
class Student:

    def __del__(self):
        print('销毁对象:{0}'.format(self))

s1 = Student()
s2 = Student()
del s1
print('程序结束......')
=================运行结果====================
销毁对象:<__main__.Student object at 0x000001E7CD2CB278>
程序结束......
销毁对象:<__main__.Student object at 0x000001E7CD532BE0>
=================运行结果====================
# 通过上面代码我们可以看到程序在结束之前会调用‘析构方法’销毁没有被用到的对象‘s2’

五、__call__方法和可调用对象

定义了__call__方法的对象,称为“可调用对象”,即该对象可以像函数一样被调用。

示例代码:

# 测试__call__,可调用对象
class SalaryAccount:
    #  工资计算类

    def __call__(self, salary):
        year_salary = salary * 12
        day_salary = salary / 30
        hour_salary = day_salary / 8

        return dict(year_salary=year_salary, month_salary = salary, day_salary=day_salary, hour_salary=hour_salary)

s = SalaryAccount()
dict_salary = s(10000)  # 此时的‘s’就像函数一样调用
print('dict_salary:{0}'.format(dict_salary))
=================运行结果====================
dict_salary:{'year_salary': 120000, 'month_salary': 10000, 'day_salary': 333.3333333333333, 'hour_salary': 41.666666666666664}
=================运行结果====================

六、方法没有重载

	在其他语言中,可以定义多个重名的方法,只要保证方法签名唯一即可。方法签名包含 3 个部分:
方法名、参数数量、参数类型。
	Python 中,方法的的参数没有声明类型(调用时确定参数的类型),参数的数量也可以由可变参数
控制。因此,Python 中是没有方法的重载的。但是Python中定义一个方法即可有多种调用方式, 相当
于实现了其他语言中的方法的重载
	如果我们在类体中定义了多个重名的方法,只有最后一个方法有效。 因此建议:不要使用重名的方
法!Python 中方法没有重载。

示例代码:

# 测试Python中没有方法的重载。定义多个同名方法,只有最后一个有效
class Student:

    school_name = '北大'

    def stu_info(self):
        print('他是{0}的学生!'.format(Student.school_name))

    def stu_info(self):
        print('他是一个游客,在{0}游玩!'.format(Student.school_name))

s1 = Student()
s1.stu_info()
=================运行结果====================
他是一个游客,在北大游玩!
=================运行结果====================
# 注:可以看到只有第二个实例方法起了作用!

七、方法的动态性

Python 是动态语言,我们可以动态的为类添加新的方法,或者动态的修改类的已有的方法。

示例代码:

# 测试方法的动态性
class Student:

    def __init__(self):
        pass

    @staticmethod
    def study(self):
        print('他在学习英语!')


def play(self):
    print('他在玩{0}!'.format(self))


def learn(self):
    print('她在学{0}!'.format(self))


s1 = Student()
s1.play_game = play  # Student动态新增了play方法
s1.study = learn  # Student动态替换了study方法的功能,调用study方法时,实际上是调用了learn方法。

s1.play_game('王者荣耀')
s1.study('钢琴')  # 调用study方法时,实际上是调用了learn方法。
=================运行结果====================
他在玩王者荣耀!
她在学钢琴!
=================运行结果====================

八、@property 装饰器

"@property"可以将一个方法的调用方式变成“属性调用”。下面是一个简单的示例,体会一下这种转变:

示例代码:

# 简单测试@property
class Student:

    @property
    def learn(self):
        return '学习语数外'


s1 = Student()
print(s1.learn)  # 学习语数外 ,此时的‘learn’就相当于‘类属性’
print('type:{0}'.format(type(s1.learn)))  # type:<class 'str'>  ,可以看出此时的'learn'是一个字符串对象
return_str = s1.learn
print('return_str:',return_str)  # return_str: 学习语数外
# s1.learn()   # 报错:TypeError: 'str' object is not callable
# s1.learn = 'English'  # ‘@property’修饰的属性,如果没有加setter方法,则为只读属性。此处修改会报错:can't set attribute
=================运行结果====================
学习语数外
type:<class 'str'>
return_str: 学习语数外
=================运行结果====================

注意:

	‘@property’主要用于帮助我们处理属性的读操作、写操作。对于某一个属性,我们可以直接通过:
				s1.learn = 'English'
	如上的操作读操作、写操作。但是,这种做法不安全。比如,我需要限制学习的科目。这时候,
我们就需要通过 getter、setter 方法来处理。
# 测试@property
class Student:

    def __init__(self, name, subject):
        self.name= name
        self.__subject = subject

    @property
    def learn(self):
        return '{0}学生必学科目:语数外,选修科目:{1}'.format(self.name, self.__subject)

    @learn.setter
    def learn(self, subject):
        sub_list = ['政治', '化学', '物理', '地理', '历史']
        if subject in sub_list:
            self.__subject = subject
        else:
            print('学习的科目不在所选的列表之内!')


s1 = Student('Jack', '地理')
print(s1.learn)
s2 = Student('Rose', '物理')
print(s2.learn)
s2.learn = '钢琴'
=================运行结果====================
Jack学生必学科目:语数外,选修科目:地理
Rose学生必学科目:语数外,选修科目:物理
学习的科目不在所选的列表之内!
=================运行结果====================

属性和方法命名总结

1. _xxx:保护成员,不能用“from module import * ”导入,只有类对象和子类对象能访问这些成员。
2. __xxx__:系统定义的特殊成员
3. __xxx: 类中的私有成员,只有类对象自己能访问,子类对象也不能访问。(但,在类外部可以
通过“对象名. _类名__xxx”这种特殊方式访问。Python不存在严格意义的私有成员) 
**注:再次强调,方法和属性都遵循上面的规则。**

类编码风格

1. 类名首字母大写,多个单词之间采用驼峰原则; 
2. 实例名、模块名采用小写,多个单词之间采用下划线隔开;
3. 每个类,应紧跟“文档字符串”,说明这个类的作用;
4. 可以用空行组织代码,但不能滥用。在类中,使用一个空行隔开方法;模块中,使用两个空行隔开多个类。

学习来自:北京尚学堂高琪老师 Python 400集

发布了20 篇原创文章 · 获赞 6 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/XuanAlex/article/details/104591310