视频加载失败

Python 面向对象1:类与对象

4801 字
24 分钟
Python 面向对象1:类与对象

面向对象1<类与对象>#

温馨提示

转载请标注来源哦~~

通过本章的学习,你将学习到编程语言的核心——面向对象(此对象非彼对象哈)的概念和使用方法。文章内容来自站主大学时期的课程笔记。

Python是一门面向对象的编程语言

所谓的面向对象的语言,简单理解就是语言中的所有操作都是通过对象来进行的

  • 面向过程的编程的语言

    面向过程指将我们的程序的逻辑分解为一个一个的步骤, 通过对每个步骤的抽象,来完成程序

    这种编程方式,符合我们人类的思维,编写起来相对比较简单

    但是这种方式编写代码的往往只适用于一个功能, 如果要在实现别的功能,即使功能相差极小,也往往要重新编写代码, 所以它可复用性比较低,并且难于维护

  • 面向对象的编程语言

    面向对象的编程语言,关注的是对象,而不关注过程

    对于面向对象的语言来说,一切都是对象

    面向对象的编程思想,将所有的功能统一保存到对应的对象中 这种方式编写的代码,比较容易阅读,并且比较易于维护,容易复用。

类与对象#

类#

  • 类是对一群具有相同 特征 或者 行为 的事物的一个统称,是抽象的,不能直接使用

特征 被称为 属性

行为 被称为 方法

类就是对象的图纸!在程序中我们需要根据类来创建对象

对象#

对象 是 由类创建出来的一个具体存在,可以直接使用 由 哪一个类 创建出来的 对象,就拥有在 哪一个类 中定义的属性方法 对象 就相当于用 图纸 制造 的飞机 在程序开发中,应该 先有类,再有对象

我们目前所学习的对象都是Python内置的对象

但是内置对象并不能满足所有的需求,所以我们在开发中经常需要自定义一些对象

如果多个对象是通过一个类创建的,我们称这些对象是一类对象

像 int() float() bool() str() list() dict() … 这些都是类

a = int(10) # 创建一个int类的实例 等价于 a = 10

我们自定义的类都需要使用大写字母开头,使用大驼峰命名法(帕斯卡命名法)来对类命名

类和对象的关系#

  • 类是模板,对象 是根据 类 这个模板创建出来的,应该 先有类,再有对象
  • 类只有一个,而对象可以有很多个
    • 不同的对象 之间 属性 可能会各不相同
  • 类 中定义了什么 属性和方法,对象 中就有什么属性和方法,不可能多,也不可能少

类的设计#

在使用面相对象开发前,应该首先分析需求,确定一下,程序中需要包含哪些类

类名的确定#

名词提炼法 分析 整个业务流程,出现的 名词,通常就是找到的类

属性和方法的确定#

  • 对 对象的特征描述,通常可以定义成 属性
  • 对象具有的行为(动词),通常可以定义成 方法

定义类#

类的基本结构

class 类名([父类]) :
公共的属性...
# 对象的初始化方法
def __init__(self,...):
...
# 其他的方法
def method_1(self,...):
...
def method_2(self,...):
...
...

1. self#

类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称,但是在调用这个方法的时候你不为这个参数赋值,Python会提供这个值。这个特别的变量指对象本身,按照惯例它的名称是**self**。

虽然你可以给这个参数任何名称,但是 强烈建议 你使用self这个名称——其他名称都是不赞成你使用的。使用一个标准的名称有很多优点——你的程序读者可以迅速识别它,如果使用self的话,还有些IDE(集成开发环境)也可以帮助你。

在定义类的过程中,无论是显式创建类的构造方法,还是向类中添加实例方法,都要求将 self 参数作为方法的第一个参数。

class Person:
# 定义一个study()实例方法
def study(self,name):
print(name,"正在学Python")

self 参数就相当于 C++ 中的 this 指针。也就是说,同一个类可以产生多个对象,当某个对象调用类方法时,该对象会把自身的引用作为第一个参数自动传给该方法,换句话说,Python 会自动绑定类方法的第一个参数指向调用该方法的对象。如此,Python解释器就能知道到底要操作哪个对象的方法了。

上面代码中,study() 中的 self 代表该方法的调用者,即谁调用该方法,那么 self 就代表谁。

2. 在外部创建属性#

在日常开发中,不推荐在 类的外部 给对象增加属性 如果在运行时,没有找到属性,程序会报错 对象应该包含有哪些属性,应该 封装在类的内部

