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()的运行流程
-
创建一个变量
-
在内存中创建一个新对象
-
__init__(self)方法执行 -
将对象的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.nametom = 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) # Falseprint(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. 类属性
类属性其实就是在类体中定义的变量,类方法是在类体中定义的函数。无论是类属性还是类方法,都无法像普通变量或者函数那样,在类的外部直接使用它们。
- 类体中、所有函数之外:此范围定义的变量,称为类属性或类变量;
- 类体中,所有函数内部:以“self.变量名”的方式定义的变量,称为实例属性或实例变量;
- 类体中,所有函数内部:以“变量名=变量值”的方式定义的变量,称为局部变量。
- 在
__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) # Dog26. 成员变量
实例变量指的是在任意类方法内部,以“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('二哈','叫') # 二哈 叫在实际编程中,几乎不会用到类方法和静态方法,因为我们完全可以使用函数代替它们实现想要的功能,但在一些特殊的场景中(例如工厂模式中),使用类方法和静态方法也是很不错的选择。
案例
- 封装 是面向对象编程的一大特点
- 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中
- 外界 使用 类 创建 对象,然后 让对象调用方法
- 对象方法的细节 都被 封装 在 类的内部
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: ''' passPython 不支持重载,但是可以用singledispatch方式实现
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!














