7. 面向对象
什么是?
在 Python 里,对象(object)就是内存中一个有类型、有值、能操作的数据实体;一个手机、一辆车都是对象
面向对象
一种以对象为中心去思考和组织代码的方式,它更关注: “谁来做这件事”
面向过程
关注过程
类(class)
用来描述一类事物的“模板”,它规定了一类事物所具有的【属性】和【行为】
实例(instance)
根据【类】创建出来的一个具体的对象就是实例(实例对像)
类是抽象的、实例是具体的
实例化
根据类“制造出”对象的过程,就是实例化
定义一个类
在类中定义了函数时,这个函数我们称为方法
__init__: 称为初始化方法,主要作用是给当前创建实例化对象添加属性;self表示正在创建的实例对象;__init__ 实际化方法,python会自动调用
__init__里面的属性称为实例属性,只能通过实例访问,不能通过类访问;每个实例都有自己的【独一份】实例nttn,各个实例属性是互不干扰的
class Person:
# __init__: 实际化就去,主要作用:给当前正在创建的实际对象添加属性
# self 表示正在创建的实例对象
# __init__ python会自动去调用
def __init__(self, name, age):
# 下面这些都是实例属性,只能通过实例问,不能通过类访问
# 每个实例都有自己的【独一份】实例nttn,各个实例属性是互不干扰的
self.name = name
self.age = age类的__dict__属性
可以查找到类上所有的属性
实例上也有__dict__属性
创建实例
创建实例、查看属性、修改属性、追加属性、查看全部属性
class Person:
# __init__: 实际化就去,主要作用:给当前正在创建的实际对象添加属性
# self 表示正在创建的实例对象
# __init__ python会自动去调用
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("huangcy", 18)
print(p.name) # huangcy
print(p.age) # 18
# 修改属性
p.age = 33
# 追加属性
p.height = 176
# 通守__dict__ 可以查看实际上所有的属性
print(p.__dict__)
# 通过type 函数,可以查看实例是哪个对象创建出来的
print(type(p))
自定义方法
speak就是自定义方法
因为是给实例调有的,所以也叫实列方法;
也可以通过类.方法的方式来调用,但是要注意的是, 第一个参数要传入一个实例
class Person:
# __init__: 实际化就去,主要作用:给当前正在创建的实际对象添加属性
# self 表示正在创建的实例对象
# __init__ python会自动去调用
def __init__(self, name, age):
self.name = name
self.age = age
# 自定义方法
# speak方法只有一份,保存在Person类身上,所有Person类的实际对象,都可以调用到这个方法
def speak(self, msg):
print(f'我是{self.name}! 我的年龄是{self.age}, 我想说:{msg}')
p1 = Person('huangcy', 33)
p2 = Person('张三', 20)
# 实际对象上是没有speak方法的
# 当实行到p1.speak方法时,查找speak方法过程: 实例对象自身(p1) -> 实际的【缔造者(Person)】身上查找
print(p1.__dict__)
p1.speak('hello')
p2.speak('hi')
#通过类来调用实例方法
Person.speak(p1, ' hello')类属性
类属性可以通过类访问,也可以通过实例访问
类属性通常用于保存:公共数据
可以通过类名.属性名的方式进行修改,也可以通过实例对象来修改,通过实例对象修改的类属性,不会影响之前创建的实例
class Person:
# 类属性
max_age = 120
address = '中国'类方法
使用@classmethod装饰器的方法,就叫类方法
类方法收到的参数:当前类本身(cls)、自定义参数
因为收到了cls参数,所以类方法中也是可以访问类属性的
类方法通常用于实际:与类相关的逻辑,例如:操作类级别的信息、工厂方法
注意:也可以通过实例对象来调用类方法,非常不推荐
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 类方法
# 例子是一个工厂方法
@classmethod
def create(cls, value):
name, age = value.split('-')
return cls(name, age)
p1 = Person.create('huangcy-25')
print(p1.name, p1.age) 静态方法
使用@staticmethod装饰的方法就叫静态方法
静态方法只是单纯的定义在类中,它不会收到self、cls参数,它只能收到自定义参数
由于静态方法没有收到self、cls参数,所以其内部不会访问任何中类和实例相关的内容
静态方法通常用于定义:与类相关的工具方法
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@staticmethod
def show():
print('我是一个类静态方法')
p1 = Person('huangcy', 25)
Person.show()
p1.show() # 不推荐继承
通过括号里加父类的方式来实现继承
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 定义一个Student类(子类、派生类),继承自Person类(父类、超类、基类)
class Student(Person):
def __init__(self, name, age, gender):
# 调用父类的__init__方法来执行初始化相关属性的操作
# 方式1
super().__init__(name, age)
# s = super()
# s.__init__(name, age)
# 方式2
# Person.__init__(self, name, age)
self.gender = gender
s1 = Student('huangcy', 25, '男')
print(s1.name)
类的__mro__属性
用于记录属性和方法的查找顺序、继承查找调用关系就很有用了
方法重写
当子烦躁定义了一个与父类中相同的方法,那子类中的方法就会“覆盖”父类的方法
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self, msg):
print(f'我是{self.name}, 我想说: {msg}')
class Student(Person):
def speak(self, msg):
print(f'我是学生{self.name}, 我会说 {msg}')
s1 = Student('huangcy', 25)
s1.speak('你好')super
可以调用父类中的各种方法或者属性
result = super().__dict__
类中的常用方法
isinstance
isinstance(instance, Class) 判断某个对象是否为指定类或者子类的实例
issubclass
issubclass(Calss1, Class2) 判断某个类是否为另一个类的子类
多重继承
多重继承指一个类同时继承多个父类,从而拥有多个父类的属性和方法
举例:就像孩子不仅继承爸爸的长相,也能继承妈妈的性别。
# 多重继承指一个类同时继承多个父类,从而拥有多个父类的属性和方法
# 举例:就像孩子不仅继承爸爸的长相,也能继承妈妈的性别。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self):
print(f'我是{self.name}, 年龄是{self.age}')
class Worker:
def __init__(self, company):
self.company = company
def to_work(self):
print(f'我在{self.company}工作')
class Student(Person, Worker):
def __init__(self, name, age, height, company):
Person.__init__(name, age)
Worker.__init__(company)
self.height = height
def study(self):
print(f'我是全校最高的学生,我的身高是{self.height}')
s1 = Student('huangcy', 25, 178, '清川智算')面向对象的三种访问权限
通过变量名之前加下划线来控制权限(笑死)
受保护的属性:在外面其实还是能访问,只是不推荐(笑死)
私有属性: 哪里访问都会报错
class Person:
def __init__(self, name, age, idcard):
self.name = name # 公有属性:当前类中,子类中、类外部,都可以访问
self._age = age # 受保护的属性:当前类中、子类中都可以访问
self.__idcard = idcard # 私有属性:仅在当前类中可以访问
面向对象之getter和setter
私有属性、成员属性其实都可以通过自定义方法修改或者读取,但显得不够优雅,而且还要加括号
class Person:
def __init__(self, name, age, idcard):
self.name = name # 公有属性:当前类中,子类中、类外部,都可以访问
self._age = age # 受保护的属性:当前类中、子类中都可以访问
self.__idcard = idcard # 私有属性:仅在当前类中可以访问
# 注册age属性的getter方法,当访问Person实例的age属性时,下面的方法就会被自动调用
# 当然这里的age函数名,可以随意;一般约定俗成就用不带下划线的命名
@property
def age(self):
return self._age
# 注册age属性的setter方法,当修改Person实例的age属性时,下面的方法就会被自动调用
# 当然这里的age函数名,可以随意;一般约定俗成就用不带下划线的命名
@age.setter
def age(self, age):
if age < 0 or age > 100:
print('年龄非法')
else:
self._age = age
p1 = Person('huangcy', 25, '123')
p1.age = 202
print(p1.age)
魔法方法(魔术方法)
以__xxx__命名的特殊方法(双下划线开头和结尾)
特点:不需要我们手动调用,我们只要准备好这些方法,python 就会在特定场景下,去自动调用
以下是常用的几个,还有很多
__str__
当执行print(实例对象) 或者 str(实例对象)时自动调用
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self) -> str:
return f'{self.name}, {self.age}'
p1 = Person('huangcy', 25)
# 打印p1实例,就会自动调用类的__str__
print(p1)
# 上面这行等同于
print(str(p1))__len__
当len(对象) 时自动调用
__lt__
当执行 对象1 < 对象2时调用
__gt__
当执行 对象1 > 对象2时调用
__eq__
当执行 对象1 == 对象2时调用
__getattr__
当访问属性不存在的属性时
__main__
只有当你直接运行这个 .py 文件时,它才执行;被别的文件 import 时不执行
object类
python中,所有的类都继承了object类,即object类是所有类的顶层父类
因为object 是所有类的父类,所以python中的所有对象,都间接是object类的实例
像int、str、list、tuper等等,其实都是内置类(函数);所有对象都继承了object类所提供的:各种属性和方法,从而保证了每个对象都具备了统一的基本能力;object.__dict__
dir函数
dir() 用来列出“一个对象有哪些属性/方法名”,返回一个字符串列表
这就非常好玩了,可以判断一个属性/函数上是不是有属性或者方法之类的了
多态
多态的概念: 同一个方法名,在不同的对象上调用时,能呈现出不同的行为
python中支持:标准多态和鸭子多态
# 标准多态
class Animal:
def speak(self):
print('动物正在发出声音...')
class Dog(Animal):
def speak(self):
print('小狗在汪汪汪...')
class Cat(Animal):
def speak(self):
print('小猫在喵喵喵...')
# animal: Animal 类型注解,后面会详细讲
def make_sound(animal: Animal):
animal.speak()
# 创建实例对象
a1 = Animal()
d1 = Dog()
c1 = Cat()
make_sound(a1)
make_sound(d1)
make_sound(c1) 鸭子多态:如果一个东西看起来像鸭子,叫起来也像鸭子,那它就是鸭子。
鸭子类型是一种编程风格,它不检查对象的类型,只关注对象能否“做某件事”(是否有对应的方法)
class Dog:
def speak(self):
print('小狗在汪汪汪...')
class Cat:
def speak(self):
print('小猫在喵喵喵...')
class Pig:
def speak(self):
print('小猫在哼哼哼...')
class Fish :
def speak(self):
print('小猪在咕咕咕...')
def make_sound(animal):
animal.speak()
# 创建实例对象
d1 = Dog()
c1 = Cat()
p1 = Pig()
f1 = Fish()
make_sound(d1)
make_sound(c1)
make_sound(p1)
make_sound(f1)抽象类
抽象类是一种不能直接实例化的类,它通常作为“规范”,让子类去继承,并实现其中定义的【抽象方法】
通过继续ABC类,那么这个类就是抽象类了
通过@abstractmethod注解的方法,就是抽象方法了
from abc import ABC, abstractmethod
class MustRun(ABC):
# 抽象方法
@abstractmethod
def run(self):
pass
class Dog(MustRun):
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print(f'我是{self.name},我已经{self.age}岁了')
print('python 这些真的挺抽象的,学习要笑抽了...')
d1 = Dog('huangcy', 25)
d1.run()内存地址
python中万物即对象?
python中常见的不可变对象有: int、float、bool、str、tuple、frozenset、None
python中常见的可变对象有:list、dict、set、自定义类的实例对象
不可变对象分析图

