前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python升级之路( Lv6 ) 面向对象基础

Python升级之路( Lv6 ) 面向对象基础

作者头像
时间静止不是简史
发布2022-06-12 14:33:07
5210
发布2022-06-12 14:33:07
举报
文章被收录于专栏:Java探索之路

Python系列文章目录

第一章 Python 入门

第二章 Python基本概念

第三章 序列

第四章 控制语句

第五章 函数

第六章 面向对象基础


面向对象基础


前言

在本章, 首先将会学习面向过程和面向对象之间的区别和联系 然后, 学习类(类对象, 类属性, 类方法, 静态方法), 对象(演变, 内存结构), 属性和方法(命名规范, 方法相关特性, 实例属性和实例方法, 私有属性和私有方法), 常用的类的方法(init, call, del, @property)以及关键字None 的相关介绍 最后, 进行实操来巩固所学习到的知识


一、面向对象是什么

1. 面向对象简介

Python完全采用了面向对象的思想,是真正面向对象的编程语言,完全支持面向对象的基本功能,例如:继承、多态、封装等。 Python中,一切皆对象. 我们在前面学习的数据类型、函数等,都是对象.

需要注意:

  • 面向对象(Object oriented Programming,OOP)编程的思想主要是针对大型软件设计而来的
  • 面向对象编程将数据和操作数据相关的方法封装到对象中,组织代码和数据的方式更加接近人的思维,从而大大提高了编程的效率
  • Python支持面向过程、面向对象、函数式编程等多种编程范式

2. 面向对象和面向过程

面向对象

为了思考面向是什么, 我们不妨考虑"如何设计事物". 比如造车, 我们首先应该不去思考怎么按步骤造车 , 而是会去思考车怎么设计. 这就是面向过程到面向对象的转变 通过对相关知识的学习, 我们知道汽车主要是由轮胎, 发动机, 车壳, 座椅, 玻璃等几个零件组成 为了能够高效的实现汽车生产, 我们可以找轮胎厂完成制造轮胎的步骤, 发动机厂完成制造发动机的步骤… 这样, 汽车的各项零件就会并行的去制造, 最终去进行组装, 大大提高了汽车生产效率 但是, 具体到轮胎厂的一个流水线操作,仍然是有步骤的,还是离不开执行者、离不开面向过程

注意:

  • 面向对象是以功能来划分问题,而不是以步骤解决。
  • 面向对象可以帮助我们从宏观上把握、从整体上分析整个系统. 但是,具体到实现部分的微观操作(就是一个个方法),仍然需要面向过程的思路去处理
  • 不要把面向过程和面向对象对立起来. 他们是相辅相成的. 面向对象离不开面向过程

面向过程

面向过程适合简单、不需要协作的事务,重点关注如何执行。 面向过程时,我们首先思考 “ 怎么按步骤实现?” 比如如何开车?我们很容易就列出实现步骤:

注意:

  • 面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数
  • 当我们思考比较复杂的设计任务时,比如“如何造车?”,就会发现列出1234这样的步骤是不大可能的 那是因为造车太复杂,需要很多协作才能完成. 此时面向对象思想就应运而生了

面向对象和面向过程总结

区别

  • 面向过程是一种“执行者思维",解决简单问题可以使用面向过程
  • 面向对象是一种“设计者思维”,解决复杂、需要协作的问题可以使用面向对象

联系

  • 都是解决问题的思维方式
  • 都是代码组织的方式
  • 面向对象离不开面向过程: 宏观上:通过面向对象进行整体设计 微观上:执行和处理数据,仍然是面向过程

二、类, 对象, 属性, 方法

1. 类

类:我们叫做class . 类一般看做是对象的抽象 我们把对象比作一个“饼干”,类就是制造这个饼干的“模具”. 类和对象的关系如下图所示:

类编码规范

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

类对象, 类属性, 类方法, 静态方法

类对象

前面讲的类定义格式为, class 类名: 实际上, 当解释器执行class 语句时, 就会创建一个类对象

代码语言:javascript
复制
class Student:
    pass  # 空语句


print(type(Student))    # <class 'type'>
print(id(Student))      # 1772468512176
Stu2 = Student
s1 = Stu2()
print(s1)   # <__main__.Student object at 0x0000019CAF6E3D90>

注意:

  • 我们可以看到实际上生成了一个变量名就是类名 Student 的对象.
  • 我们通过赋值给新变量 Stu2 , 也能实现相关的调用. 说明, 确实创建了“类对象”