class Dog:
def bark(self):
print(f'{self.name}汪汪叫')
d = Dog()
# d.bark()
d.name = '旺财'
d.bark()

3. 初始化方法(构造方法)#

在类中可以定义一些特殊方法(魔术方法)

特殊方法都是**以__开头,__结尾**的方法,注意,方法名中,开头和结尾各有 2 个下划线,且中间不能有空格。

特殊方法不需要我们自己调用,不要尝试去调用特殊方法

特殊方法将会在特殊的时刻自动调用

在创建类时,我们可以手动添加一个 __init__() 方法,该方法是一个特殊的类实例方法,称为**构造函数)。

创建对象的流程 d1 = Dog()的运行流程

  1. 创建一个变量

  2. 在内存中创建一个新对象

  3. __init__(self)方法执行

  4. 将对象的id赋值给变量

当使用 类名() 创建对象时,会 自动 执行以下操作: 为对象在内存中 分配空间 —— 创建对象 为对象的属性 设置初始值 —— 初始化方法 __init__ 方法,__init__ 是对象的内置方法

class Dog:
def __init__(self):
print('init a dog')
d = Dog() # 自动调用__init__方法,像构造函数
# init a dog

__init__ 方法是 专门 用来定义一个类 具有哪些属性的方法

class Dog:
'''这是一个学习Python定义的一个类'''
def __init__(self,name,weight):
print(name,"重量是",weight)
#创建 dog 对象,并传递参数给构造函数
dog = Dog("二哈",20)
# init会在对象创建以后离开执行
# init可以用来向新创建的对象中初始化属性
# 调用类创建对象时,类后边的所有参数都会依次传递到init()中

4. 内置方法和属性#

__del__ :

  • 当使用 类名() 创建对象时,为对象 分配完空间后,自动 调用 __init__ 方法

  • 当一个 对象被从内存中销毁 前,会 自动 调用 __del__ 方法

    生命周期

  • 一个对象从调用 类名() 创建,生命周期开始

  • 一个对象的 __del__ 方法一旦被调用,生命周期结束

  • 在对象的生命周期内,可以访问对象属性,或者让对象调用方法

class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 来了" % self.name)
def __del__(self):
print("%s 去了" % self.name)
# tom 是一个全局变量
tom = Cat("Tom")
print(tom.name)
# del 关键字可以删除一个对象
del tom

__str__:返回对象的描述信息,print 函数输出使用

  • 在 Python 中,使用 print 输出 对象变量,默认情况下,会输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示)
  • 如果在开发中,希望使用 print 输出 对象变量 时,能够打印 自定义的内容,就可以利用 __str__ 这个内置方法了
class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 来了" % self.name)
def __del__(self):
print("%s 去了" % self.name)
def __str__(self):
return "我是小猫:%s" % self.name
tom = Cat('Tom')
print(tom)
# 特殊方法,也称为魔术方法
# 特殊方法都是使用__开头和结尾的
# 特殊方法一般不需要我们手动调用,需要在一些特殊情况下自动执行
# 定义一个Person类
class Person(object):
"""人类"""
def __init__(self, name , age):
self.name = name
self.age = age
# __str__()这个特殊方法会在尝试将对象转换为字符串的时候调用
# 它的作用可以用来指定对象转换为字符串的结果 (print函数)
def __str__(self):
return 'Person [name=%s , age=%d]'%(self.name,self.age)
# __repr__()这个特殊方法会在对当前对象使用repr()函数时调用
# 它的作用是指定对象在 ‘交互模式’中直接输出的效果
def __repr__(self):
return 'Hello'
# object.__add__(self, other)
# object.__sub__(self, other)
# object.__mul__(self, other)
# object.__matmul__(self, other)
# object.__truediv__(self, other)
# object.__floordiv__(self, other)
# object.__mod__(self, other)
# object.__divmod__(self, other)
# object.__pow__(self, other[, modulo])
# object.__lshift__(self, other)
# object.__rshift__(self, other)
# object.__and__(self, other)
# object.__xor__(self, other)
# object.__or__(self, other)
# object.__lt__(self, other) 小于 <
# object.__le__(self, other) 小于等于 <=
# object.__eq__(self, other) 等于 ==
# object.__ne__(self, other) 不等于 !=
# object.__gt__(self, other) 大于 >
# object.__ge__(self, other) 大于等于 >=
# __len__()获取对象的长度
# object.__bool__(self)
# 可以通过bool来指定对象转换为布尔值的情况
def __bool__(self):
return self.age > 17
# __gt__会在对象做大于比较的时候调用,该方法的返回值将会作为比较的结果
# 他需要两个参数,一个self表示当前对象,other表示和当前对象比较的对象
# self > other
def __gt__(self , other):
return self.age > other.age
# 创建两个Person类的实例
p1 = Person('孙悟空',18)
p2 = Person('猪八戒',28)
# 打印p1
# 当我们打印一个对象时,实际上打印的是对象的中特殊方法 __str__()的返回值
# print(p1) # <__main__.Person object at 0x04E95090>
# print(p1)
print(p2) # Person [name=猪八戒 , age=28]
print(repr(p1)) # Hello
# t = 1,2,3
# print(t) # (1, 2, 3)
print(p1 > p2) # False
print(p2 > p1) # True
# print(bool(p1)) # True
if p1 :
print(p1.name,'已经成年了') # 孙悟空 已经成年了
else :
print(p1.name,'还未成年了')

