面向过程和对象
一、类与对象
1、面向过程(pop)
-
函数就是一个面向过程的,对于一个功能进行了封装,从上到下写代码
-
就是非常的严格的
-
是一个线性步骤,按照顺序来执行步骤,先完成这个,才能完成下一个
2、面向对象(oop)
-
相当于是一个模板,比如汽车,有车门,玻璃等等组成,没有先后制作顺序,因此的话,就是有多个对象,但是具体实现一个对象,车门等,还是使用的面向过程的思维
-
也就是说有了一个模版(类),还的需要一个具体的实例(对象),来一起使用
-
是通过类和对象来实现的
-
类就是一个模版,对象就是一个实例(具体的)
-
特点
-
以对象为核心,用什么做,比如,做饭,拆解成人,食材,锅具,这些对象,然后定义每个对象的属性和方法
-
以类与对象为载体的,类是对象的模版,比如食材类的所有食材共有的属性,名称,新鲜度,对象是类的实例,番茄,土豆是食材类的具体对象
-
具有封装性 对象的属性通过自身的
-

3、类和对象
1、类和对象的定义
-
类就是一个模板,里面可以包含多个方法(函数),函数里面实现一些功能
-
对象根据模板创建的实例,通过对象可以调用类中的属性和方法
class stu(): # class定义类
name="aa" # 类属性
s1=stu() # 根据类创建一个对象
print(s1.name) # 对象调用类中的属性
# 输出结果
aa
2、类的属性
1、类属性
-
定义在类中,但是不在方法内
-
所有创建出来的实例(对象)都能使用这个类属性
-
可以通过类名和对象名来进行访问,但是不建议使用对象.类属性,容易造成误会成实例属性
class stu:
name="aa" # 类属性
s1=stu()
s1.name="qq" # 添加了一个实例属性,会覆盖掉继承的aa这个属性
stu.name="mm" # 修改了类属性
print(stu.name)
print(s1.name)
# 输出结果
mm
qq
- 实例属性的优先级高于类属性
2、实例属性
-
定义在方法内部的属性称为实例属性,只能通过对象名来访问
-
在本类中的其他方法中,通过self来进行访问,self.实例属性
-
创建了对象后,通过对象名.实例变量名来进行访问
class stu:
name="qq"
def __init__(self,sex,age):
self.sex=sex # 实例属性
self.age=age
print(f"{self.name},{self.sex},{self.age}")
def info(self): # 在类中定义的函数称为方法
print(f"{self.name}")
s1=stu("男",20) # 创建的对象
s1.info() # 只能通过对象来调用类中的方法
# 输出结果为
qq,男,20
qq
class Reco:
def __init__(self,w,h):
self.w=w
self.h=h
def get_area(self):
return self.w * self.h # 直接使用实例属性
def get_per(self):
return (self.w+self.h)*2
r1=Reco(1,2)
print(f"宽为{r1.w},高为{r1.h}")
area=r1.get_area()
print(f"面积为:{area}")
per=r1.get_per()
print(f"周长为:{per}")
r1.w=2 # 修改了实例属性
print(r1.get_area())
# 输出结果为
宽为1,高为2
面积为:2
周长为:6
4
3、私有属性和保护属性
-
私有属性就是双下划线开头的,只有这个类的对象能够访问,子类对象也不能访问到这个属性
-
私有属性不能向之前访问实例属性一样,对象名.实例属性来进行访问,类的外部不能直接访问,避免了随便修改
-
但是可以通过对象名._类名_私有属性名来调用
class book():
def __init__(self,n,m): # 参数
self.__n=n # 定义私有属性
self.__m=m
def fun(self):
print(f"{self.__n},{self.__m}") # 可以在这个类中使用私有属性
b1=book(10,20)
b1.fun()
print(b1._book__m) # 也能访问,但是不建议
b1.__n=30 # 会报错的,私有属性不能这样访问
# 输出结果为
10,20
20
-
保护属性,单下划线开头的,类本身和子类都能访问到这些属性
-
私有属性是强制性的,外部访问会直接报错,但是这个是君子协议,外部能访问,但是不推荐
class book():
def __init__(self,n,m,p): # 参数
self.__n=n # 定义私有属性,然后赋值
self.__m=m
self._p=p
def fun(self):
print(f"{self.__n},{self.__m}") # 可以在这个类中使用私有属性
b1=book(10,20,30)
b1._p=90
print(b1._p) # 类的外部能够进行访问,但是不推荐
3、类中的方法
1、实例方法和私有方法和保护方法
-
就是在类中定义的函数称为实例方法
-
保护方法,以单下划线开头
-
私有方法,以双下划线开头的,只允许这个类本身进行访问,也就是在类的里面进行访问,禁止外部和子类直接访问
-
对象名._类名_私有方法()来进行调用,但是不推荐,破坏了封装性
-
这个是封装性最强的
-
但是可以通过在类中定义的实例方法,来调用私有方法,从而实现了调用私有方法
class book():
def _fun2(self):
print("我是保护方法")
def __fun3(self):
print("我是私有方法")
def fun(self):
print("我是实例方法")
self.__fun3() # 在实例方法中调用私有方法,推荐的使用的形式
self._fun2()
b1=book()
b1.fun()
b1.__fun3() # 会报错
b1._fun2()
# 输出结果
我是实例方法
我是私有方法
我是保护方法
我是保护方法
2、静态方法
-
在定义的前面需要添加@staticmethod
-
静态方法没有self参数,只能访问类的成员(类属性,类方法等),不能访问对象的成员(实例属性,实例方法都不能访问)
-
一个类中所有实例对象共享静态方法,所有的对象都能进行访问静态方法
-
使用静态方法时,通过类名.静态方法名(),对象名.静态方法名(),也可以,但是很容易被误解为实例方法
class person:
number=0
def __init__(self,name):
self.name=name
person.number+=1 # 每次创建一个对象都会+1
def getname(self):
print(f"my name is {self.name}")
@staticmethod
def getnumber():
print(f"总人数为:{person.number}")
p1=person("tom")
p1.getname()
person.getnumber()
p2=person("like")
p2.getname()
p2.getnumber()
# 输出结果
my name is tom
总人数为:1
my name is like
总人数为:2
3、类方法
-
声明类本身的方法,一般就是对类本身进行操作
-
@classmethod定义
-
不能访问实例对象的属性,类的属性可以访问
class fun():
classname = "fuu" # 类属性
def __init__(self, name):
self.name = name # 实例属性
print("init", self.name)
@classmethod # 类方法
def f2(cls):
print("classmethod", cls.classname) # 类方法中的cls调用的是类属性,而不能使用实例属性
f = fun("lisi")
f.f2()
fun.f2()
# 输出结果为
init lisi
classmethod fuu
classmethod fuu
4、类中的方法总结
-
修饰符的不同
-
实例方法不需要修饰符
-
静态方法是通过@staticmethod来修饰的
-
类方法时通过@classmethod修饰的
-
-
参数不同
-
实例方法的第一个参数self来表示的
-
类方法的第一个参数是cls
-
静态方法不需要这些附加参数
-
-
调用方式不同
-
实例方法只能通过实例对象调用,也就是对象来调用
-
类方法和静态方法可以通过类名或者实例对象调用
-
-
作用不同
-
实例方法,用于处理实例属性,完成对象的动态行为
-
主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。此时的类,更像是命名空间或者包
-
是将类本身作为对象进行操作的方法,直接处理类对象。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法
-
-
能够调用的属性的不同
-
实例方法能够调用类属性和实例属性
-
类方法和静态方法都只能调用类属性,不能调用实例属性
-
5、动态绑定属性和方法的操作
- 动态绑定属性,就是在类的外面定义这个属性
class book():
pass
b1=book()
b1.name="qq"
print(b1.name)
b2=book()
print(b2.name) # 会报错,因为没有绑定这个属性
# 输出结果为
qq
- 绑定方法
class book():
def f1(self):
print("我被绑定了")
b1=book()
b1.name="qq"
print(b1.name)
def fun(): # 创建一个函数
print("你好")
b1.test=fun # 直接将这个函数赋值给这个对象的属性
b1.test() # 直接调用这个对象的属性就能调用
二、封装