类属性

类属性是从属于“类对象”的属性, 也称为“类变量”. 由于类属性从属于类对象, 因此可以被所有实例对象共享类属性的定义方式:

代码语言:javascript
复制
class 类名: 
 类变量名= 初始值

注意:

  • 在类中或者类的外面, 我们可以通过: 类名.类变量名 来读写

实操代码

代码语言:javascript
复制
class Student:
    company = "sxd"  # 类属性


s1 = Student()
print(s1.company)
类方法

类方法是从属于“类对象”的方法. 类方法通过装饰器 @classmethod 来定义

格式如下:

代码语言:javascript
复制
@classmethod
def 类方法名(cls [, 形参列表]):
 	方法体

注意:

  1. @classmethod 必须位于方法上面一行
  2. 第一个 cls 必须有; cls 指的就是“类对象”本身
  3. 调用类方法格式: 类名.类方法名(参数列表) . 参数列表中, 不需要也不能给 cls 传值
  4. 类方法中访问实例属性和实例方法会导致错误
  5. 子类继承父类方法时, 传入 cls 是子类对象, 而非父类对象

实操代码

代码语言:javascript
复制
class Student:
    company = 'sxd'

    @classmethod
    def printCompany(cls):
        print(cls.company)


Student.printCompany()
静态方法

Python中允许定义与“类对象”无关的方法, 称为“静态方法” “静态方法”和在模块中定义普通函数没有区别, 只不过“静态方法”放到了“类的名字空间里面”, 需要通过“类调用” 静态方法通过装饰器@staticmethod来定义,

格式如下

代码语言:javascript
复制
@staticmethod
def 静态方法名([形参列表]):
 方法体

注意

  1. @staticmethod 必须位于方法上面一行
  2. 调用静态方法格式: 类名.静态方法名(参数列表)
  3. 静态方法中访问实例属性和实例方法会导致错误

实操代码

代码语言:javascript
复制
class Student:
    company = "sxd"

    @staticmethod
    def add(a, b):
        print("{0}+{1}={2}".format(a, b, a+b))
        return a + b


Student.add(111, 222)

2. 对象

对象:我们叫做object , instance (实例). 以后我们说某个类的对象,某个类的实例. 是一样的意思. 对象一般看做是类的具体体现

对象的演变

随着编程面临的问题越来越复杂,编程语言本身也在进化. 从主要处理简单数据开始,随着数据变多进化“数组”; 数据类型变复杂,进化出了“结构体”; 处理数据的方式和逻辑变复杂,进化出了“对象”.

演变过程如下图所示:

对象完整内存结构

类是抽象的,也称之为“对象的模板”. 而对象则是类的具体实现 我们需要通过类这个模板,创建类的实例对象,然后才能使用类定义的功能。 我们前面说过一个Python对象包含三个部分: id (identity识别码)、type (对象类型)、value (对象的值)

现在我们可以更进一步的说,一个Python对象包含如下部分:标识, 类型, 属性和方法. 完整对象内存结构图如下图所示:

3. 属性和方法

属性和方法命名规范总结

相较于Java 使用了关键字对类和方法以及属性进行访问权限控制. Python 使用下划线来对类, 对象, 属性和方法进行权限控制

具体区别如下:

  1. _xxx (方法名称前一个下划线):保护成员, 不能用 from module import * 导入, 只有类对象和子类对象能访问这些成员.
  2. __xxx__ (方法名称前后两个下划线):系统定义的特殊成员
  3. __xxx (方法名称前两个下划线): 类中的私有成员, 只有类对象自己能访问, 子类对象也不能访问. (但, 在类外部可以通过 对象名. _类名__xxx 这种特殊方式访问. Python不存在严格意义的私有成员)

实例属性

实例属性是从属于实例对象的属性, 也称为“实例变量”.

注意:

实例方法

实例方法是从属于实例对象的方法.

实例方法的定义格式如下:

代码语言:javascript
复制
def 方法名(self [, 形参列表]): 
	函数体

方法的调用格式如下:

代码语言:javascript
复制
对象.方法名([实参列表])

注意:

  • 定义实例方法时, 第一个参数必须为 self . 和前面一样, self 指当前的实例对象
  • 调用实例方法时, 不需要也不能给 self 传参. self 由解释器自动传参

实例对象和实例方法实操代码:

代码语言:javascript
复制
class Student:

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

    def say_score(self):  # 实例方法
        print(self.name, "的分数是: ", self.score)