可变类型变量分析

函数也是对象(重新认识函数)
- 函数也是实例对象:function的实例对象
- 函数也可以动态添加属性
- 函数可以赋值给变量
- 函数的可变参数和不可变参数
- 传参的区别:其实就是不可变类型,进去后怎么改都不会影响外层的;可变类型的,里面有调整,那么外面也就会变化(因为是引用地址)
- 函数也可以作为参数(高阶函数)
- 函数也可以作为返回值(高阶函数)
- 函数多个返回值(自动转换成元组)
def test():
print('test')
# test函数function类的实例对象
print(type(test))
# 函数可以动态添加属性
test.name = 'test'
test.version = '1.0'
print(test.name)
print(test.version)
# 函数可以赋值给变量
say_test = test
say_test()
# 可变参数 vs 不可变参数
# 传参的区别:其实就是不可变类型,进去后怎么改都不会影响外层的;可变类型的,里面有调整,那么外面也就会变化(因为是引用地址)
a = 666
def welcome(data):
print(data, id(data)) # 666
data = 888
print(data, id(data)) # 888
print(a) # 666
welcome(a)
print(a) # 666
# 可变参数的例子
b = [1, 2, 3]
def welcome1(data):
print('b是', data, id(data))
data[1] = 888
print('b是', data, id(data))
print('b是',b)
welcome1(b)
print('------')
# 函数也可以作为参数
def a():
print('a')
def b(fn):
fn()
b(a)
print('------')
# 函数也可以作为返回值
def c(msg):
print(f'c的msg是 {msg}')
def d(msg):
return c(msg)
d('ss')
print('------')
def e():
return 5, 3 # 自动转换成元组
print(e()) # (5, 3)