因此 str() 与 repr() 的不同在于:

  • str() 的输出追求可读性,输出格式要便于理解,适合用于输出内容到用户终端。
  • repr() 的输出追求明确性,除了对象内容,还需要展示出对象的数据类型信息,适合开发和调试阶段使用。

另外如果想要自定义类的实例能够被 str() 和 repr() 所调用,那么就需要在自定义类中重载 __str__ 和 __repr__ 方法。

5. 类属性#

类属性其实就是在类体中定义的变量,类方法是在类体中定义的函数。无论是类属性还是类方法,都无法像普通变量或者函数那样,在类的外部直接使用它们。

  1. 类体中、所有函数之外:此范围定义的变量,称为类属性或类变量;
  2. 类体中,所有函数内部:以“self.变量名”的方式定义的变量,称为实例属性或实例变量;
  3. 类体中,所有函数内部:以“变量名=变量值”的方式定义的变量,称为局部变量。
  • 在 __init__ 方法内部使用 self.属性名 = 属性的初始值 就可以 定义属性
  • 定义属性之后,再使用 Dog 类创建的对象,都会拥有该属性
class Dog :
# 在类的代码块中,我们可以定义变量和函数
# 在类中我们所定义的变量,将会成为所有的实例的公共属性
# 所有实例都可以访问这些变量
name = '旺财' # 公共属性,所有实例都可以访问
# 在类中也可以定义函数,类中的定义的函数,我们称为方法
# 这些方法可以通过该类的所有实例来访问
def bark(self) :
# 方法每次被调用时,解析器都会自动传递第一个实参
# 第一个参数,就是调用方法的对象本身,
# 如果是p1调的,则第一个参数就是p1对象
# 如果是p2调的,则第一个参数就是p2对象
# 一般我们都会将这个参数命名为self
#在类封装的方法内部,self 就表示 当前调用方法的对象自己
#调用方法时,程序员不需要传递 self 参数
#在方法内部
#可以通过 self. 访问对象的属性
#也可以通过 self. 调用其他的对象方法
# say_hello()这个方法,可以显示如下格式的数据:
# 你好!我是 xxx
# 在方法中不能直接访问类中的属性 直接写name会报错
print('%s: 汪汪汪' %self.name)
# 创建Dog的实例
d1 = Dog()
d2 = Dog()
print(d2.name) # 旺财
# 调用方法,对象.方法名()
# 方法调用和函数调用的区别
# 如果是函数调用,则调用时传几个参数,就会有几个实参
# 但是如果是方法调用,默认传递一个参数,所以方法中至少要定义一个形参
d1.bark() # 旺财: 汪汪汪
# 也可以使用类对象来调用所属类中的类变量,why?
print(d1.name) # 旺财
print(Dog.name) # 旺财
# 因为类变量为所有实例化对象共有,通过类名修改类变量的值,会影响所有的实例化对象。
Dog.name = 'Dog2'
print(d2.name) # Dog2

6. 成员变量#

实例变量指的是在任意类方法内部,以“self.变量名”的方式定义的变量,其特点是只作用于调用方法的对象。另外,实例变量只能通过对象名访问,无法通过类名访问。

class Dog:
# 类变量
name = 'Dog'
def __init__(self,name,weight):
print(name,"重量是",weight)
def bark(self):
# 这里直接调用name会报错
print(f'{name}汪汪叫')

7. 初始化的同时设置初始值#

如果希望在创建对象的同时,就设置对象的属性,可以对 __init__ 方法进行 改造 把希望设置的属性值,定义成__init__方法的参数 在方法内部使用 self.属性 = 形参 接收外部传递的参数 在创建对象时,使用 类名(属性1, 属性2…) 调用