s1 = Student("时间静止", 18)
s1.say_score()	# 调用示例方法
print(s1.age)
s1.salary = 3000  # s1对象增加salary属性
s2 = Student("不是简史", 81)
s2.say_score()
print(s2.age)

其他操作

  • dir(obj) 可以获得对象的所有属性、方法. 返回的是一个字典类型的数据
  • obj.__dict__ 对象的属性字典
  • pass 空语句. 用于定义空白逻辑的函数/方法
  • isinstance(对象,类型) 判断“对象”是不是“指定类型”

函数和方法的区别

都是用来完成一个功能的语句块, 本质一样. 方法调用时, 通过对象来调用. 方法从属于特定实例对象, 普通函数没有这个特点 直观上看, 方法定义时需要传递self, 函数不需要

方法没有重载

如果我们在类体中定义了多个重名的方法, 只有最后一个方法有效. 建议: 不要使用重名的方法!Python中方法没有重载. 这一点和java大不相同

注意:

  • 在其他一些语言(比如: Java)中, 可以定义多个重名的方法, 只要保证方法签名唯一即可. 方法签名包含3个部分: 方法名、参数数量、参数类型.
  • Python中, 方法的的参数没有声明类型(调用时确定参数的类型), 参数的数量也可以由可变参数控制. 因此, Python中是没有方法的重载的
  • Python中没有方法的重载. 定义多个同名方法, 只有最后一个有效

实操代码

代码语言:javascript
复制
class Person:
    def make_money(self):
        print("我要挣钱啦~")

    def make_money(self, money):
        print("我要挣钱啦~今天挣到{0}元".format(money))


p1 = Person()
# p1.make_money()  # 不带参, 报: TypeError: Person.makeMoney() missing 1 required positional argument: 'money'
p1.make_money(666)

方法的动态性

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

实操代码

我们可以看到, Person 动态的新增了 play_game 方法, 以及用 work2 替换了 work 方法

代码语言:javascript
复制
class Person:
    def work(self):
        print("努力上班!")


def play_game(self):  # 静态方法
    print("玩游戏")


def work_plus(s):
    print("上班好好工作, 下班好好玩")


Person.play = play_game
Person.work = work_plus
p = Person()
p.play()
p.work()

私有属性和私有方法

Python对于类的成员没有严格的访问控制限制, 这与其他面向对象语言有区别.

关于私有属性和私有方法, 有如下要点:

  1. 通常我们约定, 两个下划线开头的属性是私有的(private). 其他为公共的(public).
  2. 类内部可以访问私有属性(方法)
  3. 类外部不能直接访问私有属性(方法)
  4. 类外部可以通过 _类名__私有属性(方法)名 ”访问私有属性(方法)

注意:

  • 方法本质上也是属性!只不过是可以通过()执行而已. 所以, 此处讲的私有属性和公有属性, 也同时讲解了私有方法和公有方法的用法.
  • 如下测试中, 同时也包含了私有方法和公有方法的例子.

实操代码

代码语言:javascript
复制
class Employee:
    __company = "sxd"  # 私有.通过dir查到_Employee__company

    def __init__(self, name, age):
        self.name = name
        self.__age = age

    def get_company_name(self):
        print("我的公司是: ", Employee.__company)  # 类内部可以直接访问私有属性
        print("{0}的年龄是{1}".format(self.name, self.__age))

    def __work(self):  # 私有实例方法, 通过dir可查到_Employee__work
        print("工作是人类进步的阶梯")


p1 = Employee("timePause", 18)

print(dir(Employee))
print(Employee._Employee__company)  # 在将company属性私有化后,直接调用报错, 通过dir可以查到属性_Employee__company,这种方式调用输出sxd, 从而验证规则4

# print(p1.age)   # 在age属性未私有化时, 这样可以访问
print(p1._Employee__age)  # 在age属性未私有化后, 通过这种方式可以直接访问到私有属性_Employee__age.  也验证了规则4
p1.get_company_name()

# p1.work()   # 未私有之前可以调用, 私有后调用报错: AttributeError: 'Employee' object has no attribute 'work'
p1._Employee__work()  # 私有之后. 通过这种方式可以直接访问私有熟悉_Employee__work. 也验证了规则4

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

我们以下面代码为例,分析整个创建过程:

代码语言:javascript
复制
class Student:
    company = "sxd"  # 类属性
    count = 0  # 类属性

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

    def say_score(self):  # 实例方法
        print("我的公司是: ", Student.company)
        print(self.name, "的分数是: ", self.score)


