python面向对象
1.类
1.1引入类
面向对象:
所谓面向对象,就是在编程的时候尽可能的去模拟真实的现实世界,按照现实世界中的逻辑去处理问题,分析问题中参与其中的有哪些实体,这些实体应该有什么属性和方法。我们如何通过调用这些实体中的属性和方法去解决问题
类:用来描述具有相同的属性和方法的对象的集合。它定义了集合中每一个对象所共有的熟悉和方法,对象是类的实例
使用class创建类
类没有创建作用域(所以不需要申明作用域)
类创建命名空间(将同名的变量隔离了)
命名:
变量名的风格:没有大写字母,单词之间,使用下划线分隔
类名风格:单词首字母大写,不使用下划线
优势:
- 不使用闭包,减少内存泄漏风险
- 不使用闭包,实现业务直接变量隔离
- 使用类,可以从外部访问内部变量
- 使用类,不会因为重复执行代码,导致数据被销毁
class a: #创建类,没有创建作用域 n=0 #统计次数 def f(): print(f"{a.n=}") a.n+=1 class b: n="" def g(): print(f"{b.n=}") b.n+="s" n="zhangsan" a.f() b.g() a.f() b.g() a.f() b.g()
1.2 创建类
要素:
名字
属性(变量)
方法(函数
class hourse: name="" area=0 #面积 room_count=0 #房子数量 price=0 #单价 def amount(self): return self.m2*self.prices
1.3 使用类
类是为了解决实际问题
面向对象是为了解决设计问题
1.4.对象
对象是按照类,生成的多个相互隔离的空间
__init__:魔法方法,双下线特殊函数,在特殊的时机,由python自动调用
- 在类实例化时,自动调用
- 接收实例化参数
- 第一个位置参数,是实例对象本身(self)
- 不应该有返回值
class hourse: name="" area=0 #面积 room_count=0 #房子数量 price=0 #单价 def __init__(self,a,b,c,d): #双下线特色函数:在特殊的时机,由python自动调用 print(self,a,b,c,d) c1=hourse(3,4,5,6) #创建对象 c2=hourse(7,8,9,0) c3=hourse(1,2,3,4)
如何创建一个对象:
- 通过类名()可以创建一个对象
- c1=hourse()
- c1就是通过hourse创建的实例化对象
- 在创建对象的时候会自动调用__init__方法并且把对象c1传递赋值给self
- 那么该对象c1就能获取魔法方法里面的所有实例属性
1.5.属性
概念:和变量类似,用于存储数据,可以访问
类属性:类属性在整个实例化的对象中是公用的。类属性定义在类中且在函数体之外。类属性通常不作为实例属性使用
如何修改类属性:
- 类.属性=xxx
- self.__class__.属性=xxx
- cls.属性=XXX
实例属性:实例属性,在任意方法内部,以self.变量名的的方式定义的变量。实例变量只能通过对象名访问,无法通过类名访问
1 #定义类:使用class关键字 2 #类名:使用大驼峰命名规则 3 class Hero: 4 #在类中可以封装实例属性和实例方法 5 #实例属性:需要在魔法方法(构造方法)__init__封装实例属性 6 #该魔法方法会在对象被创建的时候自动执行,不需要手动调用 7 #魔法方法:一般不需要手动调用,已经定义的功能方法,在某种条件下自动触发 8 def __init__(self): 9 #self代表对象本身,谁是对象self就是谁 10 self.name="厄斐琉斯" 11 self.hp=800 12 self.mp=300 13 14 15 #通过类可以创建对象 16 #对象名():实例化一个对象赋值给h1 17 #h1就是通过Hero类创建的对象 18 #对象一旦被创建,那么自动调用魔法方法__init__ 19 #那么对象就会拥有3个实例属性:name,hp,mp 20 #通过对象调用实例属性 对象名.属性名 21 h1=Hero() 22 print(h1.name) 23 print(h1.hp) 24 print(h1.mp) 25 #一个类可以创建无数个对象 26 h2=Hero() 27 #那么对象就会拥有3个实例属性:名字,生命值,魔法值 28 print(h2.name) 29 print(h2.hp) 30 print(h2.mp)
如何定义实例属性:
- 在init魔法方法中进行定义实例属性
- 并且每一个属性前面都有self代表对象本身
- self代表被创建的对象本身,谁是对象谁就是self
- self.实例属性=“具体的属性值”
如何修改实例属性
- 实例.属性=xxx
- self.属性=XXX
class a: #创建类,没有创建作用域 n=0 #类属性:统计次数 def f(self): self.b="a" #实例属性 print(f"{a.n=}") a.n+=1
说明:如果实例有属性,则使用自己的属性,没有属性则使用类属性
实例属性动态赋值
1 class Hero: 2 #魔法方法,初始化实例属性,加载类时自动调用 3 def __init__(self,name,hp,mp): 4 self.name=name 5 self.hp=hp 6 self.mp=mp 7 8 #行为封装成实例方法 9 def attack(self): 10 print(f"{self.name}发动一次普通攻击") 11 12 def move(self,x,y): 13 print(f"开始移动,x的坐标为{x},y的坐标为{y}") 14 15 #方法中也可以有返回值 16 def back_city(self,b="b"): 17 print("执行回城操作") 18 print(f"使用快捷键回城{b}") 19 return 2 #代表回城的时间2秒 20 21 #实例方法之间可以相互调用 22 def a(self): 23 print("我是a方法") 24 #通过self.方法名()进行调用 25 self.b() 26 27 def b(self): 28 print("我是b方法") 29 30 h1=Hero("武器大师贾克斯",888,666) 31 h1.a() 32 print(h1.name) 33 print(h1.hp) 34 print(h1.mp) 35 h1.attack()
1.6 方法
概念:类中定义的函数。类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称,按照惯例她的名称是self(其实取什么名字不重要,只是有这个参数就行,默认系统叫做self)
定义在类中的函数,称之为方法,因为会自动传递参数
如果某方法,不接受参数,直接访问结果(以固定的方式返回数据)
可以通过装饰器,伪装成属性,可以不加括号直接读取
class hourse: name="" area=0 #面积 room_count=0 #房子数量 price=0 #单价 def __init__(self,a,b,c,d): #双下线特色函数:在特色的时机,由python自动调用 print(self,a,b,c,d) @property #可以通过装饰器,伪装成属性 def amount(self): return self.area*self.price print(c2.amount) #可以不加括号直接读取
1.6.1 类方法
传递类本身
class a: @classmethod #此装饰器修饰的方法称为类方法 def b(cls): pass
1.6.2 实例方法
传递实例对象
1 class Hero: 2 #魔法方法,初始化实例属性,加载类时自动调用 3 def __init__(self): 4 self.name="岩雀" 5 self.hp=900 6 self.mp=300 7 #行为封装成实例方法 8 #类中函数就是实例方法 9 #方法和函数的区别:方法定义在类中,而且第一个参数默认一定是self代表对象本身 10 def attack(self): 11 #在实例方法中可以使用实例属性 12 #通过self.属性名进行调用 13 print(f"{self.name}发动一次普通攻击") 14 #方法中的参数可以使用任何类型 15 #位置参数,关键字参数,默认参数,不定长参数... 16 #前提是所有参数必须在self后面进行添加 17 def move(self,x,y): 18 print(f"开始移动,x的坐标为{x},y的坐标为{y}") 19 #方法中也可以有返回值 20 def back_city(self,b="b"): 21 print("执行回城操作") 22 print(f"使用快捷键回城{b}") 23 return 2 #代表回城的时间2秒 24 25 26 h1=Hero() 27 print(h1.name) 28 print(h1.hp) 29 print(h1.mp) 30 #实例方法的使用:对象.方法名() 31 h1.move(280,390) 32 h1.attack() 33 print(h1.back_city("c")) 34 time=h1.back_city() 35 print(time)
类方法和类属性
1 class Hero: 2 #封装类属性:类中定义的变量就是类属性 3 num=777 #num就是类属性 4 5 #魔法方法,初始化实例属性,加载类时自动调用 6 #封装的实例属性 7 def __init__(self,name,hp,mp): 8 self.name=name 9 self.hp=hp 10 self.mp=mp 11 12 #行为封装成实例方法 13 #封装的实例方法 14 def attack(self): 15 print(f"{self.name}发动一次普通攻击") 16 17 def move(self,x,y): 18 print(f"开始移动,x的坐标为{x},y的坐标为{y}") 19 20 #方法中也可以有返回值 21 def back_city(self,b="b"): 22 print("执行回城操作") 23 print(f"使用快捷键回城{b}") 24 return 2 #代表回城的时间2秒 25 26 #定义类方法需要使用装饰器@classmethod 27 @classmethod 28 def abc(cls): 29 print("类方法abc") 30 31 h1=Hero("武器大师贾克斯",888,666) 32 33 #类属性的使用 34 #通过对象.类属性名 35 print(h1.num) 36 #类也可以使用类属性:类名.类属性名 37 print(Hero.num) 38 #类不能直接调用实例属性和实例方法 39 # print(Hero.name) 40 # Hero.attack() 41 42 #类方法的使用: 43 #通过对象名.类方法名() 44 h1.abc() 45 #通过类名.类方法名() 46 Hero.abc()
1.6.3 静态方法
什么都不传递
class a: @staticmethod def b(): pass
他们的区别:就是是否会自动传递参数,以及传递的值是什么
1 class Hero: 2 #封装类属性:类中定义的变量就是类属性 3 num=777 #num就是类属性 4 5 #魔法方法,初始化实例属性,加载类时自动调用 6 #封装的实例属性 7 def __init__(self,name,hp,mp): 8 self.name=name 9 self.hp=hp 10 self.mp=mp 11 12 #定义静态方法需要使用装饰器 @staticmethod 13 @staticmethod 14 def sendMessage():#静态方法没有默认必带参数,跟函数相似 15 print("给队友发信息") 16 17 h1=Hero("武器大师贾克斯",888,666) 18 #静态方法:对象和类都可以使用 19 h1.sendMessage() 20 Hero.sendMessage()
如果需要接收额外的参数,可以自由的定义
总结:
- 函数:定义在模块中,能实现功能
- 实例方法:定义在类中的函数,而且第一个形参必须是self,代表对象本身
- 类方法:定义在类中的函数,而且第一个形参必须是cls,代表类本身,使用@classmethod装饰器装饰
- 静态方法:定义在类中的函数,而且没有固定的必带形参,使用@staticmethod装饰器装饰
- 魔法方法:类中已经定义的函数,以双下划线开头和结尾,不需要手动调用,在某种条件下自动触发
- 共同点
- 都是通过def关键字进行定义,所有函数和方法的形参及实参的传递都符合函数的基本类型使用
- 位置参数
- 默认参数
- 关键字参数
- 不定长位置参数
- 不定长关键字参数
- 都可以使用return
1.7 面向对象三大特征
1.7.1 封装
封装:对数据进行封装,防止被外部意外的修改或者破坏
Python约定:
- 下划线开头的(属性和方法),仅供类内部使用
- 双下线开头的(属性和方法),仅供本类使用
约定没有强制约束力,可以绕过
- b._age=-100 #age属性:可以绕过
- b._A__age=-100 #age属性:可以绕过
class A: name="" _age=0 __sex='男' @property def age(self): return self._age @age.setter def age(self,value): assert value>0 self._age=value def say(self): print(f"I am {self.name},{self._age} year old") a=A() a.age=1 #age属性:可读不可写 print(a.age) print(a.say()) class B(A): def b_say(self): print(f"I am {self.name},{self._age} years old") b=B() b.age=3 b._age=-100 #age属性:可以绕过 b._A__age=-100 #age属性:可以绕过 print(b.age)
1.7.2 继承
首先,继承实现了代码的复制
其次,继承创建了关系:子类的实例,也被认为是父类的实例
1.7.2.1 单继承
class A: name="" _age=0 __sex='男' @property def age(self): return self._age @age.setter def age(self,value): assert value>0 self._age=value def say(self): print(f"I am {self.name},{self._age} year old") a=A() a.age=1 #age属性:可读不可写 print(a.age) print(a.say()) class B(A): def b_say(self): print(f"I am {self.name},{self._age} years old") b=B() b.age=3 print(b.age) print("b是B的实例对象",isinstance(b,B)) print("b是A的实例对象",isinstance(b,A))
例如:
1 class A: 2 #定义类属性a 3 a=6 4 def __init__(self): 5 #定义实例属性b 6 self.b=7 7 8 def q(self): 9 print("调用了实例方法q") 10 11 @classmethod 12 def w(cls): 13 print("调用了类方法w") 14 15 class B(A): 16 pass 17 18 #继承:定义类的时候,类名(父类名) 19 #B类继承了A类的所有属性和方法 20 #通过B类创建对象b1 21 b1=B() 22 print(b1.a) 23 print(b1.b) 24 b1.q() 25 b1.w() 26 27 print(b1.q())#None,方法如果没有使用return返回值,那么方法调用完打印就是None空值
1.7.2.2 多继承
1 class A: 2 #定义类属性a 3 a=6 4 def __init__(self): 5 #定义实例属性b 6 self.b=7 7 8 def q(self): 9 print("调用了A类的实例方法q") 10 11 @classmethod 12 def w(cls): 13 print("调用了A类的类方法w") 14 15 class B: 16 def e(self): 17 print("这是B类实例方法e") 18 def r(self): 19 print("这是B类实例方法r") 20 21 #使用多继承,只需要在括号中写多个父类的名字即可 22 class C(A,B): 23 pass 24 25 c1=C() 26 c1.w() 27 c1.q() 28 c1.e() 29 c1.r()
如果父类中的方法或者属性重名,优先使用离他近的类的方法
1 class A: 2 def e(self): 3 print("调用了A类的实例方法e") 4 5 class B: 6 def e(self): 7 print("这是B类实例方法e") 8 9 10 #使用多继承,只需要在括号中写多个父类的名字即可 11 class C(A,B): 12 pass 13 14 c1=C() 15 c1.e() #调用了A类的实例方法e:优先继承A类 16 17 #使用魔法属性__mro__或方法mro()查看继承的先后顺序 18 #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>] 19 print(C.mro()) 20 #(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>) 21 print(C.__mro__)
如果子类中和父类中的方法重名。优先使用子类的方法
1.7.3 多态
B并非一定是A的子类,但是如果B在用起来和A没有区别,那么B也可以代替A出席需要A的场合
任意一个类型,都可以代替目标类型出现
在使用的时候,数据能够按照期望的方式使用,就可以了
class A: name="" _age=0 __sex='男' @property def age(self): return self._age @age.setter def age(self,value): assert value>0 self._age=value def say(self): print(f"I am {self.name},{self._age} year old") class B(A): def b_say(self): print(f"I am {self.name},{self._age} years old") print("b是B的实例对象",isinstance(b,B)) print("b是A的实例对象",isinstance(b,A)) def hello(obj:A): #hello 函数需要A的实例参数作为参数 obj.say() #因为要调用say方法 hello(b)
1.8 数据类装饰器
可以使用对类的装饰器进行数据类的装饰
使用及特点:
- 首先需要导入dataclasses模块
- 使用dataclass装饰器
- 自动为类创建很多特殊方法,包括init魔法方法
- 不需要手动定义init魔法方法就可以直接使用
- 实例属性的另外一种定义格式
1 import dataclasses 2 3 @dataclasses.dataclass 4 class Person: 5 #通过数据类定义实例属性一定要写数据类型的申明 6 name:str="张三" 7 age:int=18 8 def playGame(self): 9 if self.age>=18: 10 print(f"{self.name}可以进网吧玩游戏") 11 else: 12 print(f"{self.name}你是未成年,不能进网吧玩游戏") 13 14 p=Person() 15 print(p.name) 16 print(p.age) 17 p.playGame()
1.9 练习
#1.通过类 设计一种动物,他们应该拥有不同的名字、体重、身高,每个动物都可以计算出自己的BMI class Animal: name="" weight=0 height=0 def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height def bmi(self): """ bmi=体重/(身高*身高 ) :return: """ return self.weight/(self.height*self.height) a=Animal("金毛",200,500) b=Animal("小喵",80,150) #2.根据面向对象的特性,创建一个可以被say函数作为参数的Cat类,使一下代码执行正确 class Dog: _msg="旺旺" def say(self): return self._msg # class Cat(Dog): #方式1:通过继承实现 # _msg="喵喵" class Cat: def say(self): #方式2:通过多态实现 return "喵喵" def say(obj): print(obj.say()) say(Dog()) say(Cat())