面向对象 (OOP)
相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法。
- 在完成某个需求前,首先确定职责--要做的事情(方法)
- 根据职责确定。不同的对象,在对象内部封装不同的方法(多个)
- 最后完成的代码,就是顺序地让不同的对象调用不同的方法
特点:
- 注重对象和职责,不同的对象承担不同的职责;
- 更加适合应对复杂的需求变化,是专门应用复杂项目开发,提供的固定套路;
- 需求在面向过程继承上,再学习一些面向对象的语法;
类和对象
类是一群具有相同特征或者行为的事物的统称,是抽象的,不能直接使用
特征被称为属性、行为被称为方法
类就相当于制造飞机时的图纸,是一个模板,是负责创建对象的。
对象是由类创建出来的一个具体存在,可以直接使用;
由哪个类创建出来的对象,就拥有哪个类中定义的属性和方法;
对象就相当于用图纸制造的飞机;
类有一个,对象可以有多个,不同的对象之间的属性可能不相同。
类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少。
类名需满足大驼峰命名法(1、每个单词首字母大写;2、单词与单词之间没有下划线)
Dir 内置函数
使用内置函数 dir
传入标识符/数据,可以查看对象内的所有属性和方法
提示__方法名__
格式的方法是 python 提供的内置方法/属性
类的基本语法
class Cat: # 大驼峰命名,创建Cat类来供对象调用
def eat(self): # 创建eat方法供调用
print("小猫爱吃鱼")
def drink(self): # self参数对应创建的对象,哪一个对象调用,self就是哪一个对象的引用;
print("%s要喝水" % self.name)
tom = Cat() # 创建Tom对象引用Cat类
tom.name = "Tom" # 越过类单独为对象创建属性,不推荐使用。在代码下方添加调用类的方法可在控制台输出
tom.eat() # tom对象调用eat方法
tom.drink() # 输出结果"小猫要喝水"
print(tom) # 能够输出内置函数main、调用的类和在内存中十六进制的地址
addr = id(tom) # 将tom对象的id赋值
print("%d" % addr) # 输出tom在内存中的十进制地址,可用%x来输出十六进制地址
引用概念
使用类创建对象后,tom 变量中仍然记录的是对象在内存中的地址;也就是 tom 变量引用了新建的猫对象;
使用 print 输出对象变量,默认情况下,是能够输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示);
在创建出来的对象后增加 .<属性>
即可给对象设置属性,但不推荐使用,因为这时这个对象已经跟类对不上了;
类和实例变量
一般来说,实例变量用于每个实例的唯一数据,而类变量用于类的所有实例共享的属性和方法
Self 参数
由哪一个对象调用的方法,方法内的 self
就是哪一个对象的引用;
调用方法时,self
就表示当前调用方法的对象自己;调用方法时,开发不需要传递 self
参数;
在方法内部,可以通过 self
访问对象的属性;也可以通过 self.
用其它的对象方法(self
与类好像是强关联,通过类创建出对象,self
对应这个对象,可以调用这个对象的各种方法);
类方法和静态方法
@classmethod
:操作类变量,第一个参数是cls
@staticmethod
:独立于类和实例的工具方法
class MyClass:
count = 0
@classmethod
def get_count(cls):
return cls.count
@staticmethod
def util_method():
print("工具方法")
初始化方法
使用 类名()
创建对象时,会自动执行:
- 为对象在内存中分配空间--创建对象;
- 为对象的属性设置初始值--初始化方法 (init),该初始化方法是
__init__
方法,是对象的内置方法;|__init__
方法是专门用来定义一个类具有哪些属性的方法;__del__
方法是对象从内存中销毁前自动调用的方法
使用 print 输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)
应用场景
__init__
改造初始化方法,可以让创建对象更加灵活;
__del__
如果希望在对象被销毁前,再做一些事情,可以考虑用该方法;
__str__
如果希望用 print 输出对象变量时,能够打印自定义内容,可以考虑用该方法;
__getitem__
实现索引访问
__iter__
实现迭代功能
__enter__/__exit__
实现上下文管理器
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 "字符串"
tom = Cat("Tom") # 输出"Tom来了" \n "Tom去了"
print(tom) # 输出"字符串",否则默认输出引用的类和对应的十六进制内存地址
__new__
和 __init__
的区别
class MyClass:
def __new__(cls, *args):
print("创建实例")
return super().__new__(cls)
def __init__(self):
print("初始化实例")
封装、继承、多态
- 封装根据职责将属性和方法封装到一个抽象的类中;
- 继承实现代码的重用,相同的代码不需要重复编写;
- 多态不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活性;
封装
- 封装是面向对象编程的一大特点;
- 面向对象编程的第一步——将属性和方法封装到一个抽象的类中;
- 外界使用类创建对象,然后让对象调用方法;
- 对象方法的细节都被封装在类的内部;
在对象的方法内部,可以直接访问对象的属性。
一个对象的属性可以是另外一个类创建的对象;
定义属性时,若不知道设置什么初始值,可以设置为None,这是一个关键字;
身份运算符
用于比较两个对象的内存地址是否一致——是否是同一个对象的引用;
is
和 ==
的区别:
is
用于判断两个变量引用对象是否是同一个;==
用于判断引用变量的值是否相等;
私有属性和私有方法
只需要在属性或者方法前面增加两个下划线,就可以成为私有属性/私有方法;但是在 python 中并没有真正意义上的私有,只是对名称做了一些特殊处理使得外界及子类无法访问到。 在属性和方法前加上 _类名
可以访问到,但在实际开发中不推荐使用。
子类对象不能在自己的方法内部直接访问父类的私有属性或私有方法,但能通过调用父类的公有方法间接访问父类的私有属性或私有方法;
应用场景:
对象的某些属性或方法只希望在对象的内部使用,而不希望在外部被访问到;
属性装饰器(@property)
控制属性的访问和修改 实现 getter/setter 方法
class Person:
def __init__(self, name):
self._name = name
@property
def name(self): # Getter
return self._name
@name.setter
def name(self, value): # Setter
if len(value) > 3:
self._name = value
继承
子类具有父类所有的方法和属性。
class Animal: # 先定义类,类名遵循大坨峰
def eat(self): # 方法需self
print("吃")
def drink(self):
print("喝")
class Dog(Animal): # 格式class 类名(父类名),该Dog类继承animal的所有方法和属性
# 若继承与重写冲突,以重写为准。
# Dog类是Animal的子类/派生类,Animal是Dog的父类/基类,Dog类从Animal类继承/派生
方法重写
应用场景:当父类的方法不能满足子类的需求时,可以对方法进行重写 (override)
覆盖
若父类的方法与子类的方法完全不同,可在子类中重新定义一个与父类同名的方法并实现;
扩展
若子类的方法中包含父类的代码实现(父类原本封装的代码实现是子类方法的一部分)可实现扩展的方式:在子类需要的位置使用super().父类方法
来调用父类方法的执行;
在 python 中,super()
是一个特殊的类;super()
就是使用 super类
创建出来的对象;最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
class Animal: # 定义第一个类不需要()
@staticmethod # 定义静态函数需要用这个注释
def eat():
print("吃")
# 每个行为之间用空行分隔
@staticmethod
def drink():
print("吃")
# 每个类之间用两个空行分隔
class Dog(Animal): # Dog类继承名为Animal的父类
def bark(self):
print("汪汪")
class XiaoTianQ(Dog):
@staticmethod
def fly(post): # 正常行为应该要有self,这里不知道怎么回事不需要
print("用%s飞" % post)
def bark(self): # 对XiaoTianQ中的bark方法进行重写
print("牛逼", end='')
super().bark() # 调用父类的方法,等同于super(XiaoTianQ, self).bark()也等同于Dog.bark(self),不过最后一种不推荐使用,因为修改了父类之后比较麻烦虽然我也不知道麻烦在哪儿
sirius = XiaoTianQ()
sirius.bark()
sirius.fly('翅膀')
牛逼汪汪 # 控制台输出
用翅膀飞 # 控制台输出
单继承
看上面即可,以下为手撸测试代码
class Animal:
post: object
def __init__(self, name):
self.name = name
def eat(self, post):
self.post = post
print("%s吃大便%s吃" % (self.name, self.post))
self.__drink()
def __drink(self):
print("%s喝" % self.name)
class Dog(Animal):
def __init__(self, bark):
super(Dog, self).__init__('yy') # 继承Animal的超类,使Animal中的name参数的初始化值为bark
self.post = bark
def bark(self):
print("%s汪汪" % self.name)
def fly(self):
print("你信不信我%s给你看" % self.bark()) # 调用方法输出的值是None
dog = Animal(name='xx')
dog.eat(post='站着')
dog1 = Dog("飞")
dog1.fly()
xx吃大便站着吃
xx喝
yy汪汪
你信不信我None给你看
多继承
子类可以拥有多个父类,并且拥有所有父类的属性和方法;若不同的父类存在同名的方法或属性,子类对象在调用方法时,会调用第一个父类的方法(因为 python 上往下执行,出现这种情况需要尽量避免)
class son(dad1 ,dad2): # 与参数语法格式一致
新式类和旧式(经典)类
新式类和经典类在多继承时,会影响到方法的搜索顺序
class C(B, A):
pass
print(C.__mro__) # 输出C类的搜索顺序
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>) # C类继承至B类和A类,且python3.x默认以object为基类
object
是 python 为所有对象提供的基类,提供有一些内置的属性和方法,可以使用 dir
函数查看;不推荐使用旧式类;为了保证编写的代码能同时在 python2.x 和 pyhton3.x 运行,推荐在定义类时,如果没有父类,统一继承至 object
抽象基类(ABC)
使用 abc
模块定义抽象类
强制子类实现特定方法
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def sound(self):
pass
class Dog(Animal):
def sound(self): # 必须实现抽象方法
print("汪汪")
多态
根据不同的子类对象调用相同的父类方法,产生不同的执行结果。以继承和重写父类方法为前提;是调用方法的技巧,不会影响到类的内部设计;
class Person(object):
def __init__(self, name): # 构造函数
self.name = name
def game_with_dog(self, dog):
# print("%s 和 %s 快乐的玩耍" % (self.name, dog.name))
print("%s和%s快乐的玩耍" % (self.name, dog.name))
dog.game() # Person类中只需要让狗对象调用game方法,而不关心具体是什么狗
class Dog(object):
def __init__(self, name):
self.name = name
def game(self):
print("%s进行玩耍" % self.name)
class XiaoTianQuan(Dog): # 继承了Dog父类的__init__参数,所有就不需要再写了
def game(self): # 因为不满足实际需要,使用多态的方法,让不同的对象调用父类方法,产生不同的结果
print("%s真会玩还会飞" % self.name)
wangcai = Dog('普通的小狗') # 放开该注释,输出"小明和普通的小狗快乐的玩耍\n普通的小狗进行玩耍"
# wangcai = XiaoTianQuan('哮天犬') # 放开该注释,输出"小明和哮天犬快乐的玩耍\n哮天犬真会玩"
person = Person('小明')
person.game_with_dog(wangcai)
鸭子类型
Python 多态的核心思想 " 当看到一只鸟走起来像鸭子、游泳像鸭子、叫起来像鸭子,那么这只鸟就可以被称为鸭子 " 关注对象行为而非具体类型
def start(device):
device.power_on() # 只要对象有power_on方法即可
class Phone:
def power_on(self):
print("手机开机")
class Computer:
def power_on(self):
print("电脑启动")
特殊属性 & 方法
名称 | 作用 | |
---|---|---|
特殊属性 | dict | 通过字典形式输出类/实例对象绑定的所有属性方法 |
特殊方法 | len() | 通过重写 len() 方法,让内置函数 len 的参数可以是自定义类型 |
add() | 通过重写 add() 方法,可使用自定义函数对象具有 "+" 功能 | 通过重写 add() 方法,可使用自定义函数对象具有 "+" 功能 |
组合
组合是除继承外的另一种代码复用方式 通过将其他类的实例作为属性来构建复杂对象
class Engine:
def start(self):
print("引擎启动")
class Car:
def __init__(self):
self.engine = Engine() # 组合关系
def drive(self):
self.engine.start()
print("汽车行驶")