s1 = Student("时间静止", 18)    # s1是实例对象, 自动调用__init__()方法
s2 = Student("不是简史", 81)
s1.say_score()
print('一共创建{0}个Student对象'.format(Student.count))
print("s1的引用:", id(s1))
print("s2的引用:", id(s2))
print("Student的引用:", id(Student))

实例对象和类对象创建过程如下图所示

实例对象的方法调用本质

5. 默认的类方法

init 构造方法和__new__ 方法

初始化对象, 我们需要定义构造函数 __init__() 方法 构造方法用于执行“实例对象的初始化工作”, 即对象创建后, 初始化当前对象的相关属性, 无返回值 如果将对象比作一座房子, 则构造方法(__init__)是负责初始化(装修) , 不是建对象(房子)

__init__() 的要点如下(约等于Java的构造方法):

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

注意:

  • Python中的 self 相当于C++中的 self指针 , JAVA和C#中的 this 关键字
  • Python中, self 必须为构造函数的第一个参数, 名字可以任意修改, 但一般都叫做 self. 他的作用是指向当前对象的本身/当前对象的引用

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

__del__() 称为“析构方法”, 用于实现对象被销毁时所需的操作. 比如: 释放对象占用的资源, 例如: 打开的文件资源、网络连接等

注意:

  1. Python实现自动的垃圾回收, 当对象没有被引用时(引用计数为0), 由垃圾回收器调用 __del__()
  2. 我们也可以通过 del语句 删除对象, 从而保证调用 __del__()
  3. 系统会自动提供 __del__方法 , 一般不需要自定义析构方法.

实操代码

代码语言:javascript
复制
class Person:
    def __del__(self):
        print("销毁对象: {0}".format(self))


p1 = Person()
p2 = Person()
del p2
print("程序结束")

__call__ 方法和可调用对象

凡是可以将 () 直接应用到自身并执行, 都称为可调用对象.

注意:

  1. 可调用对象包括自定义的函数、Python 内置函数、以及本节所讲的实例对象.
  2. 定义了 __call__() 的对象, 称为“可调用对象”, 即该对象可以像函数一样被调用.
  3. 该方法使得实例对象可以像调用普通函数那样, 以“对象名()”的形式使用

实操代码

代码语言:javascript
复制
def f1():
    print("f1")


f1()  # 本质上也是调用了 __call__()方法


class Car:
    def __call__(self, age, money):
        print("调用__call__()方法")
        print("车龄:{0}, 金额:{1}".format(age, money))


f2 = Car()
f2(3, 289000)  # 像函数那样调用, 本质上也是调用了 __call__()

@property 装饰器

@property 装饰器相当于在Java中实现了对属性的get 方法

注意:

  1. @property 可以将一个方法的调用方式变成“属性调用”.
  2. @property 主要用于帮助我们处理属性的读操作、写操作
  3. 对于某一个属性, 我们可以直接通过:emp1.salary = 30000 如上的操作读操作、写操作 但是这种做法不安全. 比如, 我需要限制薪水必须为 1-10000 的数字. 这时候我们就需要通过使用装饰器 @property 来处理.

实操代码

代码语言:javascript
复制
class Employee:

    def __init__(self, name, salary):
        self.name = name
        self.__salary = salary

    @property  # 相当于salary属性的getter方法
    def salary(self):
        print("月薪为{0}, 年薪为{1}".format(self.__salary, 12 * self.__salary))
        return self.__salary

    @salary.setter
    def salary(self, salary):  # 相当于salary属性的setter方法
        if 0 < salary < 1000000:
            self.__salary = salary
        else:
            print("薪水录入错误! 只能在0-1000000之间")


emp1 = Employee("时间静止", 8848.48)
print(emp1.salary)
emp1.salary = 12580
print(emp1.salary)

6. 关于None和判断的总结

None是什么

  • 与C和JAVA不同, python中是没有 NULL 的, 取而代之的是 None . None 是一个特殊的常量, 表示变量没有指向任何对象.
  • 在Python中, None 本身实际上也是对象, 有自己的类型 NoneType .
  • 你可以将 None 赋值给任何变量, 但我们不能创建 NoneType 类型的对象

实操代码

代码语言:javascript
复制
obj = None
obj2 = None
print(type(None))
print(id(None))
print(id(obj))
print(id(obj2))
"""输出结果
<class 'NoneType'>
140706372487160
140706372487160
140706372487160
"""

