-
python面向对象
2018-09-13 21:49:34python面向对象精彩讲解视频,详细讲解了python在面向对象中的用途。 -
Python面向对象
2019-12-24 09:25:39Python面向对象1. 面向对象基础语法2. 初始化方法__init__3. 属性查找与绑定方法4. 跑步案例5. 家具案例6. 私有属性7. 继承7.1 面向对象的三大特性7.2 单继承7.2.1 继承的概念7.2.2 继承的语法7.2.3 方法的重写 1. ...Python面向对象
1. 面向对象基础语法
# -*- coding: utf-8 -*- # @Time : 2019/12/9 14:33 # @Author : 我就是任性-Amo # @FileName: 1.面向对象基础语法.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 """ 1.类: 类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用 特征被称为属性 行为被称为方法 类就相当于制造飞机时的图纸,是一个模板,是复制创建对象的 2.对象: 对象是由类创建出来的一个具体存在,可以直接使用 由哪一个类创建出来的对象,就拥有在哪一个类中定义的: 属性和方法 对象就相当于用图纸制造的飞机 在程序开发中,应该先有类,再有对象 3.类和对象的关系 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象 类只有一个,而对象可以有很多个 不同的对象之间属性 可能会各不相同 类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少 4.类的设计 在程序开发中,要设计一个类,通常需要满足一下三个要素: 类名: 这类事物的名字,满足大驼峰命名法(每一个单词的首字母大写并且单词与单词之间没有下划线) 属性: 这类事物具有什么样的特征 方法: 这类事物具有什么样的行为 4.1 类名的确定 名词提炼法 分析整个业务流程,出现的名词,通常就是找到的类 4.2 属性和方法的确定 对对象的特征描述,通常可以定义成属性 对象具有的行为(动词),通常可以定义成方法 提示: 需求中没有涉及的属性或者方法在设计类时,不需要考虑 """ # 第一个面向对象程序: 小猫爱吃鱼 小猫要喝水 # 这里的话按照需求首先是不需要定义属性的 class Cat: """这是一个猫类""" def eat(self): # self: 由哪一个对象调用的方法,方法内的self就是哪一个对象的引用 # 1.在类封装的方法内部,self就表示当前调用方法的对象自己 # 2.调用方法时,我们是不需要传递self参数的 解释器会自动帮我们传入 # 3.在方法内部 可以通过self.访问对象的属性 也可以通过self.调用其他的对象方法 print("%s 爱吃鱼" % self.name) def drink(self): print("小猫在喝水") # 创建对象 tom = Cat() # tom.eat() # 在设置属性之前 调用方法报错 tom.drink() # 在类的外部是可以直接通过.的方式去给对象添加属性的,但是不推荐 # 因为对象属性的封装应该封装在类的内部 tom.name = "Tom" # print(tom.name) tom.eat() # tom.eat(tom) # print(tom.__dict__) # 可以查看对象绑定了哪些属性 # print(Cat.__dict__) # 可以查看类绑定了哪些命名空间 lazy_cat = Cat() lazy_cat.name = "大懒猫" lazy_cat.eat() # 在类的外部通过变量名.访问对象的属性和方法 # 在类的封装方法中,通过self.访问对象的属性和方法
2. 初始化方法__init__
# -*- coding: utf-8 -*- # @Time : 2019/12/9 14:40 # @Author : 我就是任性-Amo # @FileName: 2.初始化方法.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 """ 初始化方法: 1.当使用类名()创建对象时,会自动执行以下操作: 为对象在内存中分配空间--创建对象 为对象的属性设置初始值--初始化方法(init) 2.__init__方法是专门用来定义一个类具有哪些属性的方法 """ # 在Cat中增加__init__方法,验证该方法在创建对象时会被调用 class Cat: """这是一个猫类""" # def __init__(self): # print("这是一个初始化方法") # # 定义用Cat类创建的猫对象都有一个name的属性 # self.name = "Tom" # 在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对_init__方法进行改造 # 把希望设置的属性值,定义成__init__方法的参数,在方法内部使用 # self.属性 = 形参 接收外部传递的参数 # 在创建对象时,使用类名(属性1, 属性2...) def __init__(self, name): self.name = name def eat(self): print("%s 爱吃鱼" % self.name) # 如果在开发中,希望使用print输出对象变量时,能够打印自定义的内容,就可以利用__str__这个内置方法了 def __str__(self): return "我是小猫: %s" % self.name # def __del__(self): # __del__:如果希望在对象被销毁前,再做一些事情,可以考虑此方法 def __del__(self): print("准备删除~~~") tom = Cat("Tom") # 创建对象时 会自动调用初始化__init__方法 tom.eat() lazy_cat = Cat("大懒猫") lazy_cat.eat() # 在Python中,使用print输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象, # 以及在内存中的地址(十六进制表示) print(tom) # <__main__.Cat object at 0x103959bd0> del lazy_cat print("-" * 50)
3. 属性查找与绑定方法
# -*- coding: utf-8 -*- # @Time : 2019/12/9 15:00 # @Author : 我就是任性-Amo # @FileName: 3.属性查找与绑定方法.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 # 属性查找与绑定方法 class Person: """这是一个猫类""" # nationality = "中国" # def __init__(self, name, age, address, nationality): # self.name = name # self.age = age # self.address = address # self.nationality = nationality 给对象绑定国籍属性 def __init__(self, name, age, address): self.name = name self.age = age self.address = address def eat(self): print(f"{self.name}正在吃饭~~~") # 1.分别创建amo对象和paul对象 # amo = Person("amo", 18, "重庆市沙坪坝区", "美国") # paul = Person("paul", 22, "重庆市沙坪坝区", "中国") # 属性: 首先会从对象本身先去查找,如果对象本身没有绑定该属性,则会从类中去找 # print(id(amo.nationality)) # 4406863312 # print(id(paul.nationality)) # 4406863504 amo = Person("amo", 18, "重庆市沙坪坝区") paul = Person("paul", 22, "重庆市沙坪坝区") # 如果对象身上没有绑定该属性 则去在类中查找 类属性是被所有对象共用的 所以返回的地址值是一致的. # print(id(amo.nationality)) # 4461503952 # print(id(paul.nationality)) # 4461503952 # 如果在类中查找该属性 还是没有 程序就会报错 # print(id(paul.nationality)) # AttributeError: 'Person' object has no attribute 'nationality' # 函数属性:是绑定给对象使用的,绑定到不同的对象是不同的绑定方法,对象调用绑定方法时,会把对象本身作为一个参数进行传入,传给self print(amo.eat) # <bound method Person.eat of <__main__.Person object at 0x10e9c7350>> print(paul.eat) # <bound method Person.eat of <__main__.Person object at 0x10e9c73d0>> # 可以看到 两个的绑定方法的内存地址值是不一致的 print(Person.__dict__) print(id(Person.eat(amo))) # 4393136216 print(id(Person.eat(paul))) # 4393136216
4. 案例
4.1 跑步案例
# -*- coding: utf-8 -*- # @Time : 2019/12/10 16:28 # @Author : 我就是任性-Amo # @FileName: 4.练习1.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 # 封装是面向对象编程的一大特点 # 面向对象编程的第一步:将属性和方法封装到一个抽象的类中 # 外界使用类创建对象,然后让对象调用方法 # 对象方法的细节都被封装在类的内部 """ 需求:amo和jerry都爱跑步 amo体重75.0公斤 jerry体重45.0 公斤 每次跑步都会减少0.5公斤 每次吃东西都会增加1公斤 """ class Person: def __init__(self, name, weight): self.name = name self.weight = weight def run(self): self.weight -= 0.5 print("%s 爱锻炼,跑步锻炼身体" % self.name) def eat(self): self.weight += 1 # 注意: 在对象的方法内部,是可以直接访问对象的属性的 print("%s 是吃货,吃完这顿在减肥" % self.name) def __str__(self): return "我的名字是%s,体重%.2f公斤" % (self.name, self.weight) amo = Person("amo", 75.0) amo.run() amo.eat() amo.eat() print(amo) print("-" * 50) jerry = Person("jerry", 45.0) # 同一个类创建的多个对象之间,属性互不干扰 jerry.run() jerry.eat() jerry.eat() print(jerry)
代码运行结果如下:
4.2 家具案例
# -*- coding: utf-8 -*- # @Time : 2019/12/20 09:26 # @Author : 我就是任性-Amo # @FileName: 5.家具案例.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 """ 需求: 1.房子(House)有户型、总面积和家具名称列表 新房子没有任何的家具 2.家具(HouseItem)有名字和占地面积,其中 席梦思(bed)占地4平米 衣柜(chest)占地2平米 餐桌(table)占地1.5平米 3.将以上三件家具添加到房子中 4.打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表 """ # 分析: 要去添加家具 首先肯定要有一个家具类 被使用的类 通常应该先被开发 class HouseItem: def __init__(self, name, area): self.name = name # 家具名称 self.area = area # 占地面积 def __str__(self): return "[%s] 占地面积 %.2f" % (self.name, self.area) bed = HouseItem("bed", 4) # 席梦思 chest = HouseItem("chest", 2) # 衣柜 table = HouseItem("table", 1.5) # 餐桌 print(bed) print(chest) print(table) class House: def __init__(self, apartment, area): self.apartment = apartment # 户型 self.total_area = area # 总面积 self.free_area = area # 剩余面积开始的时候和总面积是一样的 self.furniture_list = [] # 家具名称列表 默认是没有放置任何家具的 def add_item(self, furniture): """添加家具""" print(f"添加:{furniture.name}") # 判断家具的面积是否超过剩余面积,如果超过,提示不能添加这件家具 if furniture.area > self.free_area: print("房间剩余面积不够,不能添置这件家具...") return self.furniture_list.append(furniture.name) # 将家具名称追加到家具名称列表中 self.free_area -= furniture.area # 用房子的剩余面积-家具面积 def __str__(self): return f"房子户型为:{self.apartment},总面积为:{self.total_area}," \ f"剩余面积为{self.free_area},家具列表为:{self.furniture_list}" if __name__ == "__main__": my_house1 = House("两室一厅", 60) my_house1.add_item(bed) my_house1.add_item(chest) my_house1.add_item(table) print(my_house1) my_house2 = House("一室一厅", 10) my_house2.add_item(bed) my_house2.add_item(chest) my_house2.add_item(table) my_house2.add_item(bed) print(my_house2)
代码运行结果如下:
6. 私有属性
# -*- coding: utf-8 -*- # @Time : 2019/12/20 10:08 # @Author : 我就是任性-Amo # @FileName: 6.私有属性.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 """ 应用场景: 在实际开发中,对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问到 私有属性就是对象不希望公开的属性 私有方法就是对象不希望公开的方法 定义的方式: 在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或方法 在java中的话是使用private关键字 """ class Women: def __init__(self, name): self.name = name self.__age = 18 def __secret(self): print("我的年龄是 %d" % self.__age) xiao_fang = Women("小芳") # 私有属性外界不能直接访问 # print(xiao_fang.__age) 报错:AttributeError: 'Women' object has no attribute '__age' # 私有方法,外部不嫩直接调用 # xiao_fang.__secret() # 在日常开发中,不要使用这种方式,访问对象的私有属性或私有方法 # 在Python中,并没有真正意义的私有 # 在给属性、方法 命名时,实际是对名称做了一些特殊处理,使得外界无法访问到 # 处理方式:在名称前面加上 _类名 => _类名__名称 print(xiao_fang.__dict__) # 查看名称空间 print(xiao_fang._Women__age) # print(Women.__dict__) xiao_fang._Women__secret()
程序运行结果如下:
7. 继承
7.1 面向对象的三大特性
- 封装: 根据职责将属性和方法封装到一个抽象的类中
- 继承: 实现代码的重用,相同的代码不需要重复的编写
- 多态: 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
7.2 单继承
7.2.1 继承的概念
继承的概念: 子类拥有父类的所有方法和属性
# ---------不使用继承开发动物和狗--------------- class Animal: def eat(self): print("吃") def drink(self): print("喝") def run(self): print("跑") def sleep(self): print("睡") class Dog: def eat(self): # 不使用继承重复书写的代码太多 print("吃") def drink(self): print("喝") def run(self): print("跑") def sleep(self): print("睡") def bark(self): print("汪汪叫") # 创建一个狗对象 wang_cai = Dog() wang_cai.eat() wang_cai.drink() wang_cai.run() wang_cai.sleep() wang_cai.bark() # # ---------使用继承开发动物和狗--------------- class Animal: def eat(self): print("吃---") def drink(self): print("喝---") def run(self): print("跑---") def sleep(self): print("睡---") class Dog(Animal): def bark(self): print("汪汪叫---") # 创建一个狗对象 wang_cai = Dog() wang_cai.eat() wang_cai.drink() wang_cai.run() wang_cai.sleep() wang_cai.bark() # 直接享受父类中已经封装好的方法 不需要再次开发
7.2.2 继承的语法
class 类名(父类名): pass
- 子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
- 子类中应该根据职责,封装子类特有的属性和方法
- 专业术语
- Dog类是Animal类的子类,Animal类是Dog类的父类,Dog类从Animal类继承
- Dog类是Animal类的派生类,Animal类是Dog类的基类,Dog类从Animal类派生
- 继承的传递性
C类从B类继承,B类又从A类继承,那么C类就具有B类和A类的所有属性和方法
# -*- coding: utf-8 -*- # @Time : 2019/12/21 10:52 # @Author : 我就是任性-Amo # @FileName: 9.继承的传递性.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 class Animal: def eat(self): print("吃---") def drink(self): print("喝---") def run(self): print("跑---") def sleep(self): print("睡---") class Dog(Animal): def bark(self): print("汪汪叫") class XiaoTianQuan(Dog): def fly(self): print("我会飞") # 创建一个哮天犬的对象 xtq = XiaoTianQuan() xtq.fly() xtq.bark() xtq.eat()
程序运行结果如下:
7.2.3 方法的重写
- 子类拥有父类的所有方法和属性
- 子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
- 应用场景: 当父类的方法实现不能满足子类需求时,可以对方法进行 重写(override)
- 重写父类的方法有两种情况
- 覆盖父类的方法
如果在开发中,父类的方法实现和子类的方法实现,完全不同。就可以使用覆盖的方式,在子类中重新编写父类的方法实现。具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现。重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法
# -*- coding: utf-8 -*- # @Time : 2019/12/21 10:55 # @Author : 我就是任性-Amo # @FileName: 10.覆盖父类方法.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 class Animal: def eat(self): print("吃---") def drink(self): print("喝---") def run(self): print("跑---") def sleep(self): print("睡---") class Dog(Animal): def bark(self): print("汪汪叫") class XiaoTianQuan(Dog): def fly(self): print("我会飞") def bark(self): print("叫得跟神一样...") xtq = XiaoTianQuan() # 如果子类中,重写了父类的方法 # 在使用子类对象调用方法时,会调用子类中重写的方法 xtq.bark()
- 对父类方法进行扩展
如果在开发中,子类的方法实现中包含父类的方法实现。父类原本封装的方法实现是子类方法的一部分。就可以使用扩展的方式:
1. 在子类中重写父类的方法
2. 在需要的位置使用super().父类方法
来调用父类方法的执行
3. 代码其他的位置针对子类的需求,编写子类特有的代码实现- 关于super
- 在
Python
中super
是一个特殊的类 super()
就是使用super
类创建出来的对象- 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
- 在Python 2.x时,如果需要调用父类的方法,还可以使用以下方式:父类名.方法(self)
- 这种方式,目前在 Python 3.x 还支持这种方式
- 这种方法不推荐使用,因为一旦父类发生变化,方法调用位置的类名同样需要修改
- 在开发时,父类名和 super()两种方式不要混用
- 如果使用当前子类名调用方法,会形成递归调用,出现死循环
- 在
# -*- coding: utf-8 -*- # @Time : 2019/12/21 11:03 # @Author : 我就是任性-Amo # @FileName: 11.扩展父类方法.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 class Animal: def eat(self): print("吃---") def drink(self): print("喝---") def run(self): print("跑---") def sleep(self): print("睡---") class Dog(Animal): def bark(self): print("汪汪叫") class XiaoTianQuan(Dog): def fly(self): print("我会飞") def bark(self): # 1.针对子类特有的需求,编写代码 print("神一样的叫唤...") # 2.使用super().调用原本在父类中封装的方法 # super().bark() # 父类名.方法(self) Dog.bark(self) # 但是一旦父类名改变 这里也要随之改变 # 注意: 如果使用子类调用方法,会出现递归调用-死循环! # XiaoTianQuan.bark(self) # 3. 增加其他子类的代码 print("$%^*%^$%^#%$%") xtq = XiaoTianQuan() # 如果子类中,重写了父类的方法 # 在使用子类对象调用方法时,会调用子类中重写的方法 xtq.bark()
程序运行结果如下:
7.2.4 父类的私有属性和私有方法
- 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
- 子类对象可以通过父类的公有方法间接访问到私有属性或私有方法
- 示例:
B
的对象不能直接访问__num2
属性B
的对象不能在demo
方法内访问__num2
属性B
的对象可以在demo
方法内,调用父类的test
方法- 父类的
test
方法内部,能够访问__num2
属性和__test
方法
# -*- coding: utf-8 -*- # @Time : 2019/12/21 11:11 # @Author : 我就是任性-Amo # @FileName: 13.父类的公有方法.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 class A: def __init__(self): self.num1 = 100 self.__num2 = 200 def __test(self): print("私有方法 %d %d" % (self.num1, self.__num2)) def test(self): print("父类的公有方法 %d" % self.__num2) self.__test() class B(A): def demo(self): # 1. 在子类的对象方法中,不能访问父类的私有属性 # print("访问父类的私有属性 %d" % self.__num2) # 2. 在子类的对象方法中,不能调用父类的私有方法 # self.__test() # 3. 访问父类的公有属性 print("子类方法 %d" % self.num1) # 4. 调用父类的公有方法 self.test() pass # 创建一个子类对象 b = B() print(b) b.demo() # 在外界访问父类的公有属性/调用公有方法 # print(b.num1) # b.test() # 在外界不能直接访问对象的私有属性/调用私有方法 # print(b.__num2) # b.__test()
7.3 多继承
7.3.1 概念及语法
- 子类可以拥有多个父类,并且具有所有父类的属性和方法
- 例如:孩子会继承自己 父亲和母亲的特性
- 语法:
class 子类名(父类名1, 父类名2...): pass
class A: def test(self): print("test 方法") class B: def demo(self): print("demo 方法") class C(A, B): """多继承可以让子类对象,同时具有多个父类的属性和方法""" pass # 创建子类对象 c = C() c.test() c.demo()
7.3.2 多继承使用注意事项
- 如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法呢?
提示:开发时,应该尽量避免这种容易产生混淆的情况。 如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承
class A: def test(self): print("A --- test 方法") def demo(self): print("A --- demo 方法") class B: def test(self): print("B --- test 方法") def demo(self): print("B --- demo 方法") class C(B, A): """多继承可以让子类对象,同时具有多个父类的属性和方法""" pass # 创建子类对象 c = C() c.test() c.demo() # 确定C类对象调用方法的顺序 print(C.__mro__)
7.3.3 新式类与旧式(经典)类
object是Python为所有对象提供的基类,提供有一些内置的属性和方法,可以使用dir函数查看
在Python3中,使用dir函数查看效果图如下:
在Python2中,使用dir函数查看效果如下:
- 新式类:以
object
为基类的类,推荐使用 - 经典类:不以
object
为基类的类,不推荐使用 - 在Python3.x中定义类时,如果没有指定父类,会默认使用object 作为该类的基类——Python 3.x 中定义的类都是新式类
- 在Python2.x中定义类时,如果没有指定父类,则不会以object作为基类
- 为了保证编写的代码能够同时在Python 2.x和Python3.x运行!今后在定义类时,如果没有父类,建议统一继承自object
7.3.4 继承的实现原理
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
- 在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如果继承了多个父类,那么属性的查找方式有两种,分别是:深度优先和广度优先
示例代码:
# -*- coding: utf-8 -*- # @Time : 2019/12/24 09:30 # @Author : 我就是任性-Amo # @FileName: 15.新式类与经典类.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D, E): # def test(self): # print('from F') pass f1 = F() f1.test() print(F.__mro__) # 只有新式才有这个属性可以查看线性列表,经典类没有这个属性 # import inspect # 使用inspect模块中的getmro()方法可以查看python2.x的mro顺序
MRO顺序如下:
# 新式类继承顺序如下: # <class '__main__.F'>, # <class '__main__.D'>, # <class '__main__.B'>, # <class '__main__.E'>, # <class '__main__.C'>, # <class '__main__.A'>, # <class 'object'> # 经典类继承顺序如下: # <class __main__.F at 0x1033b3c18>, # <class __main__.D at 0x1033b3b48>, # <class __main__.B at 0x1033b3a78>, # <class __main__.A at 0x1033b3a10>, # <class __main__.E at 0x1033b3bb0>, # <class __main__.C at 0x1033b3ae0>
8. 多态
不同的子类对象调用相同的父类方法,产生不同的执行结果
- 多态可以增加代码的灵活度
- 以继承和重写父类方法为前提
- 是调用方法的技巧,不会影响到类的内部设计
8.1 多态案例演练
- 在Dog类中封装方法 game 普通狗只是简单的玩耍
- 定义XiaoTianDog继承自Dog,并且重写game方法 哮天犬需要在天上玩耍
- 定义 Person 类,并且封装一个和狗玩的方法 在方法内部,直接让狗对象调用game方法
# -*- coding: utf-8 -*- # @Time : 2019/12/24 15:20 # @Author : 我就是任性-Amo # @FileName: 17.多态.py # @Software: PyCharm # @Blog :https://blog.csdn.net/xw1680 class Dog(object): def __init__(self, name): self.name = name def game(self): print("%s 蹦蹦跳跳的玩耍..." % self.name) class XiaoTianDog(Dog): def game(self): print("%s 飞到天上去玩耍..." % self.name) class Person(object): def __init__(self, name): self.name = name def game_with_dog(self, dog): print("%s 和 %s 快乐的玩耍..." % (self.name, dog.name)) # 让狗玩耍 dog.game() # 1. 创建一个狗对象 # wang_cai = Dog("旺财") wang_cai = XiaoTianDog("飞天旺财") # 2. 创建一个小明对象 xiao_ming = Person("小明") # 3. 让小明调用和狗玩的方法 xiao_ming.game_with_dog(wang_cai)
程序运行结果如下:
8.2 案例总结
- Person类中只需要让狗对象调用game方法,而不关心具体是什么狗 game方法是在Dog父类中定义的
- 在程序执行时,传入不同的狗对象实参,就会产生不同的执行效果
- 多态更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化!
9. 类属性和类方法
9.1 类的结构
9.1.1 术语——实例
- 使用面相对象开发,第1步是设计类
- 使用类名()创建对象,创建对象的动作有两步:
- 在内存中为对象分配空间
- 调用初始化方法
__init__
为对象初始化
- 对象创建后,内存中就有了一个对象的实实在在的存在—实例
因此,通常也会把:- 创建出来的对象叫做类的实例
- 创建对象的动作叫做实例化
- 对象的属性叫做实例属性
- 对象调用的方法叫做实例方法
在程序执行时:对象各自拥有自己的实例属性 调用对象方法,可以通过 self.访问自己的属性和调用自己的方法
结论:
- 每一个对象都有自己独立的内存空间,保存各自不同的属性
- 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部
9.1.2 类是一个特殊的对象
Python中一切皆对象,class AAA: 定义的类属于类对象 obj1 = AAA() 属于实例对象
- 在程序运行时,类同样会被加载到内存
- 在Python中,类是一个特殊的对象–类对象
- 在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例
- 除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法
- 类属性
- 类方法
- 通过类名
.
的方式可以访问类的属性或者调用类的方法
9.2 类属性和实例属性
9.2.1 概念和使用
- 类属性就是给类对象中定义的属性
- 通常用来记录与这个类相关的特征
- 类属性不会用于记录具体对象的特征
示例需求:
- 定义一个工具类
- 每件工具都有自己的name
- 需求——知道使用这个类,创建了多少个工具对象?
class Tool(object): # 使用赋值语句,定义类属性,记录创建工具对象的总数 count = 0 def __init__(self, name): self.name = name # 针对类属性做一个计数+1 Tool.count += 1 # 创建工具对象 tool1 = Tool("斧头") tool2 = Tool("榔头") tool3 = Tool("铁锹") # 知道使用 Tool 类到底创建了多少个对象? print("现在创建了 %d 个工具" % Tool.count)
9.2.2 属性的获取机制
在Python中属性的获取存在一个向上查找机制
因此,要访问类属性有两种方式:- 类名.类属性
- 对象.类属性 (不推荐)
如果使用
对象.类属性 = 值
赋值语句,只会给对象添加一个属性,而不会影响到类属性的值9.3 类方法和静态方法
9.3.1 类方法
- 类属性就是针对类对象定义的属性
- 使用赋值语句在class关键字下方可以定义类属性
- 类属性用于记录与这个类相关的特征
- 类方法就是针对类对象定义的方法
- 在类方法内部可以直接访问类属性或者调用其他的类方法
- 语法如下
@classmethod def 类方法名(cls): pass
- 类方法需要用@classmethod来标识,告诉解释器这是一个类方法
- 类方法的第一个参数应该是cls
- 由哪一个类调用的方法,方法内的
cls
就是哪一个类的引用 - 这个参数和实例方法的第一个参数是
self
类似 - 提示使用其他名称也可以,不过习惯使用
cls
- 由哪一个类调用的方法,方法内的
- 通过
类名.
调用类方法,调用方法时,不需要传递cls
参数 - 在方法内部
- 可以通过
cls.
访问类的属性 - 也可以通过
cls.
调用其他的类方法
- 可以通过
# 定义一个工具类 每件工具都有自己的name 需求—在类封装一个show_tool_count的类方法,输出使用当前这个类,创建的对象个数 @classmethod def show_tool_count(cls): """显示对象工具的总数""" print("工具对象的总数 %d" % cls.count) # 在类方法内部,可以直接使用cls访问类属性或者调用类方法
9.3.2 静态方法
- 在开发时,如果需要在类中封装一个方法,这个方法:
- 既不需要访问实例属性或者调用实例方法
- 也不需要访问类属性或者调用类方法
- 这个时候,可以把这个方法封装成一个静态方法
- 语法如下
@staticmethod def 静态方法名(): pass
- 静态方法需要用
@staticmethod
来标识,告诉解释器这是一个静态方法 - 通过
类名.
调用静态方法
class Dog(object): # 狗对象计数 dog_count = 0 @staticmethod def run(): # 不需要访问实例属性也不需要访问类属性的方法 print("狗在跑...") def __init__(self, name): self.name = name
9.3.3 方法综合案例
- 设计一个Game类
- 属性:
- 定义一个类属性
top_score
记录游戏的历史最高分 - 定义一个实例属性
player_name
记录当前游戏的玩家姓名
- 定义一个类属性
- 方法:
- 静态方法
show_help
显示游戏帮助信息 - 类方法
show_top_score
显示历史最高分 - 实例方法
start_game
开始当前玩家的游戏
- 静态方法
- 主程序步骤
- 查看帮助信息
- 查看历史最高分
- 创建游戏对象,开始游戏
class Game(object): # 游戏最高分,类属性 top_score = 0 @staticmethod def show_help(): print("帮助信息:让僵尸走进房间") @classmethod def show_top_score(cls): print("游戏最高分是 %d" % cls.top_score) def __init__(self, player_name): self.player_name = player_name def start_game(self): print("[%s] 开始游戏..." % self.player_name) # 使用类名.修改历史最高分 Game.top_score = 999 # 1. 查看游戏帮助 Game.show_help() # 2. 查看游戏最高分 Game.show_top_score() # 3. 创建游戏对象,开始游戏 game = Game("小明") game.start_game() # 4. 游戏结束,查看游戏最高分 Game.show_top_score() # 如果方法内部即需要访问实例属性,又需要访问类属性,应该定义成什么方法? # 应该定义实例方法 # 因为,类只有一个,在实例方法内部可以使用类名.访问类属性
9.3.4 案例小结
- 实例方法 —— 方法内部需要访问实例属性
- 实例方法内部可以使用
类名.
访问类属性
- 实例方法内部可以使用
- 类方法 —— 方法内部只需要访问类属性
- 静态方法 —— 方法内部,不需要访问实例属性和类属性
-
python 面向对象
2016-09-19 13:44:02一.[python] 类常用的内置方法 二.Python 面向对象编程(一) 三.Python面向对象编程(二)展开全文 -
Python 面向对象
2020-02-04 13:13:33Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。 如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些...Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。
如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。
接下来我们先来简单的了解下面向对象的一些基本特征。
面向对象技术简介
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量, 用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 局部变量:定义在方法中的变量,只作用于当前实例的类。
- 实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
- 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
- 实例化:创建一个类的实例,类的具体对象。
- 方法:类中定义的函数。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。