class Dog:
# 类变量,公共属性
name = '旺财'
def bark(self):
print(f'{self.name}汪汪叫')
def __init__(self,name):
self.name = name
d = Dog('二哈')
d.bark()
print(d.name,Dog.name)

8. 实例方法#

通常情况下,在类中定义的方法默认都是实例方法。前面章节中,我们已经定义了不只一个实例方法。不仅如此,类的构造方法理论上也属于实例方法,只不过它比较特殊。

实例方法最大的特点就是,它最少也要包含一个 self 参数,用于绑定调用此方法的实例对象(Python 会自动完成绑定)。实例方法通常会用类对象直接调用,例如:

dog = Dog()
dog.bark()

Python 也支持使用类名调用实例方法,但此方式需要手动给 self 参数传值。例如:

Dog.bark(dog)

值得一提的是,上面的报错信息只是让我们手动为 self 参数传值,但并没有规定必须传一个该类的对象,其实完全可以任意传入一个参数,例如:

class Dog:
def bark(self):
print(self,‘汪汪叫’)
d = Dog()
Dog.bark("二哈")

可以看到,“二哈” 这个字符串传给了 bark() 方法的 self 参数。显然,无论是 bark() 方法中使用 self 参数调用其它类方法,还是使用 self 参数定义新的实例变量,胡乱的给 self 参数传参都将会导致程序运行崩溃。

总的来说,Python 中允许使用类名直接调用实例方法,但必须手动为该方法的第一个 self 参数传递参数,这种调用方法的方式被称为“非绑定方法”。

用类的实例对象访问类成员的方式称为绑定方法,而用类名调用类成员的方式称为非绑定方法。

9. 类方法#

Python 类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,Python 会自动将类本身绑定给 cls 参数(注意,绑定的不是类对象)。也就是说,我们在调用类方法时,无需显式为 cls 参数传参。和 self 一样,cls 参数的命名也不是规定的(可以随意命名),只是 Python 程序员约定俗称的习惯而已。

class Dog:
#类构造方法,也属于实例方法
def __init__(self):
self.name = "二哈"
#下面定义了一个类方法
@classmethod
def bark(cls):
print("正在调用类方法",cls)

注意,如果没有 @classmethod,则 Python 解释器会将 bark() 方法认定为实例方法,而不是类方法。

类方法推荐使用类名直接调用,当然也可以使用实例对象来调用(不推荐)。例如,在上面 Dog类的基础上,在该类外部添加如下代码:

d = Dog()
d.bark()

10. 静态方法#

静态方法,其实就是我们学过的函数,和函数唯一的区别是,静态方法定义在类这个空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空间)中。静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。

静态方法需要使用@staticmethod修饰,例如:

class Dog:
@staticmethod
def bark(name,action):
print(name,action)

静态方法的调用,既可以使用类名,也可以使用类对象,例如:

Dog.bark('二哈','叫') # 二哈 叫
d = Dog()
d.bark('二哈','叫') # 二哈 叫

在实际编程中,几乎不会用到类方法和静态方法,因为我们完全可以使用函数代替它们实现想要的功能,但在一些特殊的场景中(例如工厂模式中),使用类方法和静态方法也是很不错的选择。

案例#

  1. 封装 是面向对象编程的一大特点
  2. 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中
  3. 外界 使用 类 创建 对象,然后 让对象调用方法
  4. 对象方法的细节 都被 封装 在 类的内部
class BankAccount:
def __init__(self, username, card_no, balance):
self.username = username # 用户姓名
self.card_no = card_no # 账号
self.balance = balance # 余额
self.history_lst = [] # 历史操作记录
def deposit(self, amount):
'''
存钱
:param amount:
:return:
'''
pass
def withdraw(self, amount):
'''
取钱
:param amount:
:return:
'''
pass
def transfer(self, another, amount):
'''
转账
:param another:
:param amount:
:return:
'''
pass
def history(self):
'''
历史操作记录
:return:
'''
pass

Python 不支持重载,但是可以用singledispatch方式实现#

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
Python 面向对象1:类与对象
https://blog.furjesk.com/posts/tutorial-python-oo1/
作者
Furjesk
发布于
2022-06-02
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Furjesk
读书,应由薄到厚,再由厚到薄
分类
标签
最新动态
站点统计
文章
35
分类
9
标签
51
总字数
71,518
运行时长
0 天
最后活动
0 天前