None和其他类型的比较

  • None不是False, None不是0, None不是空字符串. None和任何其他的数据类型比较永远返回False
  • if语句判断时,空列表[]、空字典{}、空元组()、0等一系列代表空和无的对象会被转换成False
  • == 和 is 判断时, 空列表、空字符串不会自动转成 False

实操代码

代码语言:javascript
复制
# None不是False, None不是0, None不是空字符串. None和任何其他的数据类型比较永远返回False. 
a = None
if a is None and a == None:
    print("a是None")  # 会执行
if a == False or a == 0:
    print("None不等于False")  # 不会被打印

a = []
b = ()
c = {}
d = ""
e = 0
f = None
# if语句判断时,空列表[]、空字典{}、空元组()、0等一系列代表空和无的对象会被转换成False
if (not a) and (not b) and (not c) and (not d) and (not e) and (not f):
    print("if判断时, 空列表[]、空字符串、0、None等代表空和无的对象会被转换成False")
# == 和 is 判断时, 空列表、空字符串不会自动转成 False
if a == False or d == False:
    print("==时, 空列表、空字符串不是False!")  # 不会执行
if(e == False):
    print("==时, 0会转成False")

三、实操作业

设计一个名为MyRectangle的矩形类来表示矩形。这个类包含:

  1. 左上角顶点的坐标:x,y
  2. 宽度和高度:width、height
  3. 构造方法:传入x,y,width,height。如果(x,y)不传则默认是0,如果width 和height不传,则默认是100.
  4. 定义一个getArea() 计算面积的方法
  5. 定义一个getPerimeter(),计算周长的方法
  6. 定义一个draw()方法,使用海龟绘图绘制出这个矩形

考察核心

  1. 构造方法与属性的默认值设置
  2. self 的使用
  3. 海龟绘图包的使用

实操代码

代码语言:javascript
复制
import turtle


class MyRectangle:
    """
    设计一个名为MyRectangle的矩形类来表示矩形. 这个类包含:
    (1) 左上角顶点的坐标:x, y
    (2) 宽度和高度:width、height
    (3) 构造方法:传入x, y, width, height. 如果(x, y)不传
    则默认是0, 如果width 和height不传, 则默认是100.
    (4) 定义一个getArea() 计算面积的方法
    (5) 定义一个getPerimeter(), 计算周长的方法
    (6) 定义一个draw()方法, 使用海龟绘图绘制出这个矩形
    """
    __x = 0
    __y = 0

    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

    def get_area(self):
        print("该矩形面积为:", self.width * self.height)

    def get_perimeter(self):
        print("该矩形周长为:", (self.width + self.height) * 2)

    def draw(self):
        turtle.showturtle()
        turtle.write("画出该矩形, 左上角坐标为:({0},{1}), 宽为:{2}, 高为:{3}".format(self.x, self.y, self.width, self.height))
        turtle.speed(1)
        turtle.goto(self.x, self.y)
        turtle.goto(self.x, self.y - self.height)
        turtle.goto(self.x + self.width, self.y - self.height)
        turtle.goto(self.x + self.width, self.y)
        turtle.goto(self.x, self.y)


m1 = MyRectangle(0, 100, 50, 100)
m1.get_area()
m1.get_perimeter()
m1.draw()
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Python系列文章目录
    • 面向对象基础
    • 前言
    • 一、面向对象是什么
      • 1. 面向对象简介
        • 2. 面向对象和面向过程
          • 面向对象
          • 面向过程
          • 面向对象和面向过程总结
      • 二、类, 对象, 属性, 方法
        • 1. 类
          • 类编码规范
          • 类对象, 类属性, 类方法, 静态方法
        • 2. 对象
          • 对象的演变
          • 对象完整内存结构
        • 3. 属性和方法
          • 属性和方法命名规范总结
          • 实例属性
          • 实例方法
          • 其他操作
          • 函数和方法的区别
          • 方法没有重载
          • 方法的动态性
          • 私有属性和私有方法
        • 4. 实例对象和类对象创建过程内存分析(重要)
          • 实例对象的方法调用本质
        • 5. 默认的类方法
          • init 构造方法和__new__ 方法
          • __del__方法(析构函数)和垃圾回收机制
          • __call__ 方法和可调用对象
          • @property 装饰器
        • 6. 关于None和判断的总结
          • None是什么
          • None和其他类型的比较
      • 三、实操作业
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档