目录
- 类与对象的概念
- 实例方法
- 实例变量
- 初始化方法
- 析构方法
- 常用内置方法
- 继承
- 类方法与静态方法
- 动态扩展类与实例
- @property装饰器
概述
面向对象是当前流行的程序设计方法,其以人类习惯的思维方法,用对象来理解和分析问题空间,使开发软件的方法与过程尽可能接近人类认识世界丶解决问题的思维方法与过程。
面向对象方法的基本观点是一切系统都是由对象构成的,每个对象都可以接收并处理其他对象发送的消息,它们的相互作用丶相互影响丶实现了整个系统的运转
1.类与对象的概念
- 类和对象是面向对象程序设计的两个重要概念
- 类和对象的关系即数据类型与变量的关系,根据一个类节约创建多个对象,而每个对象只能是某一个类的对象
- 类规定了可以用于存储什么数据,而对象用于实际存储数据,每个对象可存储不同的数据
- 与C/C++等语言不同,Python规定万物皆对象,即Python中提供的基本数据类型也是类,如int,float等。
类的定义
- 拥有相同属性和行为的对象分为一组,即为一个类
- 类是用来描述对象的工具,用类可以创建此类的对象(实例)
- 属性对象一个类可以用来保存哪些数据,而方法对应一个类可以支持哪些操作(即数据处理)
类的定义形式多样
- 我们即可以直接创建新的类,也可以基于一个或多个已有的类创建新的类。
- 我们即可以创建一个空的类,然后再动态添加属性和方法,也可以在创建类的同时设置属性和方法。
- 每次创建对象时,系统都会在内存中选择一块区域分配给对象,每次选择的内存通常是不一样的。
类的创建语句 class 语句 语法: class 类名(继承列表): '''类的文档字符串''' 实例方法定义 类变量的定义 类方法(@classmethod) 定义 静态方法(@staticmethod) 定义
说明:
继承列表可以省略
类名必须是一个标识符(写变量的命名相同,建议首字母大写)
类名实质上就是变量,它绑定一个类,即指向这个类的地址
构造函数
构造函数调用表达式
类名([创建传参列表])
作用:
创建这个类的实例对象,并返回此实例对象的引用关系
实例说明:
实例有自己的作用域和名字空间,可以为该实例添加实例变量(属性)
实例可以调用实例方法和类方法
实例可以访问实例变量和类变量
2.实例方法(instance method)
定义语法:
class 类名(继承列表):
def 实例方法名(self, 参数1, 参数2, ...):
'''方法的文档字符串'''
语句块
作用:
用于描述一个对象的行为.让此类型的全部对象都拥有相同的行为
说明:
实例方法的实质是函数,是定义在类内的函数
实例方法至少有一个形参,第一个形参用来调用(传递)这个方法的实例,一般命名为'self',self即这个实例本身
调用语法:
实例.实例方法名(调用传参)
类名.实例方法名(实例, 调用传参)
# 此示例示意实例方法的定义和调用 class Dog: '''这是一种小动物的定义''' def eat(self, food): '''此方法用来描述小狗吃的行为''' print('id为', id(self), "的小狗正在吃", food) def sleep(self, hour): print('id为', id(self), '的小狗睡了', hour, '小时') dog1 = Dog() # 创建一个对象 dog1.eat('骨头') dog2 = Dog() # 创建另一个对象 dog2.eat('狗粮') dog1.sleep(1) dog2.sleep(3) Dog.eat(dog1, '包子') #等同于 dog1.eat("包子") 输出 id为 60750064 的小狗正在吃 骨头 id为 61271952 的小狗正在吃 狗粮 id为 60750064 的小狗睡了 1 小时 id为 61271952 的小狗睡了 3 小时 id为 60750064 的小狗正在吃 包子
3.实例变量(也称为实例属性)
每个实例可以有自己的变量,称为实例变量(实例属性)
语法:
实例.属性名
作用:
记录每个对象自身的数据
赋值规则:
首次为属性赋值则创建此属性,再次为属性赋值则改变属性的绑定关系
# 此示例示意实例变量的创建和访问 class Dog: def eat(self, food): print(self.color, '的', self.kinds, '正在吃', food) self.last_food = food # 为正在吃的狗创建一个属性 # 用来记住上次吃的是什么 def info(self): print(self.color, '的', self.kinds, '上次吃的是', self.last_food) dog1 = Dog() dog1.kinds = '导盲犬' # 为dog1绑定的对象添加kinds属性 dog1.color = '灰色' # 初次赋值是创建变量color并绑定为'灰色' dog1.color = '黑色' # 再次赋值则改变color的绑定关系 # print(dog1.color, '的', dog1.kinds) dog1.eat("骨头") dog2 = Dog() dog2.kinds = '哈士奇' dog2.color = '黑白色' dog2.eat('狗粮') dog1.info() dog2.info() 输出: 黑色 的 导盲犬 正在吃 骨头 黑白色 的 哈士奇 正在吃 狗粮 黑色 的 导盲犬 上次吃的是 骨头 黑白色 的 哈士奇 上次吃的是 狗粮
删除属性
del 语句
语法:
del 对象.实例变量名
示例:
class Dog: pass dog1 = Dog() dog1.color = '白色' # <<<--- 添加实例变量 print(dog1.color) # 白色 del dog1.color # 删除实例变量(属性) print(dog1.color) # 属性错误,没有color这个属性
4.初始化方法(构造方法):
作用:
对新创建的对象添加属性
格式:
class 类名(继承列表):
def __init__(self[, 形参列表]):
语句块
说明:
1. 初始化方法名必须为__init__ 不可改变(注:双下划线)
2. 初始化方法会在构造函数创建实例后自动调用,且将实例自身通过第一个参数self传入__init__方法
3. 构造函数的实参将通过__init__方法的参数列表传入到__init__ 方法中
4. 初始化方法内如果需要return 语句返回则必须返回None
5.析构方法:
作用:
析构方法是类的另一个内置方法,它的方法名为__del__, 在销毁一个类对象时会自动执行.
负责完成待销毁对象占用的资源清理工作,如关闭文件等
格式:
class 类名(继承列表):
def __del_(self[):
语句块
说明:
- 析构方法在对象(实例)销毁前被自动调用
- python语言建议不要在对象销毁时做任何事情,因为销毁的时间难以确定
- 类对象销毁有如下三种情况:
- 局部变量的作用域结束
- 使用del删除对象
- 程序结束时,程序中的所有对象都将被销毁
注意:
如果多个变量对应同一片内存空间,则只有这些变量都删除后才会销毁这片内存空间中保存的对象,也才会自动执行析构方法。
比如:
stu = student('小王') # 创建一个对象学生stu stu1 = stu del stu # 使用del删除stu对象,但不会删除stu变量指向的内存空间,即不会自动调用析构方法,因为stu1此时也对应着这一片内存空间
6.常用的内置方法:
__str__ :
调用str函数对类对象进行处理时或者调用Python内置函数format()以及print()时自动执行, __str__方法的返回值必须是字符串。
如果我们需要将一个对象当成字符串来使用的话,那么我们就必须在这个类中提供__str__ 方法。
有这个方法才能将类对象自动转为使用时所需要的字符串。如果没有则会输出这个对象的地址。
例如:
class Complex: # 定义复数类Complex def __init__(self, real, image): self.real = real # 将self对应对象的real属性赋值为形参real的值 self.image = image # 将self对应对象的image属性赋值为形参image的值 def __str__(self): # 定义内置方法__str__ return str(self.real) + '+' + str(self.image) + 'i' c = Complex(3.2, 5.3) # 定义Complex类对象c print(c) # 输出 3.2+5.3i
上例如果没有定义__str__方法,则输出<__main__.Complex object at 0x02C3EFD0>
比较运算的内置方法
内置方法 | 功能描述 |
__gt__(self,other) | 进行self>other运算时自动执行 |
__lt__(self,other) | 进行self |
__ge__(self,other) | 进行self>=other运算时自动执行 |
__le__(self,other) | 进行self<=other运算时自动执行 |
__eq__(self,other) | 进行self==other运算时自动执行 |
__ne__(self,other) | 进行self!=other运算时自动执行 |
大于 greater than 等于 equal 小于 less than 不等于 not equal
实例:
class Student: def __init__(self, name, age): self.name = name self.age = age def __le__(self, other): return self.age <= other.age stu = Student('李明', 19) stu1 = Student('马红', 20) print('马红的年龄小于等于李明的年龄:', stu1 <= stu) 输出: 马红的年龄小于等于李明的年龄: False
7.继承
继承和派生的概念
- 继承允许开发者基于已有的类创建新的类
-
派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为
- 如果一个类C1通过继承已有类C而创建,则将C1称为2子类(sub class), 将C称作基类、父类或超类(base class、super class).
- 子类会继承父类中定义的所有属性和方法, 另外也能够在子类中增加新的属性和方法。
- 如果一个子类只有一个父类,则将这种继承关系称为单继承;如果一个子类有两个或更多父类,则将这种继承关系称为多重继承。
为什么继承/派生
继承的目的是延续旧类的功能
派生的目地是在旧类的基础上添加新功能
作用:
用继承派生机制, 可以将一些共有功能加在基类中,实现代码共享
在不改变基类的代码的基础上改变原有类的功能
子类的定义:
class 子类名(父类1、父类2、...、父类M):
语句块
当M 等于1时。则为单继承;当M大于1时,则为多重继承。
方法重写
是指子类可以对从父类中继承过来的方法进行重新定义,从而使得子类对象可以表现出对父类对象不同的行为。
如果在重定义的方法中,传入不同的实例参数,系统会根据对象实际所属的类去调用相应类中的方法。于是就出现了在执行同样代码的情况下,
输出不同的结果,这种现象被称为多态。
def Print_Info(对象实例): #普通函数 对象实例.PrintInfo() #调用该实例对象的方法
鸭子类型
在鸭子类型中,关注的不是对象所属的类,而是一个对象能够如何使用。
在Python中编写一个函数,传递实参前其参数的类型并不确定,在函数中使用形参进行操作时只要传入的对象能够支持该操作程序就能正常执行。
所以,实际上,Python中的多态也是借助鸭子类型实现,与C++、Java等语言中的多态并不是同一含义。
super方法
super方法用于获取父类的代理方法,以执行已在子类中被重写的父类方法,其语法格式为:
super( [类名, [对象名或类名] ] )
super方法有两个参数:
第一个参数是要获取父类代理对象的类名。
第二个参数如果传入对象名,则该对象所属的类必须是第一个参数指定的类或该类的子类,找到的父类对象的self会绑定到这个对象上;
如果传入类名, 则该类必须是第一个参数指定的类的子类。
在一个类A的定义中调用super方法时,可以将两个参数都省略, 此时,super( )等价于super(A, self), 即获取A的父类代理对象,且获取
到的父类代理对象中的self绑定到当前A类对象的self上。
有关类的内置函数
isinstance: 用于判断一个对象所属的类是否是指定类或指定类的子类;
issubclass: 用于判断一个类是否是另一个类的子类;
type: 用于获取一个对象所属的类。
8.类方法和静态方法
类方法:
类方法是指使用@classmethod修饰的方法, 其第一个参数是类本身(而不是类的实例对象)。
类方法的特点是既可以通过类名直接调用,也可以通过类的实例对象调用。
1 class Complex: # 定义复数类Complex 2 def __init__(self, real=0, image=0): 3 self.real = real # 将self对应对象的real属性赋值为形参real的值 4 self.image = image # 将self对应对象的image属性赋值为形参image的值 5 6 @classmethod 7 def add(cls, c1, c2): # 定义类方法add, 完成两个数相加 8 print('cls的值为:', cls) 9 c = Complex() #创建Complex对象c 10 c.real = c1.real + c2.real # 实部相加 11 c.image = c1.image + c2.image # 虚部相加 12 return c 13 14 c1 = Complex(1, 2.5) 15 c2 = Complex(2.2, 3.1) 16 c = Complex.add(c1, c2) # 直接使用类名调用类方法add 17 print('c1+c2=%.2f+%.2fi' %(c.real,c.image)) 18 19 输出: 20 cls的值为: <class '__main__.Complex'> 21 c1+c2=:3.20+5.60i
提示:
将16行的 "c = Complex.add(c1, c2)" 改成 “c = c1.add(c1, c2)” 或 "c = c2.add(c1, c2)" 或 "c = Complex().add(c1, c2)", 将程序运行后可得到相同的输出结果,
即类方法也可以使用实例对象调用。
通过类方法add的第一个参数,从输出结果中可以看到 cls 是 Complex类。
静态方法
静态方法是指使用@staticmethod修饰的方法。
与类方法相同,静态方法既可以直接通过类名调用,也可以通过类的实例对象调用。
与类方法不同的地方在于,静态方法中没有类方法中的第一个类参数。
9.动态扩展类与实例
- python作为一种动态语言,除了可以在定义类时定义属性和方法外,还可以动态地为已经创建的对象绑定新的属性和方法
- 在给对象绑定方法时,需要使用types模块中的MethodType方法,其第一个参数是要绑定的函数名,第二个参数是绑定的对象名。
- 动态绑定属性时,可直接在对象上绑定,但要注意__slots__属性的限制。
例:绑定新方法实例
from types import MethodType class Student: pass def SetName(self, name): # 定义SetName函数 self.name = name def SetSno(self, son): # 定义SetSno函数 self.son = son stu1 = Student() stu2 = Student() stu1.SetName = MethodType(SetName, stu1) # 为stu对象绑定SetName方法 Student.SetSno = SetSno # 为Student类绑定Setno方法 stu1.SetName('李晓明') stu1.SetSno('1810100') # stu2.SetName('张刚') # 取消注释则会报错 stu2.SetSno('1810101')
注意: 如果只是给一个对象绑定一个新方法,那么只有这个对象有这个方法,只能通过该对象调用该方法。同一个类的其他对象没有这个方法。
如果是给一个类绑定新方法,那么这个类的所有实例都有这个新方法。
__slots__
- 在定义类时,python提供了__slots__变量以限制可动态扩展的 属性。
- __slots__中所做的动态扩展属性限制只对__slots__所在类的实例对象有效
- 如果子类中没有__slots__定义,则子类的实例对象可以进行任意属性的动态扩展。
- 如果子类中有__slots__定义,则子类的实例对象可动态扩展的属性包括子类中通过__slots__定义的属性和其父类中通过__slots__定义的属性。
例: __slots__使用实例
class Person: __slots__ = ('name') # 定义允许动态扩展的属性 class Student(Person): # 以Person类作为父类定义子类Student类 __slots__ = ('sno') # 定义允许动态扩展的属性 class Postgraduate(Student): # 以Student类作为父类定义子类Postgraduate类 pass stu = Student() stu.sno = '1810100' # 为stu对象动态扩展属性sno stu.name = '李晓明' # 为stu对象动态扩展属性name #stu.tutor = '马红' # AttributeError: 'Student' object has no attribute 'tutor' pg = Postgraduate() # 定义Postgraduate类对象 pg.sno = '1810101' # 为pg动态扩展属性son pg.name = '张刚' # 为pg对象动态扩展属性name pg.tutor = '马红' # 为pg对象动态扩展属性tutor
10.@property装饰器
类中的属性可以直接访问和赋值,这为类的使用者提供了方便,但也带来了问题: 类的使用者可能可能会给一个属性赋上超出有效范围的值。
为了解决这个问题,Python提供了@property装饰器, 可以将类中属性的访问和赋值操作自动转为方法调用,这样可以在方法中对属性值得取值范围做一些条件限定
直接使用@property就可以定义一个用于获取属性值的方法(即getter)
如果要定义一个设置属性值的方法(setter),则需要使用名字 "@属性值.setter"的装饰器。
如果一个属性只有用于获取属性值的getter方法,而没有用于设置属性值的setter方法,则该属性是一个只读属性,只允许读取该属性的值,而不能设置该属性的值。
例:通过@property装饰器使得学生成绩的取值范围必须在0~100之间。
import datetime class Student: # 定义Student类 @property def score(self): # 用@property装饰器定义一个用于获取score值的方法 return self._score @score.setter def score(self, score): # 用score.setter定义一个用于设在score值的方法 if score<0 or score>100: # 不符合0~100的限定条件 print('成绩必须在0~100之间') else: self._score = score @property def age(self): # 用@property装饰器定义一个用于获取age值的方法 return datetime.datetime.now().year-self.birthyear stu = Student() # 创建Student类对象Stu stu.score = 80 # 将stu对象的score属性赋值为80 stu.birthyear = 2000 # 将stu对象的birthyear属性赋值为2000 print('年龄:%d,成绩:%d' %(stu.age, stu.score)) # stu.age = 19 # 取消注释会报错, 因为在student类的age属性只设置了getter方法, # 而没有设置setter方法(没有@age.setter),所以age只能取值而不能赋值,即age是只读属性 stu.score = 105 # print('年龄:%d,成绩:%d' %(stu.age, stu.score))
输出:
年龄:19,成绩:80 成绩必须在0~100之间 年龄:19,成绩:80
注意:在类的setter'和getter方法中使用self访问属性时,需要在属性名前加上下划线,否则系统会因不断递归调用而报错。