1、封装的定义
-
隐藏对象的属性和实现细节,仅对外提供公共访问方式
-
就是知道怎么调用的方式就行了,使用者不需要知道这个是怎么实现的
-
封装的原则
-
将不需要对外提供的内容都隐藏起来
-
把属性都隐藏起来,提供公共方法对其进行访问
-
2、封装与扩展性
-
封装明确了区分内外,类的制定者可以修改封装内的东西而不能影响调用者的代码,而外部的使用者只需要知道有一个接口,接口的函数名和参数不改变的话,使用者的代码就永远不会改变
-
制定者修改里面的代码,使用者使用这个接口来进行调用即可,不需要知道里面的怎么实现的
-
因此的话,封装就是将类的属性和方法隐藏起来,只需要提供一个接口给外面来进行调用即可,不需要里面的具体的实现方法
-
比如atm机器,我只需要知道这个机器能够取钱即可,不需要知道它是怎么实现自动取钱的功能即可
# 类的设计者
class Room:
def __init__(self,name,width,height,length):
self.name=name # 创建了四个实例属性
self.width=width
self.height=height
self.length=length
def tell_area(self):
# 返回一个面积
return self.width*self.height
# 类的使用者
r1=Room("zz",6,9,10)
print(r1.tell_area())
# 现在类的设计者扩展一个功能即可,而类的使用者不需要改变自己的代码
class Room:
def __init__(self,name,width,height,length):
self.name=name # 创建了四个实例属性
self.width=width
self.height=height
self.length=length
def tell_area(self): # 对外提供的一个接口,隐藏了内部的实现,此时求出的是一个体积,内部逻辑改变了,扩展了另外一个功能
# 返回一个体积
return self.width*self.height*self.length
# 类的使用者
r1=Room("zz",6,9,10) # 还是一样的调用,但是现在使用的是新的功能
print(r1.tell_area())
3、property属性
-
是一个特殊的属性,访问它时,会执行一段函数(方法),然后有一个返回值
-
property参数 ,就是将这个方法伪装成属性,调用的时候不需要()这个括号了,就向使用实例属性一样的进行调用即可
-
还有修改的属性(方法名.setter),和删除的属性(方法名.delter)
class anmil():
def __init__(self,w,h):
self.w=w
self.h=h
@property
def area(self):
return self.w*self.h
p1=anmil(10,20)
print(p1.area) # 调用这个伪装成属性的方法,使用者不知道这个还经历了一个函数来实现的
p1.area=10 # 不能为这个伪装的属性进行赋值,下面会介绍怎么赋值的方法的
class anmil():
def __init__(self,w,h):
self.w=w
self.h=h
@property
def area(self):
return self.w*self.h
@area.setter # 当有修改属性的值时,就会触发这个
def area(self,value): # 只有一个参数,有且只有一个
self.w=value
p1=anmil(10,20)
print(p1.area)
p1.area=20 # 当修改了属性的值时,就会触发这个area.setter这个
print(p1.area)
# 删除属性
class anmil():
def __init__(self,w,h):
self.w=w
self.h=h
@property
def area(self):
return self.w*self.h
@area.setter
def area(self,value):
self.w=value
@area.deleter # 当有删除的调用时,就会触发
def area(self):
print("我被删除了")
p1=anmil(10,20)
print(p1.area)
p1.area=20
print(p1.area)
del p1.area # 就会触发这个删除的方法
# 输出结果为
200
400
我被删除了
1、property案例
- 比如商品的价格,原价,折扣,当修改了原价之后的价格了,以及删除了原价之后了
| 装饰器 | 对应的用户写法 | 背后真实调用 |
|---|---|---|
@property |
x = obj.foo |
obj.foo() 取返回值 |
@foo.setter |
obj.foo = x |
obj.foo(x) |
@foo.deleter |
del obj.foo |
obj.foo() |
三、继承
1、继承的概述
-
是一种新的创建类的方式,在python中,新建的类可以继承一个类或者多个父类
-
有单继承和多继承
class test():
pass
class book(test): # 单继承
pass
class a(test,book): # 多继承
pass
2、继承与重用性
class animal():
def __init__(self,name):
self.name=name
def eat(self):
print(f"{self.name} 吃")
def drink(self):
print(f"{self.name} 喝")
class cat(animal):
def climb(self):
print("爬树")
class dog(animal):
def look_after(self):
print("汪汪叫")
c1=cat("猫") #创建一个对象,因为继承了父类,因此的话,就需要添加一个实参
c1.drink()
c1.climb()
d1=dog("狗")
d1.eat()
d1.look_after()
# 输出结果为
猫 喝
爬树
狗 吃
汪汪叫
3、派生
-
就是子类不仅可以继承父类的属性和方法,自己也可以定义这些属性和方法,或者再次重新定义父类的属性或者方法,不会影响父类的
-
子类的属性和方法与父类重名的话,以子类的为准,子类的优先级高一点
class animal:
def __init__(self,name,aggre,life_value):
self.name=name # 名称
self.aggre=aggre # 攻击力
self.life_value=life_value # 生命值
print(f"{self.name},攻击力为{self.aggre},生命值为{self.life_value}")
class Dog(animal):
# 定义了一个狗攻击的方法
def bite(self,people): # 参数是一个对象,所以传参的时候,需要传入一个对象
# 人的生命值为
people.life_value = people.life_value - self.aggre
return (f"人被狗攻击了,生命值为{people.life_value}")
class Person(animal):
def attack(self,dog):
dog.life_value = dog.life_value - self.aggre
return (f"狗被人攻击了,狗的生命值为{dog.life_value}")
egg=Person("eggon",10,1000)
ha2=Dog("张三",50,1000)
print(ha2.bite(egg))
print(egg.attack(ha2))
# 输出结果为
eggon,攻击力为10,生命值为1000
张三,攻击力为50,生命值为1000
人被狗攻击了,生命值为950
狗被人攻击了,狗的生命值为990
4、方法重写
-
父类定义了一个eat的方法,子类继承的时候,如果这个不好的话,还可以在写一个eat方法,这个就叫做方法重写,调用的时候以子类的优先级高一点
-
但是如果还想要使用父类的方法名的话,就需要使用super这个函数来调用父类的方法
-
规则就是方法名必须完全相同
-
参数列表必须完全相同
-
这个方法重写即使用父类的共性,又能通过重写体现子类的个性,多态的基础
class animal():
def __init__(self,name):
self.name=name
def eat(self):
print(f"{self.name}在吃东西")
class dog(animal):
def eat(self): # 重写了父类的方法,覆盖了父类的eat方法
print(f"{self.name}在啃骨头")
d1=dog("狗")
d1.eat() # 调用的时候,子类中的eat的方法优先级更高一点
# 输出结果为
狗在啃骨头
# 当然如果还想要使用父类的方法的话,使用super函数
class animal():
def __init__(self,name):
self.name=name
def eat(self):
print(f"{self.name}在吃东西")
class dog(animal):
def eat(self):
super().eat()
print("还在喝水")
d1=dog("狗")
d1.eat()
# 输出结果为
狗子吃东西
还在喝水
四、多态
-
同一个方法名,不同对象调用时,会执行各自不同的实现
-
不同对象调用会有不同的结果
-
多态指的就是一类事物有多种形态
-
动物有多种形态,人,狗,猪这三种
class animal:
def __init__(self,name):
self.name=name
def action(self):
print(f"{self.name}在活动")
class dog(animal):
def action(self):
print(f"狗{self.name}在喝水")
class person(animal):
def action(self):
print(f"人{self.name}在走路")
# 定义一个函数
def do_action(an): # 传入的参数必须是一个对象
an.action() # 这个是在调用action, 就是有了继承,一个方法名却能实现不同的功能
# 创建2个对象
d1=dog("pp")
p1=person("张三")
do_action(d1)
do_action(p1)
# 输出结果为
狗pp在喝水
人张三在走路
-
基于继承和方法重写
-
多态建立在继承和方法重写上,没有继承的话,无法统一接口(统一接口的核心,就是方法名一致),没有重写的话,所有对象调用的都是父类的方法
-
如果没有接口(方法名)一致的话,调用的时候狗的行为就是g_action,人的行为就是r_action,各自为政,但是现在如果有统一的接口action就能调用人的行为,狗的行为也能调用
-
-
接口统一,实现不同
- do_action是同一个接口,只需要调用这个do_action方法,不需要关心传入的是人还是狗,而人和狗各自的action实现不同
-
灵活性和可扩展性
- 后面如果新增一个cat类的话,并继承animal和重写了action的话,do_action不需要修改任何东西就能直接使用,因为它依赖的都是父类的统一的接口
总结
-
类是一个模版,比如食材,属性和方法就是,名称,新鲜度,方法就是清洗,切割等等
-
对象就是类的具体的实例,比如番茄,土豆等等
类的属性与方法总结
一、类的属性总结(按归属和权限分类)
| 分类维度 | 类型 | 定义方式 | 访问范围 | 核心特点 |
|---|---|---|---|---|
| 按归属 | 类属性 | 类内部直接定义(不在方法中) | 类内外均可访问(类名.属性 / 实例.属性) | 1. 属于类本身,所有实例共享同一值 2. 修改需通过“类名.属性”(实例修改仅影响自身) |
| 实例属性 | 方法中通过self.属性定义 |
仅属于实例,通过“实例.属性”访问 | 1. 每个实例独立拥有,值可不同 2. 需先创建实例才能访问 |
|
| 按权限 | 公有属性 | 无下划线开头(如name) |
类内外、子类均可自由访问和修改 | 无访问限制,公开可见 |
| 保护属性 | 单下划线开头(如_score) |
1. 类内部、子类可直接访问 2. 外部可访问(但约定不推荐) |
1. 约定级限制(非语法强制) 2. 用于类内部和子类共享逻辑 |
|
| 私有属性 | 双下划线开头(如__id) |
1. 仅类内部可直接访问 2. 外部和子类不可直接访问 |
1. 语法级限制(名称修饰) 2. 严格封装,保护核心数据不被外部修改 |
二、类的方法总结(按功能和权限分类)
| 类型 | 定义方式 | 第一个参数 | 访问范围 | 核心特点 |
|---|---|---|---|---|
| 实例方法 | 类中直接定义(无装饰器) | self(代表实例) |
1. 类内部通过self.方法()调用2. 外部通过“实例.方法()”调用 |
1. 可访问实例属性和类属性 2. 最常用,用于操作实例数据 |
| 私有方法 | 双下划线开头(如__method) |
self |
仅类内部可调用(通过self.__方法()) |
1. 语法级限制,外部和子类不可直接调用 2. 用于类内部辅助逻辑(如加密、校验) |
| 类方法 | 用@classmethod装饰 |
cls(代表类) |
1. 类内部通过cls.方法()调用2. 外部通过“类名.方法()”或“实例.方法()”调用 |
1. 可访问类属性,不可直接访问实例属性 2. 用于操作类属性或创建实例 |
| 静态方法 | 用@staticmethod装饰 |
无默认参数 | 外部通过“类名.方法()”或“实例.方法()”调用 | 1. 与类和实例无关,不能直接访问类/实例属性 2. 用于独立于类的工具功能 |
关键补充说明:
- 归属与权限的关系:类属性/实例属性均可通过“有无下划线”定义为公有、保护或私有(如“私有实例属性”“公有类属性”)。
- 权限本质:私有(
__)是语法强制限制,保护(_)是代码约定(提醒谨慎使用),核心目的是封装数据、减少误操作。 - 方法参数约定:
self(实例方法)和cls(类方法)是约定命名,分别指向实例和类本身,非语法强制但需遵守以保证可读性。 - 静态方法定位:更接近“类内工具函数”,仅用于逻辑归类,与类的属性/实例无直接关联。
封装和继承和多态,为什么需要
-
封装的就是属性的权限,就是如果没有的话,在类的外面就能随便定义个属性,导致这个逻辑混乱,打成一个模版
-
继承的话,就是能够继承父类的属性和方法,提高代码的重复性,还有方法重写,就是子类有自己的方法
-
多态,同一个方法名,能实现不同的效果
#封装:把 “动物” 的属性(名字、生命值)和方法(吃、叫)打包成一个 “动物模板”;
#继承:“狗” 和 “人” 复制 “动物模板”,再加上自己的特有技能(咬、攻击);
# 多态:不管是 “狗” 还是 “人”,我们都能用 “叫” 这个统一指令,让它们做出不同反应。
面向对象的更多说明
面向对象的软件开发
-
很多人在学完了python的class机制之后,遇到一个生产中的问题,还是会懵逼,这其实太正常了,因为任何程序的开发都是先设计后编程,python的class机制只不过是一种编程方式,如果你硬要拿着class去和你的问题死磕,变得更加懵逼都是分分钟的事,在以前,软件的开发相对简单,从任务的分析到编写程序,再到程序的调试,可以由一个人或一个小组去完成。但是随着软件规模的迅速增大,软件任意面临的问题十分复杂,需要考虑的因素太多,在一个软件中所产生的错误和隐藏的错误、未知的错误可能达到惊人的程度,这也不是在设计阶段就完全解决的
-
所以软件的开发其实一整套规范,我们所学的只是其中的一小部分,一个完整的开发过程,需要明确每个阶段的任务,在保证一个阶段正确的前提下再进行下一个阶段的工作,称之为软件工程
面向对象常用术语
-
抽象和实现 抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口
-
封装和接口 封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性
-
派生和继承 派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式 -
多态和多态性
-
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
-
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
-
-
自省和反射 自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,name__及__doc

浙公网安备 33010602011771号