初始面向对象
引入
面向过程思维
什么是面向过程?
-
面向过程就是按步骤一步一步的执行,从头一直执行到尾。
-
面向过程的核心就是
过程
二字。过程指的是解决问题的步骤,就好比设计一条流水线,是一种机械式的思维方式,但是这种机械式的思维方式也有一定的优点与缺点。
优点:将复杂的问题流程化,进而再简单化。
缺点:扩展性很差。
面向对对象思维
面向对象与面向过程就是完全不同的两种编程思维,面向对象可以将整个任务封装成一个大的类,在这个类里面详细的分解每个步骤,我们只需要执行类就可以完成相应的任务。
在软件开发过程中,面向对象编程是一种解决软件复用的设计和编程方法。
类的概念
类是一个比较抽象的东西,类是封装对象的属性和行为的载体,反过来说具有相同属性和行为的一类实体被称为类。
对象的概念
对象,是一个抽象概念,英文称作“Object”,表示任意存在的事物,世间万物皆对象。
通常将对象划分为两个部分,即静态部分与动态部分。
对象同样也是由属性和方法构成的,比如说学生类,而学生类再具体到某某学生的时候,这个学生就是一个对象。
静态部分
静态部分被称为“属性”
人类举例:属性包括姓名、性别、年龄等。
动态部分
动态部分指的是对象的行为,即对象执行的动作,一般定义到类的函数里面(类里面的函数也称之为方法)
人类举例:打篮球、游泳、跑步、吃饭、喝水、睡觉等。
举例说明
汽车属于一个类,而某辆红色的 mini 轿车就是汽车类中的一个对象。
水果属于一个类,而火龙果、苹果等都属于水果类中的一个对象。
动物属于一个类,而调皮的狗狗、可爱的猫咪都属于动物中的一个对象。类:汽车、水果、动物。
对象:mini 轿车、火龙果、苹果、狗狗、猫咪。
定义类
ClassName
:用于指定类名,一般使用大写字母开头,如果类名中包括两个单词,第二个单词的首字母也大写,这种命名方法也称为“驼峰式命名法”,这是惯例。当然,也可根据自己的习惯命名,但是一般推荐按照惯例来命名。
class ClassName:
'''类的帮助信息''' # 类文档字符串
statement # 类体
class ClassName():
pass
定义类是否要添加括号
在 Python 中,定义类时,可以选择在类名后面加括号或不加括号。这取决于你是否要继承自其他类。
如果你的类不需要继承自其他类,那么在类名后面加括号是可选的,你可以选择省略它们。例如:
class MyClass:
pass
上述代码定义了一个名为 MyClass
的类,它没有继承自其他类。
如果你的类需要继承自其他类,那么在类名后面加括号是必要的,括号中指定你要继承的类。例如:
class MyClass(BaseClass):
pass
上述代码定义了一个名为 MyClass
的类,它继承自 BaseClass
类。
实例化对象
类名加括号就可以调用类,调用类的目的是创建对象。对象的创建又称类的实例化,即实例出一个对象。
创建对象的格式如下:
对象名1 = 类名()
对象名2 = 类名()
对象名3 = 类名()
我们通过上述格式创建出来的对象是实例化对象。
注:只要是属于这个类创建出来的对象,那么这个对象就可以调用类中的所有方法
在Python中,类名后面加上括号通常表示实例化一个类。例如,Hero()
表示实例化Hero
类,而Hero().类方法
则表示调用实例化后的对象的类方法。
这种语法用于创建类的实例并调用其方法,实例化类是创建类的对象,使其具有类的属性和方法。
因此,Hero().类方法
表示调用实例化后的对象的类方法。
class Hero:
name = "小满"
age = 3
def eat(self):
print(f"{self.name}开始吃东西啦!")
Hero().name # '小满'
Hero().eat() # 小满开始吃东西啦!
xm = Hero()
xm.name # 小满'
xm.eat() # 小满开始吃东西啦!
# 注意,Hero().name 的方式创建了一个新的 Hero 实例对象,与 xm 不同。
# 因此,Hero().name 和 xm.name 的值都是 "小满",但它们指向的是不同的对象。
class Hero:
name = "小满"
def hi(self):
print(f"你好!我是{Hero.name}") # 使用类名访问类属性
print(f"你好!我是{xm.name}") # 使用实例对象访问类属性
print(f"你好!我是{self.name}") # 使用 self 访问实例对象的属性
xm = Hero()
xm.hi()
通过vars获取属性和属性值的字典
class Hero:
pass
xm = Hero()
xm.name = "小满"
xm.age = 3
vars(xm) # {'name': '小满', 'age': 3}
关于self
参数
谁调用的我self就是谁
self 这个参数,可以修改它的名称,但是一般不建议修改,因为当我们在类中编写方法时 Python 解释器会自动将 self 参数打印出来。
一言以蔽之:
self 是一个特殊的参数名,用于表示类的实例对象自身。它允许你在类的方法中访问和操作实例对象的属性和方法,因为在定义类时不知道具体的实例是谁,就用 self 代替这个实例。
实例对象的属性
定义对象的属性方式有两种:
1. 一种是在类外面定义(用的比较少)
2. 直接在类里面定义
class Hero:
pass
xm = Hero()
xm.name = "小满"
xm.age = 3
print(xm.name) # 小满
print(xm.age) # 3
类属性
类属性咱们可以理解为是类对象的属性,但是类属性可以被这个类所创建的所有实例对象共同拥有。
实例属性只属于实例对象,而类属性就类似于公共的属性,只要是这个类所创建的实例对象,那么大家都可以去访问,去使用这个类属性。
注意:类变量 属于类 不属于个体 需要放在构造方法
__init__
上面。虽然可以通过实例去访问,但是不建议。
什么情况下需要设置类属性
假设,同一个类我们现在需要创建很多个实例对象,但是这些实例对象中都拥有相同的属性,那么我们就可以将这个相同的属性单独写成类属性,这样可以减少我们的代码量,同时也可以节约电脑的内存,提高程序的效率
类对象
因为 Python 万物皆对象,所以我们在定义一个类时,这个类也是一个对象,我们将其称为类对象
。
# 我们可以将目前所创建出来的两个对象当做两名学生eva与amigo,同时他俩是属于 '精英班' 的学生。
# 而我们创建的类属性 money = 1000,可以理解为 '精英班' 的班费,那么这个班费是本班的学生共同拥有的。
# 所以如果我们说这 1000 元班费是属于某某学生的,那么就显得很不合理了。
class Student:
# 类属性 (数据属性)
money = 1000
# 函数属性
def __init__(self):
# 实例属性 grade 班级
self.grade = "精英班"
eva = Student()
amigo = Student()
# 实例对象访问类属性值
print(eva.money) # 1000
print(amigo.money) # 1000
# 通过类对象访问类属性:类对象.类属性名
print(Student.money) # 1000
修改类属性
类的外面修改
# 一样通过 类对象.类属性名 去修改
# 我们通过类对象.类属性名的方式可以访问到这个属性的值,同时我们也可以通过这种方式来为其重新赋一个值,这就是修改类属性的方式。
Student.money = 800
print(Student.money) # 800
print(eva.money) # 800
print(amigo.money) # 800
类的里面修改
class Student:
# 类属性
money = 1000
def __init__(self):
# 实例属性 grade 班级
self.grade = "精英班"
def change(self):
print("买零食".center(30, '-'))
print("买奖品".center(30, '-'))
# 内部修改类属性
Student.money = 0
print(f"一共花费800元,剩余班费{Student.money}元")
eva = Student()
# 在外部修改类属性
Student.money = 800
# 通过实例调用实例方法
eva.change()
-------------买零食--------------
-------------买奖品--------------
一共花费800元,剩余班费0元
属性方法命名
单下划线、双下划线、头尾双下划三种分别是:
_foo
(单下划线): 表示被保护的(protected)类型的变量,只能本身与子类访问,不能用于from module import *
__foo
(双下划线): 私有类型(private) 变量, 只允许这个类本身访问__foo__
(头尾双下划):特殊方法,一般是系统内置的通用属性和方法名,如__init__()
foo_
(单后置下划线,单右下划线):用于避免与关键词冲突,也用于初始化参数中表示不需要用户传入的变量,通常会定义初始值,如love_ = None
注:以上属性(变量)和方法(函数)均适用。
访问属性和方法
通过类访问
# 方式1:类.__dict__ 得到的是一个字典
class Hero:
name = "小满"
age = 3
def eat(self):
print(f"{self.name}开始吃东西啦!")
# 得到字典键值对的格式,__dict__字典存放的是共有的属性
Hero.__dict__
# 可以根据字典取值去获得对应的值
Hero.__dict__['name'] # '小满'
Hero.__dict__['age'] # 3
Hero.__dict__['eat'] # <function __main__.Hero.eat(self)> 方法的内存地址
# 方式2(推荐):可以通过`类名.方法名()`或者`对象名().方法名()`,运行属性同理。
Hero.name # '小满'
Hero.age # 3
Hero.eat # <function __main__.Hero.eat(self)>
# 如果要行方法,直接添加上括号即可
通过实例访问
class Hero:
name = "小满"
age = 3
def eat(self):
print(f"{self.name}开始吃东西啦!")
hero = Hero()
hero.name # '小满'
hero.age # 3
hero.eat # <bound method Hero.eat of <__main__.Hero object at 0x0000029A40E01ED0>>
实例化对象&对象属性
class Hero:
name = "小满"
age = 3
def eat(self):
print(f"{self.name}开始吃东西啦!")
# 这种方式刚实例化,每个对象是没有自己特有的属性,多一打印的结果是空的。
hero = Hero()
# 对象的.__dict__中存放的是对象的私有属性,不是类中大家共有的。
# 所以拿到的结果是一个空字典
hero.__dict__ # {}
vars(hero) # {}
# 可以通过 实例名.__dict__['key'] = value 或者 实例名.key = value 去添加私有属性
# 不过想想都很繁琐
hero.__dict__['name'] = "小满"
hero.__dict__ # {'name': '小满'}
hero.age = 3
vars(hero) # {'name': '小满', 'age': 3}
实例(对象)属性的查找顺序
先从自己本身开始找 从
xx.__dict__
开始找找不到再从
类.__dict__
找实例化对象自己 ---> 类 ---> 父类 都找不到就报错
创建实例属性
__init__
方法
在类里面定义对象的属性,__init__()
初始化魔法方法
class Hero:
def __init__(self):
self.name = "小满"
self.age = 3
xm = Hero()
print(xm.name) # 小满
print(xm.age) # 3
# 通过 __init__ 魔法方法为对象创建的属性叫做——实例属性。
# self:表示调用初始化的对象本身,谁调用了这个方法 self 就代表哪个对象。
# name:name 是一个形参,用来接收创建对象时传数的数据,形参名可以根据情况修改。
# age:age 是一个形参,用来接收创建对象时传数的数据,形参名可以根据情况修改。
class Hero:
def __init__(self, name, age):
self.name = name
self.age = age
xm = Hero("小满", 3)
print(xm.name) # 小满
print(xm.age) # 3
# 在创建实例方法时,也可以和创建函数时一样为参数设置默认值。但是被设置了默认值的参数必须位于所有参数的最后(即最右侧)。
class Hero:
def __init__(self, name, age=3):
self.name = name
self.age = age
xm = Hero("小满")
print(xm.name) # 小满
print(xm.age) # 3
类不能访问实例属性
实例属性只能通过实例访问,如果通过类去访问实例属性,会报错。
class Hero:
def __init__(self, name, age):
self.name = name
self.age = age
xm = Hero("小满", 3)
print(Hero.name)
# AttributeError: type object 'Hero' has no attribute 'name'
尝试返回__init__
class Hero:
def __init__(self):
return True
hero = Hero() # TypeError: __init__() should return None, not 'bool'
__str__
魔法方法
我们通常在该方法中定义对当前类的实例对象的描述信息。然后我们可以通过print(实例对象名)的方式来将这个对象的描述信息进行打印。
class Hero:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"你好!我的名字叫做{self.name}, 我今年{self.age}岁了"
xm = Hero("小满", 3)
print(xm) # 你好!我的名字叫做小满, 我今年3岁了
dq = Hero("大乔", 4)
print(dq) # 你好!我的名字叫做大乔, 我今年4岁了
总结__init__
方法
1、会在调用类时自动触发执行,用来为对象初始化自己独有的数据
2、__init__内应该存放是为对象初始化属性的功能,但是可以存放任意其他代码,想要在类调用时就立刻执行的代码都可以放到该方法内
3、__init__方法必须返回None,默认不写return语句
绑定方法(也成为动态方法)
在Python中,绑定方法是一个特殊的对象,它是一个函数对象,它在调用时会将实例对象作为第一个参数传递给该函数。通过这种方式,绑定方法可以访问实例对象的属性和方法,以及调用实例方法。
要创建绑定方法,首先需要定义一个类,然后在类中定义一个方法。在定义方法时,需要将第一个参数命名为self,这个参数用来接收实例对象。然后,可以在方法中访问实例对象的属性和方法。
class Hero:
def say(self):
print("你好!我叫小满~ 欢迎来到我的博客 ^_^")
xm = Hero()
xm.say() # 你好!我叫小满~ 欢迎来到我的博客 ^_^
类方法
类方法与普通的实例方法不同,类方法需要通过类对象调用。
普通的实例方法参数是 self,而类方法的参数是cls
。
在 Python 中,
@classmethod
是一个装饰器,用于表示一个方法是类方法,而不是实例方法。类方法不接收实例作为参数,而是接收类作为参数。这意味着你在调用类方法时,不需要创建类的实例,而是直接使用类来调用方法。如果需要定义类方法的话,需要使用装饰器
@classmethod
来进行装饰,而且第一个参数必须是当前类对象,该参数一般约定命名为cls
。
# 类方法,用@classmethod 来进行修饰
@classmethod
def get_country(cls):
pass
class Hero:
name = '小满'
@classmethod
def kill(cls):
print(f"{cls.name}正在大杀四方..")
Hero.kill() # 小满正在大杀四方..
class Student:
money = 1000 # 类属性,表示班费的初始金额
@classmethod
def change_money(cls): # cls 代表调用这个方法的类对象本身
cls.money = 800 # 通过cls.类属性名 = 值的方式,修改类属性 money 的值为 800
return cls.money # 将类属性的值进行返回
result = Student.change_money() # 调用类方法 change_money
print(result) # 打印修改后的类属性 money 的值 800
注意:类方法可以被类对象和实例对象调用,而实例方法只能被实例对象调用
静态方法(也称为非绑定方法)
定义静态方法与定义类方法相似,不过是代码细节上有一定区别。
定义静态方法,同样也需要装饰器来进行装饰,而这个装饰器就是
@staticmethod
。经过
@staticmethod
装饰的函数没有self
和cls
这两个参数。一般情况下静态方法主要用来存放逻辑性的代码,静态方法内部的操作一般不会涉及到类中的属性和实例方法的操作。
import time
class Student:
# 通过@staticmethod装饰器定义一个静态方法
@staticmethod
def showTime():
# 格式化输入当前时间
return time.strftime("%x %X %p")
# 通过类属性访问静态方法
print(Student.showTime()) # 01/02/24 19:37:59 PM
# 通过实例对象访问静态方法
eva = Student()
now = eva.showTime() # 01/02/24 19:37:59 PM
print(now)
__call__
__call__
可以让实例对象像函数那样可被执行,callable(eva)
默认是不能被执行的,我们重写 call 。
使用call前
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# 实例化
eva = Student('eva', 2)
callable(eva) # False
eva() # TypeError: 'Student' object is not callable
使用call后
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __call__(self):
self.age += 1
print('我能执行了~~~')
# 实例化
eva = Student('eva', 2)
callable(eva) # True
eva() # 我能执行了~~~
eva.age # 3
用类给函数添加装饰器
无参装饰器
# 需求 将类定义成一个装饰器 执行之前打印start 执行结束之后打印 end
# 会把函数传递给 __init__
# 当函数被执行的时候 call会先执行
class MyDecorator:
def __init__(self, f): # 初始化装饰器实例的属性
self.f = f
def __call__(self, *args, **kwargs): # 当装饰器被调用时执行的逻辑
print("start")
# 在这里添加装饰器逻辑
result = self.f(*args, **kwargs) # 调用被装饰的函数,如果有参数则传递给f()
print(f'函数的名称是: {self.f.__name__}')
print("end")
return result # 返回被装饰函数的结果
@MyDecorator # 将MyDecorator实例作为装饰器应用于test函数
def test():
print("test")
return 200
r = test() # 调用被装饰的test函数
print(r) # 打印test函数的返回值
"""
start
test
函数的名称是: test
end
200
"""
在上面的代码中,
__call__
方法是在装饰器被调用时执行的。具体来说,在@MyDecorator
行下面的def test():行上使用装饰器语法时,MyDecorator
类的实例将被创建,并且__call__方法将被调用。当你调用被装饰的函数,例如 test(),实际上是在调用装饰器实例的__call__方法。这是因为装饰器实例被应用于函数时,Python 会自动调用该实例的__call__方法。
from dataclasses import dataclass
status_dict = {"login": 'eva'}
@dataclass
class LoginAuth:
f: callable
def __call__(self, *args, **kwargs):
if status_dict['login']:
return self.f(*args, **kwargs)
print("对不起 不登录无法使用本功能。。")
@LoginAuth
def creat_scoure():
print('开始创建学校了。。。')
creat_scoure()
有参装饰器
class ParamDecorator:
def __init__(self, name): # 初始化装饰器实例的属性
self.name = name
def __call__(self, f): # 当装饰器被调用时执行的逻辑
def wrap(*args, **kwargs): # 包装函数,用于执行装饰器逻辑和原始函数逻辑
# 执行装饰器逻辑
print(f"我是{self.name}")
# 执行原始函数逻辑
result = f(*args, **kwargs)
# 执行装饰器逻辑
print("end")
return result # 返回原始函数的结果
return wrap # 返回包装函数作为装饰器的结果
@ParamDecorator("eva") # 将ParamDecorator实例作为装饰器应用于foo函数
def foo():
print("我是foo")
return "小满最棒啦"
f = foo() # 调用被装饰的foo函数
print(f) # 打印foo函数的返回值
"""
我是eva
我是foo
end
小满最棒啦
"""
有参装饰器2
from dataclasses import dataclass
status = True
@dataclass
class LoginAuth:
name: str
def __call__(self, f):
def inner(*args, **kwargs):
if status is True:
print(f"查询到用户[{self.name}]已登记,正在进入系统...")
return f(*args, **kwargs)
else:
print(f"查询到用户[{self.name}]尚未登录,无法使用本系统。")
return inner
@LoginAuth("eva")
def withdraw():
print("欢迎使用取款功能")
withdraw()
"""
查询到用户[eva]已登记,正在进入系统...
欢迎使用取款功能
"""
用函数给类添加装饰器
def cls_decorator(cls):
print("你好!我是小满")
return cls
@cls_decorator
class Person():
pass
# 此处的eva拿到的并不是真正的Person 因为被装饰了
# 所以拿到的实际上是上面函数 cls_decorator的返回值
eva = Person()
# 打印 你好!我是小满
def cls_decorator(cls):
print("你好!我是小满") # 打印装饰器函数的提示信息
def inner(): # 定义内部函数
print("start") # 打印装饰器逻辑的开始
obj = cls() # 创建被装饰的类的实例
print("end") # 打印装饰器逻辑的结束
return obj # 返回被装饰类的实例
return inner # 返回内部函数
@cls_decorator # 将cls_decorator函数作为装饰器应用于Person类
class Person():
pass
eva = Person() # 创建Person类的实例
amigo = Person() # 创建Person类的另一个实例
"""
你好!我是小满
start
end
start
end
"""
property(属性)
python中有两种方式实现装饰器。第一种是函数装饰器,第二种是类装饰器。比如python3
中的property就是一个类装饰器,将来绑定给对象的方法伪造成一个数据属性。可以通过@property
(装饰器)将一个方法转换为属性,从而实现用于计算的属性。将方法转换为属性后,可以直接通过方法名来访问方法,而不需要再添加一对小括号“()”,这样可以让代码更加简洁。
应用场景1:将函数属性伪装成数据属性
下面代码是在
jupyterlab
运行的注意:使用@property必须return
class Rect:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
"""
计算矩形的面积
Returns:
int: 矩形的面积
"""
return self.width * self.height
rect = Rect(4, 8)
print(f"面积为:{rect.area}") # 面积为:32
type(rect.area) # int
场景2:统一数据属性的查、改、删操作
在Python中,默认情况下,创建的类属性或者实例是可以在类体外进行修改的,如果想要限制其不能在类体外修改,可以将其设置为私有的,但设置为私有后,在类体外也不能直接通过实例名+属性名获取它的值。如果想要创建一个可以读取但不能修改的属性,那么可以使用@property
实现只读属性。
class TvShow:
def __init__(self, show):
self.__show = show
@property
def show(self):
return self.__show
tv_show = TvShow("正在播放《七大罪:天空的囚徒》")
print(tv_show.show) # 正在播放《七大罪:天空的囚徒》
# 尝试对 “属性” 进行修改
tv_show.show = "正在播放《狐妖小红娘》" # AttributeError: can't set attribute 'show'
class Dog:
def __init__(self, name, age, sex="雌性"):
self.name = name
self.__age = age
# 实例方法
def run(self):
print(f"{self.name}在跑。。。")
# 这里的名字应该和你的私有成员变量的名字是一样的
@property
def age(self): # 替代get_age(self)
return self.__age
@age.setter
def age(self, age): # 替代set_age(self, age)
self.__age = age
@age.deleter
def age(self):
print("无法删除~")
dog = Dog(name="球球", age=2)
print(f"狗狗的年龄:{dog.age}") # 狗狗的年龄:2
dog.age = 4
print(f"修改之后狗狗的年龄:{dog.age}") # 修改之后狗狗的年龄:4
del dog.age # 无法删除~
class TvShow:
films:list = ["天气之子", "萤火之森", "铃牙之旅"]
def __init__(self, show):
self.__show = show
# 将方法转换为属性
# @property.getter 默认就是getter
@property
def show(self):
# 返回私有属性的值
return self.__show
# 设置setter方法,让属性可以修改
# show这个名称实际上就是上面的方法名称
@show.setter
def show(self, value):
# 判断值是否在列表中
if value in self.films:
self.__show = f"您选择了《{value}》稍后将播放"
else:
self.__show = f"您点播的电影《{value}》不存在"
@show.deleter
def show(self):
print("无法删除哦~~~~")
# 创建类的实例
tv_show = TvShow("铃牙之旅")
# 获取属性值
print(f"正在播放《{tv_show.show}》") # 正在播放《铃牙之旅》
# 修改属性值
tv_show.show = "一人之下" # 您点播的电影《一人之下》不存在
# 再次获取属性值
print(tv_show.show)
# 尝试删除属性
del tv_show.show # 无法删除哦~~~~
当show 遇到查询时,触发被property装饰的函数的执行
当show 遇到赋值操作,即 = 时触发被property.setter装饰的函数的执行
当show 遇到删除操作,即 del 时触发property.deleter装饰的函数的执行
上述使用property的流程如下:
1.将想要伪装的系列方法命名成一个相同的名字
2.在查看功能上加上property语法糖
3.在修改和删除功能上加名字.setter和 .deleter语法糖
# 方案2
class TvShow:
# 类属性
films: list = ["天气之子", "萤火之森", "铃牙之旅"]
def __init__(self, show):
# 实例属性
self.__show = show
def getName(self):
return self.__show
def setName(self, value):
# 如果 value 在 films 列表中,设置 __show 属性为 "您选择了《{value}》稍后将播放"
# 否则设置为 "您点播的电影《{value}》不存在"
if value in self.films:
self.__show = f"您选择了《{value}》稍后将播放"
else:
self.__show = f"您点播的电影《{value}》不存在"
def delName(self):
# 删除 __show 属性
print("无法删除哦~~~~")
# 定义 show 属性,使用 getName、setName 和 delName 方法
show = property(getName, setName, delName)
# 创建类的实例
tv_show = TvShow("铃牙之旅")
# 获取属性值
print(f"正在播放《{tv_show.show}》") # 正在播放《铃牙之旅》
# 修改属性值
tv_show.show = "一人之下" # 您点播的电影《一人之下》不存在
# 再次获取属性值
print(tv_show.show)
# 尝试删除属性
del tv_show.show # 无法删除哦~~~~
# 将三个函数(get_name,set_name,del_name)统一为一个相同的符号show
# 当show 遇到查询时触发get_name函数的执行
# 当show 遇到赋值操作,即 = 时触发set_name函数的执行
# 当show 遇到删除操作,即 del 时触发del_name函数的执行
不论是双下划线开头的属性命名来隐藏属性,还是使用类装饰器property来将函数属性伪装成数据属性,本质上都是类的设计者封装类的行为。这样做的目的都是控制和规范类的使用者。
类的继承
在程序当中,继承描述的是多个类之间的所属关系,如果一个类 A 里面的属性和方法可以被复用,那么我们就可以通过继承的方式,传递到 B 类里面。
那么这种情况,类 A 就是基类称为父类,类 B 就是派生类称为子类。我们一般使用父类与子类来进行描述。
没有使用继承之前
# 导入时间模块
import time
class A():
# 实例方法
def showTime(self):
# 格式化输出当前时间
print(time.strftime("%x %X %p"))
class B():
# 实例方法
def showTime(self):
# 格式化输出当前时间
print(time.strftime("%x %X %p"))
a = A()
a.showTime() # 01/02/24 20:07:16 PM
b = B()
b.showTime() # 01/02/24 20:07:16 PM
使用了继承
# 导入时间模块
import time
class A():
# 实例方法
def showTime(self):
# 格式化输出当前时间
print(time.strftime("%x %X %p"))
# 类 B 继承 类A
class B(A):
pass
a = A()
a.showTime() # 01/02/24 20:08:43 PM
b = B()
b.showTime() # 01/02/24 20:08:43 PM
通过一个小故事了解类的继承
单继承
子类只继承一个父类(上面的例子使用到的就是单继承)
在很久很久以前,有一位专注于做葱油饼的大师在葱油饼子界摸爬滚打很多年,拥有一身精湛的葱油饼子技术,并且总结了一套秘制葱油饼配方
。
# 定义一个大师类
class Master():
# 实例方法
def makeCake(self):
print("♥按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子♥")
可是这位大师现已年迈,他希望在嗝屁之前把自己的秘制配方传承下去,于是这位大师将自己的秘制配方传给了他的大徒弟:宝宝。
# 定义一个大师类
class Master():
# 实例方法
def makeCake(self):
print("♥按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子♥")
# 定义 宝宝 类, 继承了Master 则 宝宝 是子类 Master 是父类
class Prentice(Master):
# 子类可以继承父类所有的属性和方法,哪怕子类没有自己的属性和方法,也可以使用父类的属性和方法。
pass
# 调用父类方法:实例对象名.方法名
baobao = Prentice()
baobao.makeCake()
多继承
这位大师的大徒弟宝宝也是非常的努力,初到泰国就凭借着自己高超的手艺吸引了一大群迷妹,每天排队买葱油饼的顾客更是络绎不绝。
大徒弟宝宝,很快便在当地有了一定的名气,虽然当前的手艺受到了很多人的喜爱,但是这个宝宝也一直渴望学习掌握到新的制做技术。
直到有一天,他偶然遇到了人生中的第二个师傅。这位师傅手中掌握着酱制葱油饼子配方
,宝宝为了学到新的技术,便拜入了这位师傅的门下,凭借着自己之前的手艺很快便得到了这位师傅的认可。
class Master():
# 实例方法
def makeCake(self):
print("♥按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子♥")
# 定义一个新的 师傅 类
class NewMaster():
def makeCakeNew(self):
print("♥按照 <酱制葱油饼子配方> 制作了一份香甜可口的葱油饼子♥")
随着宝宝的不断努力学习,在第二个师傅手里也学到了酱制葱油饼子配方
。
宝宝的葱油饼事业从此又踏上一个台阶。
到目前为止,宝宝已经遇到了两个师傅并且从每个师傅手中都学到了一份独门配方。
我们如何使用代码来表示宝宝学习到的这两份独门配方呢?
class Prentice(Master, New_Master): # 多继承,继承了多个父类(Master在前)
pass
单继承只需要在类后面的括号中写一个父类名即可,而多继承只需要在后面的括号中写入多个要继承的父类名字即可。
class Master():
# 实例方法
def makeCake(self):
print("♥按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子♥")
# 定义一个新的 师傅 类
class NewMaster():
def makeCakeNew(self):
print("♥按照 <酱制葱油饼子配方> 制作了一份香甜可口的葱油饼子♥")
class Prentice(Master, NewMaster):
pass
baobao = Prentice()
# 调用 Master 类中的方法
baobao.makeCake() # ♥按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子♥
# 调用 New_Master 类中的方法
baobao.makeCakeNew() # ♥按照 <酱制葱油饼子配方> 制作了一份香甜可口的葱油饼子♥
注意点:
-
多继承可以继承多个父类,写法与单继承相似,只需要在继承的括号中写入需要继承的父类名即可。
-
当我们继承多个父类时,也继承了父类的属性和方法,但是有一点需要注意,如果继承的父类中有同名的方法或者属性,则默认使用第一个属性和方法,当然多个父类中如果属性名与方法名不重名,那么就不会受到影响。
从左到右依次继承
继续讲故事~~ ^_^
随着故事剧情的发展,宝宝在学到了两位师傅的独门配方后,经过自己日夜的钻研,终于在这两个配方的基础上,创建了一种全新的葱油饼子配方,称之为:宝氏葱油饼配方
。
子类重写父类方法
子类重写父类方法,也就是说,在子类中定义一个与父类一模一样的方法,这就是重写父类方法。
宝宝经过自己的研制,成功研发出了‘宝氏葱油饼配方’。那么我们就可以在宝宝这个类中,新建一个与父类方法同名的方法,让我们来看看会是什么效果。
class Master():
# 实例方法
def makeCake(self):
print("♥按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子♥")
# 定义一个新的 师傅 类
class NewMaster():
def makeCakeNew(self):
print("♥按照 <酱制葱油饼子配方> 制作了一份香甜可口的葱油饼子♥")
# 创建子类
class Prentice(Master, NewMaster):
# 重写 NewMaster 类的方法
def makeCakeNew(self):
print("♥按照 <宝氏葱油饼配方> 制作了一份让人流连忘返的葱油饼子♥")
# 创建子类对象
baobao = Prentice()
# 调用 Master 类中的方法
baobao.makeCake() # ♥按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子♥
# 调用子类重写的方法
baobao.makeCakeNew() # ♥按照 <宝氏葱油饼配方> 制作了一份让人流连忘返的葱油饼子♥
注意点:
- 当我们在继承父类时,我们会自动继承父类的方法,而当我们在子类中根据自己的需求重写父类的方法之后,调用重名的方法,优先执行的是子类的方法。
- 所以,从这一点能总结出:子类和父类有同名的方法,则默认使用子类的。
- 其实不单单是方法,如果子类继承父类的话,子类同样可以使用父类的属性,当重写父类的属性之后,也是优先使用子类的属性。
经典类和新式类
1. 只有在python2中才分新式类和经典类,python3中统一都是新式类(默认继承object)
2. 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
3. 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
__mro__属性
用于获取类的方法解析顺序(MRO)的属性。MRO 是指在多继承的情况下,确定方法和属性的搜索顺序的规则。Python 使用 C3 线性化算法来计算 MRO,确保在多继承的情况下能够准确地确定方法和属性的搜索顺序。__mro__ 属性返回一个元组,其中包含了当前类及其基类的顺序。
经典类演示
class Phone(object):
pass
class Android(Phone):
pass
class XiaoMi(Android):
pass
class RedMi(XiaoMi):
pass
redmi = RedMi()
RedMi.__mro__
(__main__.RedMi, __main__.XiaoMi, __main__.Android, __main__.Phone, object)
新式类演示
class SS(object):
pass
class BB(SS):
pass
class AA(BB, SS):
pass
class EE(BB,SS):
pass
class FF(AA, EE, SS):
pass
class HH(FF, EE, BB, SS):
pass
HH.__mro__
(__main__.HH,
__main__.FF,
__main__.AA,
__main__.EE,
__main__.BB,
__main__.SS,
object)
__class__
访问对象所属的类
# 显示一个对象类型,换句话说就是显示自己是被谁实例化的。
# 在python中一切皆对象,也就是任何数据类型都会有__class__属性
(1).__class__ # int
{}.__class__ # dict
[].__class__ # list
''.__class__ # str
object.__class__ # type
# 萨摩耶犬
class Samoyed:
pass
# 柯基犬
class Corgi:
pass
# 牧羊犬
class Collie:
pass
def welcome(animal):
# 需要逐个判断 animal 是不是已知犬种
if type(animal) in [Samoyed, Corgi, Collie]:
print(type(animal)) # <class '__main__.Samoyed'>
print(animal.__class__) # <class '__main__.Samoyed'>
print(type(animal) == Samoyed) # True
print(animal.__class__ is Samoyed) # True
print('经检测,这是一只小狗,欢迎进入狗狗乐园!')
else:
print('这好像不是狗狗,不好意思,不能进入狗狗乐园')
dingding = Samoyed()
welcome(dingding)
# 输出:经检测,这是一只小狗,欢迎进入狗狗乐园!
__base__
查看直接父类
class Master():
# 实例方法
def makeCake(self):
print("♥按照 <秘制葱油饼配方> 制作了一份外焦里嫩的葱油饼子♥")
# 定义一个新的 师傅 类
class NewMaster():
def makeCakeNew(self):
print("♥按照 <酱制葱油饼子配方> 制作了一份香甜可口的葱油饼子♥")
# 创建子类
class Prentice(Master, NewMaster):
# 重写 NewMaster 类的方法
def makeCakeNew(self):
print("♥按照 <宝氏葱油饼配方> 制作了一份让人流连忘返的葱油饼子♥")
Prentice.__base__ # __main__.Master
__bases__
查看所有父类
得到一个元组
Prentice.__bases__ # (__main__.Master, __main__.NewMaster)
super()函数
super()
函数是用于调用父类(超类)的一个方法,语法是:super(type[, object-or-type])
。super(SubClass, self).method()
的意思是,根据 self 去找SubClass
的「父亲」,然后调用这个「父亲」的 method()。经常用在我们在子类中重写了父类中的方法,但有时候还是需要用父类中的方法。
class Student:
def __init__(self, name):
self.name = name
def say(self):
print(f"Hello, my name is {self.name}.")
def add(self, x, y):
print(f"这个加法我会,{x}+{y}={x + y}")
class CollegesStudent(Student):
def practice(self):
print(f"我是{self.name},在世界500强实习。")
super().say()
super(CollegesStudent, self).add(29, 4)
eva = CollegesStudent('eva')
eva.say()
eva.practice()
Hello, my name is eva.
我是eva,在世界500强实习。
Hello, my name is eva.
这个加法我会,29+4=33
Python3.x
和Python2.x
的一个区别是:Python 3 可
以使用直接使用super().xxx
代替super(Class, self).xxx
import time
class Person:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
@staticmethod
def timeNow():
now = time.strftime("%x %X %p")
print(f"现在时间是: {now} 程序正常运行中。")
class Student(Person):
@property
def study(self):
return f"{self.name}正在学习。。"
class Teacher(Person):
def __init__(self, name, age, sex, score):
# 方式1 直接使用父类.__init__
# Person.__init__(self, name, age, sex)
# 方式2 使用super()函数去调用, 注意使用super()的时候 不需要添加self
# super().__init__(name, age, sex)
# 方式3 指名道姓的去调用
super(Teacher, self).__init__(name, age ,sex)
self.score = score
print(f"{self.name}给出了{score}分")
def showStatic(self):
# 调用父类的方法
super().timeNow()
eva = Student('eva', 18, 'female')
print(eva.study)
amigo = Teacher('amigo', 20, 'male', 80)
amigo.showStatic()
eva正在学习。。
amigo给出了80分
现在时间是: 01/05/24 15:13:53 PM 程序正常运行中。
注意:super()函数应该在方法内部使用,而不是在类定义中使用。
class People:
def __init__(self, name):
self.name = name
class Student:
super().__init__(name)
self.name = name # RuntimeError: super(): no arguments
派生
在面向对象编程中,被继承的类称为父类或基类,新的类称为子类或派生类。
class Fruit:
color = "绿色"
def harvest(self, color):
# 输出形参的color
print(f"水果是{color}的!")
print("水果已经收获....")
# 输出类属性的color
print(f"水果原来是{Fruit.color}的!")
# 定义苹果类(派生类)
class Apple(Fruit):
color = "红色"
def __init__(self):
print("我是苹果")
# 定义橘子类(派生类)
class Orange(Fruit):
color = "橙色"
def __init__(self):
print("\n我是橘子")
# 创建类的实例(苹果)
apple = Apple()
# 调用积累的harvest()方法
apple.harvest(apple.color)
# 创建类实例(橘子)
orange = Orange()
# 调用基类的harvest()方法
orange.harvest(orange.color)
"""
我是苹果
水果是红色的!
水果已经收获....
水果原来是绿色的!
我是橘子
水果是橙色的!
水果已经收获....
水果原来是绿色的!
"""
在派生类中定义
__init__()
方法时,不会自动调用基类的__init__()
方法。
class Fruit:
def __init__(self, color="绿色"):
Fruit.color = color
def harvest(self):
print(f"水果原来是{Fruit.color}的")
class Apple(Fruit):
def __init__(self):
print("我是苹果")
apple = Apple()
apple.harvest() # AttributeError: type object 'Fruit' has no attribute 'color'
可以通过super()去解决
class Fruit:
def __init__(self, color="绿色"):
Fruit.color = color
def harvest(self):
print(f"水果原来是{Fruit.color}的")
class Apple(Fruit):
def __init__(self):
super().__init__()
print("我是苹果")
apple = Apple()
apple.harvest()
"""
我是苹果
水果原来是绿色的
"""
class Fruit:
def __init__(self, color="绿色"):
self.color = color
def harvest(self, color):
print(f"水果是{color}的")
print("水果已经收获。。")
print(f"水果原来是{self.color}的!")
class Apple(Fruit):
color = "红色"
def __init__(self):
print("我是苹果")
super().__init__()
class Sapodilla(Fruit):
def __init__(self, color):
print("\n我是人参果")
super().__init__(color)
def harvest(self, color):
print(f"人参果是{color}的!")
print("人参果已收获。。")
print(f"人参果原来是{self.color}的!")
apple = Apple()
apple.harvest(apple.color)
sapodilla = Sapodilla('白色')
sapodilla.harvest("金黄色带紫色条纹")
print(vars(apple))
print(vars(sapodilla))
"""
我是苹果
水果是绿色的
水果已经收获。。
水果原来是绿色的! ---> 基类的 __init__()方法中设置的默认值
我是人参果
人参果是金黄色带紫色条纹的!
人参果已收获。。
人参果原来是白色的! ---> 派生类初始化时指定的
{'color': '绿色'}
{'color': '白色'} ---> 这里指定了颜色
"""
继承和派生是同一个概念吗?
答案来自GPT:
是的,继承和派生在面向对象编程中是同一个概念的两种不同叫法。继承是指创建一个新类(称为子类或派生类),该类从现有类(称为父类或基类)继承属性和方法。通过继承,子类可以重用父类的代码,并且还可以添加、修改或覆盖继承的行为。
所以,当我们说一个类从另一个类继承,我们也可以说这个类是从另一个类派生出来的。继承和派生是同一个概念的两种表述方式,只是用词上略有不同。
isinstance()
isinstance(obj,cls)
检查是否obj是否是类cls
的对象
isinstance()
函数接受两个参数,第一个参数为某个实例对象,第二个参数为某个类,能够检查第一个参数是否是第二个参数的 实例,并返回 True
或 False
class Dog:
# 犬科动物有四条腿
leg_num = 4
# 能奔跑
def run(self):
print('🐕💨 狗狗快跑')
# 开心的时候会摇尾巴
def happy(self):
print('🐕💓 狗狗开心地摇起了尾巴')
# 萨摩耶犬
class Samoyed(Dog):
def run(self):
print('🐕💨💨 萨摩耶狂奔起来,像一团漂浮的云')
# 柯基犬
class Corgi(Dog):
def run(self):
print('🐕💨 柯基飞快地摆动它的小短腿,屁股一晃一晃的')
# 牧羊犬
class Collie(Dog):
def run(self):
print('🐕💨💨💨 牧羊犬咻地一下蹿了出去,不愧是竞速王者')
# 猫咪
class Cat:
# 猫科动物有四条腿
leg_num = 4
# 走路时只会留下一列脚印
def run(self):
print('🐈🐾 猫咪迈着优雅的步伐')
# 布偶猫
class Dagdoll(Cat):
def run(self):
print('🐈🐾 布偶猫走起路来优雅,十分优雅!')
# 狸花猫
class DragonLi(Cat):
def run(self):
print('🐈🐾🐾🐾 狸花猫身手矫健,连走带跑,像只小豹子')
# 暹罗猫
class Siamese(Cat):
def run(self):
print('🐈🐾🐾 暹罗猫一边走一边喵喵地叫了起来')
pets = [Siamese(), DragonLi(), Corgi(), Collie(), Collie(), Samoyed(), Corgi(), DragonLi(), Siamese(), Samoyed()]
def welcome(animal):
# 类型检测,属于猫的派生类,就执行run方法
if isinstance(animal, Cat):
animal.run()
else:
print("[{}]这好像不是小猫,不好意思,不能进入猫猫乐园".format(animal.__class__.__name__))
for pet in pets:
welcome(pet)
"""
🐈🐾🐾 暹罗猫一边走一边喵喵地叫了起来
🐈🐾🐾🐾 狸花猫身手矫健,连走带跑,像只小豹子
[Corgi]这好像不是小猫,不好意思,不能进入猫猫乐园
[Collie]这好像不是小猫,不好意思,不能进入猫猫乐园
[Collie]这好像不是小猫,不好意思,不能进入猫猫乐园
[Samoyed]这好像不是小猫,不好意思,不能进入猫猫乐园
[Corgi]这好像不是小猫,不好意思,不能进入猫猫乐园
🐈🐾🐾🐾 狸花猫身手矫健,连走带跑,像只小豹子
🐈🐾🐾 暹罗猫一边走一边喵喵地叫了起来
[Samoyed]这好像不是小猫,不好意思,不能进入猫猫乐园
"""
issubclass()
issubclass(sub, super)
检查sub
类是否是super
类的派生类
issubclass(People, object)
Out[50]: True
issubclass(object, type)
Out[51]: False
class People:
pass
class Hero(People):
pass
print(issubclass(Hero, People)) # True
继承的棱形问题
一般编程语言都不允许多重继承,主要是为了避免菱形问题,即两个父 类如果有共同的祖父类,但对祖父类相同部分做了不同的修改,则这个类再继承 两个父类就会产生冲突。
Python的Mixins
机制
大多数面向对象语言都不支持多重继承,因为这会导致菱形问题, 而 Python 虽然形式上支持多重继承,但其实现机制却是利用 mixin
,从而有效 地避免了菱形问题。
Mixins
是一种在面向对象编程中,为了代码复用而提出的一种技术。它是一种类,提供了方法的实现,其他类可以访问Mixin
类的方法而不必成为其子类。Mixin
可以在多个类之间共享代码,而不需要继承。
# 定义一个Mixin类
class PrintInfoMixin:
def print_info(self):
print(f"Name: {self.name}, Age: {self.age}, Gender: {self.gender}, Hobby: {self.hobby}")
# 创建一个普通的类,并使用Mixin类
class Person(PrintInfoMixin):
def __init__(self, name, age, gender, hobby):
self.name = name
self.age = age
self.gender = gender
self.hobby = hobby
# 创建Person的实例
person = Person("小满", 3, "Female", "摸鱼")
person.print_info() # Name: 小满, Age: 3, Gender: Female, Hobby: 摸鱼
封装
访问限制
在类的内部可以定义属性和方法,而在类的外部则可以直接调用属性或方法来操作数据,从而隐藏了类内部的复杂逻辑。但是Python并没有对属性和方法的访问权限进行限制。为了保证类内部的某些属性或方法不被外部所访问,可以在属性或方法名前面添加双下划线(__foo)
或首尾加双下划线(__foo__)
,从而限制访问权限。其中,双下划线、首尾双下划线的作用如下:
(1)首尾双下划线表示定义特殊方法,一般是系统定义名字,如
__init__()
。
(2)双下划线表示private(私有)类型的成员,只允许定义该方法的类本身进行访问,而且也不能通过类的实例进行访问,但是可以通过“类的实例名._类名__xxx
”方式访问。
class Hero:
__hobby = "摸鱼"
def __init__(self, name, age):
self.name = name
self.age = age
def showInfo(self):
# 内部可以调用私有属性
print(self.__hobby)
xm = Hero("小满", 3)
xm.showInfo() # 摸鱼
# 实例不能直接访问私有属性
xm.__hobby # AttributeError: 'Hero' object has no attribute '__hobby'
# 类也不能直接访问私有属性
Hero.__hobby # AttributeError: type object 'Hero' has no attribute '__hobby'
修改私有属性以及执行私有方法:
class Hero:
def __init__(self, name, age):
self.name = name
self.age = age
self.__hobby = '摸鱼'
# 定义修改私有变量的方法,就可以在外部直接修改了
def change(self, hobby):
self.__hobby = hobby
def __toys(self):
print("里面是一大堆的玩具~~")
def showToys(self):
self.__toys()
def __str__(self):
return f"姓名:{self.name} 年龄:{self.age}岁 爱好:{self.__hobby}"
# 直接访问,报错
# print(xm.__hobby)
# AttributeError: 'Student' object has no attribute '__hobby'
xm = Hero("小满", 3)
print(xm) # 姓名:小满 年龄:3岁 爱好:摸鱼
# 修改方式1 通过 实例名._类名__xx去修改 (非真正的修改,只是新建了一个)
xm._Hero__hobby = "抢红buff"
print(xm) # 姓名:小满 年龄:3岁 爱好:抢红buff
# 修改方式2 内部定义修改方法 外部调用修改方法
xm.change("抢人头")
print(xm) # 姓名:小满 年龄:3岁 爱好:抢人头
# 直接调用私有方法,报错
# xm.__toys()
# AttributeError: 'Hero' object has no attribute '__toys'
# 通过 实例名._类名__xx去访问
xm._Hero__toys() # 里面是一大堆的玩具~~
# 内部定义访问方法 外部调用访问方法
xm.showToys() # 里面是一大堆的玩具~~
注意:虽然两种方法都可以在外部访问到私有方法属性,以及外部也可以修改私有属性,但是不建议在外部操作
注意:封装__不支持跨类使用。
__slot__
__slots__
是一个特殊的类属性,用于限制类的实例
可以拥有的属性。通过使用
__slots__
,你可以告诉Python仅为类的实例分配指定的属性,从而节省了内存空间。当你知道类的实例只需要固定的一组属性时,使用__slots__
可以提高性能。 下面是一个示例,演示了如何使用__slots__
:
class Hero:
__slots__ = ('name', 'age')
def __init__(self):
self.name = '小满'
self.age = 3
hero = Hero()
print(hero.name) # 小满
print(hero.age) # 3
hero.hobby = "摸鱼" # # 无法为属性hobby分配内存,会引发 AttributeError
print(hero.hobby) # AttributeError: 'Hero' object has no attribute 'hobby'
对类属性没有限制
class Hero:
__slots__ = ('name', 'age')
def __init__(self):
self.name = '小满'
self.age = 3
Hero.hobby = "摸鱼"
print(vars(Hero))
"""
{'__module__': '__main__',
'__slots__': ('name', 'age'),
'__init__': <function Hero.__init__ at 0x000001FC6A15BAC0>,
'age': <member 'age' of 'Hero' objects>,
'name': <member 'name' of 'Hero' objects>,
'__doc__': None,
'hobby': '摸鱼'}
"""
对派生类也没有限制
class Person:
__slots__ = ("name", "age")
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
pass
eva = Student("eva", 17)
print(eva.name) # eva
print(eva.age) # 17
eva.addr = "花果山"
eva.score = 89
print(eva.addr) # 花果山
print(eva.score) # 89
多态和多态性
多态
多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
- 序列数据类型有多种形态:字符串,列表,元组
- 动物有多种形态:人,狗,猪
多态是指同一个方法名可以在不同的类中具有不同的实现方式。
class Animal:
def speak(self):
print("Animal speaks")
class Dog(Animal):
def speak(self):
print("Dog barks")
class Cat(Animal):
def speak(self):
print("Cat meows")
# 创建 Animal、Dog 和 Cat 实例
animal = Animal()
dog = Dog()
cat = Cat()
# 调用 speak 方法
animal.speak() # 输出: Animal speaks
dog.speak() # 输出: Dog barks
cat.speak() # 输出: Cat meows
# 使用父类类型的变量引用子类对象,实现多态
animal = Dog()
animal.speak() # 输出: Dog barks
animal = Cat()
animal.speak() # 输出: Cat meows
abc
模块
多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象。
指定
metaclass
属性将类设置为抽象类,抽象类本身只是用来约束子类的,抽象类本身不能被实例化,否则报错。
import abc
class Animals(metaclass=abc.ABCMeta):
@abc.abstractmethod
def run(self):
print(1)
pass
animals = Animals() # TypeError: Can't instantiate abstract class Animals with abstract method run
子类约定俗成必须有这个抽象类指定的方法,不然会报错
import abc
class Animals(metaclass=abc.ABCMeta):
@abc.abstractmethod # 子类约定俗成必须有这个父类指定的方法 不然会报错
def language(self): # 通常情况下,父类指定的方法里面只给一个pass 因为会被子类给重写,只是约定规范
pass
class People(Animals):
pass
# 若子类中没有一个定义抽象类指定的方法则会抛出异常TypeError,无法实例化
people = People() # TypeError: Can't instantiate abstract class People with abstract method language
正常设置
import abc
class Animals(metaclass=abc.ABCMeta):
@abc.abstractmethod
def language(self):
pass
class People(Animals):
def language(self):
print("说各种语言")
class Pig(Animals):
def language(self):
print("哼唧哼唧")
class Dog(Animals):
def language(self):
print("汪汪汪")
people = People()
pig = Pig()
dog = Dog()
people.language() # 说各种语言
pig.language() # 哼唧哼唧
dog.language() # 汪汪汪
多态性
注意:多态与多态性是两种概念
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。
所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
import abc
class Animals(metaclass=abc.ABCMeta):
@abc.abstractmethod
def run(self):
pass
class People(Animals):
def run(self):
print("人正在跑步")
class Dog(Animals):
def run(self):
print("狗正在狂奔")
people = People()
dog = Dog()
def func(obj):
obj.run()
func(people) # 人正在跑步
func(dog) # 狗正在狂奔
多态性依赖于:继承
多态性:定义统一的接口
obj这个参数没有类型限制,可以传入不同类型的值
调用的逻辑都一样,执行的结果却不一样
综上可以说,多态性是一个接口(函数func)的多种实现(如obj.run(),obj.talk(),obj.click(),len(obj))
多态性的好处
增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
鸭子类型
鸭子类型的名称来源于“如果它走起来像鸭子,游泳起来像鸭子,叫起来像鸭子,那么它就是鸭子”的说法。
由于支持鸭子类型测试,所以Python解释器不检查发生多态的对象是否继承了同一个父类,只要它们有相同的行为(方法),它们之间就是多态的。
这意味着在鸭子类型中,我们不关心对象的具体类型,而是关心它是否具有所需的方法和属性。
class Duck:
def quack(self): # 定义一个叫quack的方法,打印"Quaaaaaak!"
print("Quaaaaaak!")
class Bird:
def quack(self): # 定义一个叫quack的方法,打印"bird imitate duck."
print("bird imitate duck.")
class Dog:
def quack(self): # 定义一个叫quack的方法,打印"Dog imitate duck."
print("Dog imitate duck.")
def zoo(animal): # 定义一个叫zoo的函数,接受一个动物类型的对象作为参数
animal.quack() # 调用动物类型的quack方法
duck = Duck() # 创建一个Duck类的实例
bird = Bird() # 创建一个Bird类的实例
dog = Dog() # 创建一个Dog类的实例
for animal in [duck, bird, dog]: # 遍历包含动物类型的对象列表
zoo(animal) # 调用zoo函数,让动物类型的对象执行quack方法
"""
Quaaaaaack!
bird imitate duck.
Dog imitate duck.
"""
鸭子自然会嘎嘎的叫
小鸟也会叫,这鸟叫声跟鸭子叫声很类似, 哇哇啾啾
小狗也会叫,这狗叫声跟鸭子叫声也很类似,哇哇
既然叫声都很类似,那就认为它们都是鸭子,直接指鸟为鸭,指狗为鸭,
我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
在Python中,鸭子类型编程可以通过以下方式实现:
- 不依赖具体的类型:编写代码时,不需要关注对象的具体类型,而是关注对象是否具有所需的方法或属性。例如,如果一个对象具有
read()
和write()
方法,那么它可以被当作文件对象来使用,而不需要是file
类型的实例。 - 使用try-except语句:在使用某个方法或属性之前,可以使用
try-except
语句来捕获可能的异常。如果对象具有所需的方法或属性,那么代码将正常执行;如果对象没有所需的方法或属性,那么会抛出异常,可以在except
块中处理该异常。
def proces_data(obj):
try:
obj.read()
obj.process()
obj.write()
except AttributeError:
print("对象不具有所需的方法或属性。")
class FileObject:
def read(self):
print("读取文件")
def process(self):
print("处理数据")
def write(self):
print("写入文件")
class DatabaseObject:
def read(self):
print("读取数据库")
def process(self):
print("处理数据")
def write(self):
print("写入数据库")
file_obj = FileObject()
database_obj = DatabaseObject()
proces_data(file_obj) # 输出 读取文件 处理数据 写入文件
proces_data(database_obj) # 输出 读取数据库 处理数据 写入数据库
proces_data("deleted") # 对象不具有所需的方法或属性。
在上面的示例中,我们定义了一个
process_data()
函数,它接受一个对象作为参数。函数内部通过调用对象的read()
、process()
和write()
方法来处理数据。无论传入的对象是FileObject
还是DatabaseObject
,只要它们具有相应的方法,就可以被process_data()
函数处理。 需要注意的是,鸭子类型编程的核心是关注对象的行为而不是具体的类型。这种编程方式可以提高代码的灵活性和可复用性,但也需要保证对象在使用时确实具有所需的方法和属性,否则可能会导致运行时错误。
反射
反射指的是在程序运行过程中可以"动态"获取对象的信息。比如判断用户输入的一个字符串是不是一个对象的属性。通过反射可以避免程序使用较为繁琐的if-elif-else
判断,使得代码结构简洁清晰。
class Ftp:
def get(self):
pass
def put(self):
pass
def set(self):
pass
ftp = Ftp()
cmd = input("请输入指令:").strip()
if cmd == "get":
ftp.get()
elif cmd == "put":
ftp.put()
elif cmd == "set":
ftp.set()
else:
print("指令不存在")
hasattr()
判断对象是否有指定属性,返回布尔值
# 语法 hasattr(obj, name)
class Foo:
name = "eva"
foo = Foo()
hasattr(foo, "name") # True
hasattr(foo, "age") # False
getattr()
动态获取对象的属性值
如果获取到的是对象中的函数,返回的结果是该函数的内存地址,可以加括号去调用
# 语法 getattr(obj, name, default=None) 如果捕不设置默认值,那么获取不到结果会直接报错。
# getattr 语法 getattr(obj, name)
class Foo:
name = '小满'
def say(self):
print(f"你好!我是{self.name}")
foo = Foo()
getattr(foo, "name") # '小满'
# getattr(foo, age) # NameError: name 'age' is not defined
getattr(foo, "age", False) # False
getattr(foo, 'say') # <bound method Foo.say of <__main__.Foo object at 0x000001A1E8B3E230>>
func = getattr(foo, "say")
func() # 你好!我是小满
setattr()
动态的设置属性
# 语法 setattr(obj, key, value) 更新对象key的值,等价于 obj.key = value, 当key不存在时新增。
class Foo:
age = 3
foo = Foo()
setattr(foo, "name", "小满")
foo.name # '小满'
setattr(foo, 'age', 4)
foo.age # 4
delattr()
删除一个对象的属性
# 语法 delattr(obj, name) 等价于 del obj.name 若不存在则会报错
class Foo:
def __init__(self, name):
self.name = name
foo = Foo("小满")
vars(foo) # {'name': '小满'}
delattr(foo, 'name')
vars(foo) # {}
foo.name # AttributeError: 'Foo' object has no attribute 'name'
类也是对象,也可以使用反射
class Foo:
name = "小满"
if hasattr(Foo, "name"):
print(Foo.name)
setattr(Foo, "age", 3)
else:
delattr(Foo, "age")
vars(Foo)
"""
mappingproxy({'__module__': '__main__',
'name': '小满',
'__dict__': <attribute '__dict__' of 'Foo' objects>,
'__weakref__': <attribute '__weakref__' of 'Foo' objects>,
'__doc__': None,
'age': 3})
"""
优化上面的案例
class Ftp:
def get(self):
pass
def put(self):
pass
def set(self):
pass
ftp = Ftp()
cmd = input("请输入指令:").strip()
if hasattr(ftp, cmd):
getattr(ftp, cmd)
else:
print("Error")
常用魔法方法
魔法方法是指 Python 内部已经包含的,被双下划线所包围的方法,这些方法在进行特定的操作时会被自动调用,它们是 Python 面向对象下智慧的结晶。
魔法方法名 | 说明 |
---|---|
__init__(self[...]) |
初始化方法,初始化类的时候被调用 |
__del__(self) |
析构方法,当实例化对象被彻底销毁时被调用(实例化对象的所有指针被销毁时调用) |
__call__(self[,args...]) |
允许一个类的实例像函数一样被调用:x(a,b)调用x.__call__(a,b) |
__len__(self) |
定义被当成len() 调用时的行为 |
__repr__(self) |
定义被当成repr() 调用时的行为 |
__bytes__(self) |
定义被当成bytes() 调用时的行为 |
__hash__(self) |
定义被当成hash() 调用时的行为 |
__bool__(self) |
定义被当成bool() 调用时的行为,应该返回True 或False |
__format__(self,format_apec) |
定义被format() 调用时的行为 |
__new__(cls[...]) |
1.实例化对象时第一个被调用的方法 |
2. 其参数直接传递给__init__ 方法处理 |
|
3. 我们一般不会重写该方法 | |
__doc__ |
查看类的简介 |
__dict__ |
查看类的属性,是一个字典 |
__module__ |
类定义所在的模块 |
__name__ |
类名 |
__getattr()__
当访问的属性不存在的时候报错
class Hero:
name = "小满"
def say(self):
print(f"你好!我是{Hero.attr}")
def __getattr__(self, attr):
return f"你访问的{item}属性不存在。"
xm = Hero()
print(xm.age) # 你访问的age属性不存在。
xm.say() # 你好!我是小满
__getattribute__
注意:
__getattribute__
方法是在访问属性时被调用的,而在该方法内部如果调用了 print 语句,会导致再次调用__getattribute__
方法,从而形成无限递归。
class Hero:
name = "小满"
def say(self):
print(f"你好!我是{Hero.name}")
def __getattr__(self, attr):
return f"你访问的{attr}属性不存在。"
def __getattribute__(self, attr):
return f"我才不管你访问的属性[{attr}]存不存在!我都要执行! ^_^"
# 报错TypeError: 'str' object is not callable
# 导致错误的原因是在 __getattribute__ 方法中返回了一个字符串,而不是返回属性的值。
# 因此,在调用 xm.say() 时会出现 TypeError: 'str' object is not callable 错误。
xm = Hero()
print(xm.age)
xm.say()
class Hero:
name = "小满"
def say(self):
print(f"你好!我是{Hero.name}")
def __getattribute__(self, name):
try:
return super().__getattribute__(name)
except AttributeError:
return f"属性[{name}]不存在"
xm = Hero()
print(xm.age) # 属性[age]不存在
xm.say() # 你好!我是小满
当__getattr__
和__getattribute__
同时出现时
如果
__getattribute__
捕获到了异常,那么不会执行后续的代码,也就是__getattribute__
的优先级是高于__getattr__
的。
class Hero:
name = "小满"
def say(self):
print(f"你好!我是{Hero.name}")
def __getattr__(self):
print("执行的是我!")
return f"属性[{self}]不存在。。。。"
def __getattribute__(self, name):
try:
return super().__getattribute__(name)
except AttributeError:
return f"属性[{name}]不存在"
xm = Hero()
print(xm.age) # 属性[age]不存在
xm.say() # 你好!我是小满
__get__item()
__getitem__(self, key)
:该方法用于获取指定键或索引的元素。当我们使用类似 obj[key] 的方式访问对象时,__getitem__
方法会被调用。它接受一个参数 key,表示要获取的元素的键或索引,并返回对应的值。
__setitem__()
__setitem__(self, key, value)
:该方法用于设置指定键或索引的元素的值。当我们使用类似 obj[key] = value 的方式设置对象的元素时,__setitem__
方法会被调用。
__delitem__()
__delitem__(self, key)
:该方法用于删除指定键或索引的元素。当我们使用类似del obj[key]
的方式删除对象的元素时,__delitem__
方法会被调用。它接受一个参数 key,表示要删除的元素的键或索引。
class MyList:
def __init__(self):
self.data = []
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, index, value):
if index >= len(self.data):
self.data.append(value)
else:
self.data[index] = value
def __delitem__(self, index):
del self.data[index]
# 创建一个 MyList 实例
my_list = MyList()
# 添加元素
my_list[0] = "小满"
my_list[1] = "大乔"
my_list[2] = "阿珂"
# 获取元素
print(my_list[1]) # 大乔
# 删除元素
del my_list[0]
# 打印剩余的元素
print(my_list.data) # ['大乔', '阿珂']
上下文管理器
上下文管理器是Python中用于管理资源的一种机制。它提供了确保资源在使用完毕后被正确释放的方式,无论代码是否发生异常。上下文管理器可以使用 with
语句来管理资源的获取和释放。
在Python中,上下文管理器是通过实现 __enter__
和 __exit__
方法来实现的。当进入 with
代码块时,__enter__
方法被调用,用于获取资源并返回一个对象。当退出 with
代码块时,无论代码是否发生异常,__exit__
方法都会被调用,用于释放资源。
使用上下文管理器的好处是可以确保资源的正确释放,即使代码发生异常也不会导致资源泄漏。此外,上下文管理器还可以提供一些额外的功能,例如记录日志、处理异常等。
Python中有多种方式可以创建上下文管理器,包括使用类实现和使用 contextlib
模块提供的装饰器和上下文管理器生成器。
下面是一个示例,演示了如何使用类来创建一个简单的上下文管理器:
这个例子并不恰当
class FileWriter(object):
"""
文件写入对象,用于在 with 语句中写入文件。
"""
def __init__(self, file_name):
"""
初始化 FileWriter 对象。
Args:
file_name (str): 文件名。
"""
self.file_name = file_name
def __enter__(self):
"""
进入 with 语句块时调用的方法,打开文件并返回文件对象。
Returns:
file: 文件对象。
"""
self.file = open(self.file_name, "w", encoding="utf-8")
return self.file
def __exit__(self, exc_type, exc_value, traceback):
"""
退出 with 语句块时调用的方法,负责关闭文件。
Args:
exc_type: 异常类型。
exc_value: 异常实例。
traceback: 异常追踪信息。
"""
if self.file:
self.file.close()
# 通过 with 语句使用 FileWriter
with FileWriter("message.txt") as f:
f.write('你好!我是小满 ^_^')
上下文管理器是一种非常有用的机制,可以确保资源的正确释放,提高代码的可读性和可维护性。在处理文件、数据库连接、网络连接等需要手动释放资源的场景中,使用上下文管理器可以减少错误和资源泄漏的可能性。
参数下面的案例,
as 后面的那个变量
就是__enter__
的返回值 ,这样就非常好理解了
import time
class Timer:
def __init__(self):
self.default_time = 0
def __enter__(self):
# 记录进入上下文时的当前时间
self.t1 = time.time()
return self
def __exit__(self, exc_type, exc_value, traceback):
# 记录退出上下文时的当前时间
t2 = time.time()
# 计算时间差,得到执行时间
self.default_time = t2 - self.t1
# 使用 Timer 上下文管理器计时列表推导式的执行时间
with Timer() as timer:
# 列表推导式:生成一个包含1000000个元素的列表,每个元素为索引的平方
g = [index ** 2 for index in range(1000000)]
# 打印执行完成的消息和计时结果
print('The End')
print(timer.default_time)
"""
The End
0.4982779026031494
"""
元类
class机制
class是python的一个关键字,目的是用来创建类,它在底层实现类的定义本质上有四个步骤。
class People:
def __init__(self, name):
self.name = name
def talk(self):
print('hello')
# 类的三大特征:
- 类名:People
- 基类们:object,
- 名称空间:__dict__
-
获取类名:
class_name=People
-
获取基类们:
class_bases=(object,)
-
获取类的名称空间:
class_dict={}
-
调用元类
type
实例化产生People
类这个对象# 类名 = type(类名,(父类1,父类2...),{类里面的数据属性}) People = type(class_name, class_bases, class_dict) # ----------------------------------------------- # People = type("People", (object,), {})
使用type去动态创建类
Person = type("Person", (), {}) # 如果不指定 那么父类就是object
p1 = Person()
print(p1)
print(Person)
print(Person.__mro__) # (<class '__main__.Person'>, <class 'object'>)
class Animal():
def __init__(self, color):
self.color = color
def eat(self):
print("动物需要吃东西")
def sleep(self):
print("狗狗趴着睡觉")
# 使用type动态创建一个类,父类就是Animal
Dog = type("Dog", (Animal,), {"age": 3, 'sleep':sleep}) # 创建了属性以及方法
# 因为继承了父类Animal 所以这里会报错
# dog = Dog() # TypeError: Animal.__init__() missing 1 required positional argument: 'color'
dog = Dog('Yellow')
dog.sleep() # 狗狗趴着睡觉
# 是否继承了父类中的特性
# 父类中的属性
print(dog.color) # Yellow
# 是否继承了父类的方法
dog.eat() # 动物需要吃东西
content = """
def __init__(self):
self.name = "eva"
self.age = 17
def show(self):
print(f"姓名是:{self.name} 年龄是:{self.age}.")
def __str__(self):
return "这是一个通过type动态生成的类"
"""
dic = {}
exec(content, globals(), dic)
Student = type("Student", (object,), dic)
s1 = Student()
print(s1)
print(vars(s1))
s1.show()
"""
这是一个通过type动态生成的类
{'name': 'eva', 'age': 17}
姓名是:eva 年龄是:17.
"""
使用type去动态创建类的时候,第一个参数(即类名)应该与前面的变量名保持一致。
上述四部是定义元类的底层原理,也是我们定义产生一个类的第二种方式。
什么是元类
元类是Python中一个非常强大且高级的概念。简单来说,元类是用来创建类的类。在Python中,类是对象,而元类是用来创建这些类的对象。
如果把类也看做对象的话,那类这个对象的类就成为类的类,即元类。python中内置的默认元类是
type
我们用class关键字定义的所有的类以及内置的类都是由元类type实例化产生。
自定义元类
在上面class机制的四步中,我们可以发挥的地方在第四步,这就需要我们自己去定义一个元类。
class机制默认的元类是type, 我们可以修改
metaclass
参数来选择自定义元类
class People(metaclass=type):
def __init__(self, name):
self.name = name
def talk(self):
print("Hello")
注意:在python中,type
是一切类的基石,我们自定义的元类也必须继承type
。
目的
自定义元类继承type
的目的是为了使用type
的大部分功能,我们只定制我们需要的那部分功能。
# 只有继承了type的类才能作为元类使用
class MyMeta(type):
pass
# 使用MyMeta元类,即MyMeta(class_name, class_bases, class_dict)
class People(metaclass=MyMeta):
def __init__(self, name):
self.name = name
def talk(self):
print("Hello world")
class People:
def __init__(self, name, age):
self.name = name
eva = People("eva", 17)
"""
实例化一个对象发生的三件事情:
1. 创建一个空对象
2. 初始化空对象
3. 返回初始化完成的对象
- 创建空对象是由 __new__() 方法实现, __new__ 创建并返回一个空对象
- __init__ 接收这个空对象,并初始化该对象
- 最后返回初始化完成的对象。
整个散步流程是由类的类,即元类中的__call__方法管理的。
因为实例化对象,本身是类的调用,类本身又是一个对象,当它调用时就会触发其它类的__call__函数的执行
即实例化对象时,触发元类的__call__函数。
在元类__call__内实现实例化的三件事。
当我们默认使用的是type原来时,想要自定制实例化的过程的需求都是无法实现的,因为无法修改内置元类type的__call__方法。
当我们使用自定义元类时,就可以实现实例化过程的自定制。因为我们可以重写自定义元类的__call__方法,即自定制实例化需求。
"""
自定义元类控制类的调用
控制类的调用就是控制对象的实例化过程
类也是一个对象,它的类是元类。类的调用就会触发元类
__call__
函数执行。
class MyMeta(type):
# self是People, *args **kwargs 接收类调用时括号内的实参
def __call__(self, *args, **kwargs):
print("我是MyMeta中的__call__ 我被执行了")
# 调用类下面的方法,必须手动传参self
people_obj = self.__new__(self, *args, **kwargs)
# 类调用手动传参people_obj和*args **kwargs 还可以是
# people_obj.__init__(*args, **kwargs) 此时是对象调用,自动传参
self.__init__(people_obj, *args, **kwargs)
return people_obj
class People(metaclass=MyMeta):
def __init__(self, name, age):
print("我是People中的__init__ 我被执行了")
self.name = name
self.age = age
# 此处不写__new__ 在元类的__call__方法中就会使用其父类的__new__
def __new__(cls, *args, **kwargs):
print("我是People中的__new__我被执行了")
print(f"此时的cls是{cls.__name__}")
# 本质还是使用父类的__new__ 没有继承则使用object的__new__
return super().__new__(cls)
eva = People("eva", 17) # People.__call__("eva", 17)
print(eva.__dict__)
"""
我是MyMeta中的__call__ 我被执行了
我是People中的__new__我被执行了
此时的cls是People
我是People中的__init__ 我被执行了
{'name': 'eva', 'age': 17}
"""
此处的元类MyMeta
是一个自定义元类模板,我们需要自定制对象实例化过程中的需求时可以在其中的__call__
里面的三步中添加需求功能。
自定义元类控制类的定义
类的定义是通过元类的实例化产生的,因此类的定义过程必然牵涉到元类的__new__()
、__init__()
以及元类的__call__()
,因此我们通过自定义元类控制类的定义,就需要在自定义的元类实现__new__()
和__init__()
。
class MyMeta(type):
def __init__(self, class_name, class_bases, class_dict):
# 自定制需求
if not class_name.istitle():
raise NameError('出错了,类名的首字母必须要大写。')
# if not getattr(self, '__doc__', None): 两种写法都可以
if not self.__doc__:
raise TypeError("出错了,必须要有文档注释。")
super().__init__(class_name, class_bases, class_dict)
def __new__(cls, *args, **kwargs):
class_obj = type.__new__(cls, *args, **kwargs)
print(class_obj.__dict__) # {'__module__': '__main__', '__doc__': '我的基类是MyMeta', '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>}
return class_obj
# 元类的类.__call__(self, *args, **kwargs)
# 组织管理,元类.__new__、__init__
# class people(object, metaclass=MyMeta):
# pass
"""
raise NameError('出错了,类名的首字母必须要大写。')
NameError: 出错了,类名的首字母必须要大写
"""
# class People(object, metaclass=MyMeta):
# pass
"""
raise TypeError("出错了,必须要有文档注释。")
TypeError: 出错了,必须要有文档注释。
"""
class People(object, metaclass=MyMeta):
"""我的基类是MyMeta"""
def __init__(self, name):
self.name = name
def foo(self):
pass
eva = People("eva")
print(vars(eva)) # {'name': 'eva'}
属性查找
从对象出发的属性查找: 对象 ---> 对象所在的类 ---> 父类 ---> object
从类出发的属性查找: 类 ---> 父类 ---> object ---> 元类 ---> type
总结
type和object的关系
type是object的类,即object是由type实例化得到. (type是所有类的元类)
type是type自己的类
type又继承了object
拓展
__init_subclass__()
__init_subclass__
是一个特殊的类方法,在 Python 3.6 中引入。它允许我们在定义一个类时,自动调用其子类的方法。当一个类被定义为另一个类的子类时,__init_subclass__
方法会在子类的定义过程中被调用。
class Hook:
# __init_subclass__ 当类被当做父类继承,就会触发该方法自动执行
def __init_subclass__(cls, **kwargs):
# 此处的cls是被继承的子类,并非父类
print("__init_subclass__", cls, kwargs)
class A(Hook, name="eva", age=17):
pass
"""
__init_subclass__ <class '__main__.A'> {'name': 'eva', 'age': 17}
"""
模拟元类控制类的生成过程
class Hook:
def __init_subclass__(cls, **kwargs):
for k, v in kwargs.items():
type.__setattr__(cls, k, v)
class A(Hook, name="eva", age=17):
pass
print(A.name) # 输出: eva
print(A.age) # 输出: 17
dataclass
初始化实例对象
dataclass是
Python 3.7
引入的一个装饰器,用于自动创建具有一些常见功能的数据类。数据类主要用于存储数据,而不带有复杂的逻辑或方法。使用
dataclass
装饰器可以简化数据类的定义过程,自动为类添加一些特殊方法(如__init__
、__repr__
、__eq__
等),以及自动为类的属性生成默认值。
# 实例初始化,结果和 __init__ 一样
from dataclasses import dataclass
@dataclass
class Student:
name: str
age: int
hobby: str = "摸鱼"
xm = Student("小满", 3)
print(xm) # Student(name='小满', age=3, hobby='摸鱼')
print(xm.__class__) # <class '__main__.Student'>
print(type(xm)) # <class '__main__.Student'>
print(vars(xm)) # {'name': '小满', 'age': 3, 'hobby': '摸鱼'}
# 和正常类一样,用父类访问实例的对象会报错
print(Student.name) # AttributeError: type object 'Student' has no attribute 'name'
不可变对象
使用
frozen=True
的参数将创建一个不可变的数据类。
from dataclasses import dataclass
@dataclass(frozen=True) # 使用frozen=True的参数将创建一个不可变的数据类。
class Student: # 这意味着一旦创建了Student对象,就无法修改其属性的值。
name:str # 和私有属性差不多
age: int
hobby: str = "摸鱼" # 这里理解为默认参数就可以了
print(Student.__dict__)
xm = Student("小满", 3, "抢人头")
print(vars(xm)) # {'name': '小满', 'age': 3, 'hobby': '抢人头'}
class CodeClass(Student):
pass
eva = CodeClass() # 派生类继承一样需要传参,不然会报错
# TypeError: Student.__init__() missing 2 required positional arguments: 'name' and 'age'
print(vars(eva))
如果不指定类型一样会报错
from dataclasses import dataclass
@dataclass(frozen=True)
class Student:
name:str
age # NameError: name 'age' is not defined
hobby: str = "摸鱼"
设置了
frozen=True
的时候,也不能通过 实例名._类名__xx 去访问因为这几个属性本身就不是私有属性
from dataclasses import dataclass
@dataclass(frozen=True)
class Student:
name:str
age # NameError: name 'age' is not defined
hobby: str = "摸鱼"
xm = Student("小满", 3, "抢人头")
print(vars(xm))
# 也不能通过 实例名._类名__xx 去访问
print(xm._Hero__name) # AttributeError: 'Student' object has no attribute '_Hero__name'
在没有设置frozen=True的前提下
如果定义的时候设置成私有属性,就可以正常通过 实例名._类名__xx 去访问或者修改了
from dataclasses import dataclass
@dataclass
class Student:
name:str
__age: 3
hobby: str = "摸鱼"
xm = Student("小满", 3, "抢人头")
# print(xm.age) # AttributeError: 'Student' object has no attribute 'age'
print(vars(xm)) # {'name': '小满', '_Student__age': 3, 'hobby': '抢人头'}
# xm.age = 4 # FrozenInstanceError: cannot assign to field 'age'
print(xm._Student__age) # 3
xm._Student__age = 4
print(xm._Student__age) # 4
如果设置了frozen=True,则可以通过 实例名._类名__xx 去访问 ,但是不能去修改
from dataclasses import dataclass
@dataclass(frozen=True)
class Student:
name:str
__age: 3
hobby: str = "摸鱼"
xm = Student("小满", 3, "抢人头")
xm._Student__age = 4 # FrozenInstanceError: cannot assign to field '_Student__age'
定制属性行为
field
通过field()来定制属性的特征()
使用的时候需要导入field
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
age: int
hobby: str = "摸鱼"
independent: bool = field(default=False, init=True)
# 注意这里 init=True 意思就是默认会实例化这个值
# 如果不希望这个值被实例化,改成False就可以了
xm = Student("小满", 3)
print(Student.independent) # False
print(vars(xm)) # {'name': '小满', 'age': 3, 'hobby': '摸鱼', 'independent': False}
init参数
当init=False的时候,就不会实例化这个属性了
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
age: int
hobby: str = "摸鱼"
independent: bool = field(default=False, init=False)
xm = Student("小满", 3)
print(Student.independent) # False
print(vars(xm)) # {'name': '小满', 'age': 3, 'hobby': '摸鱼'}
repr参数
repr这个参数,就是直接打印实例的时候是否会显示,默认为True(即会正常显示打印)
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
age: int
hobby: str = "摸鱼"
independent: bool = field(default=False, init=True, repr=True) #
xm = Student("小满", 3)
print(xm) # Student(name='小满', age=3, hobby='摸鱼', independent=False)
print(vars(xm)) # {'name': '小满', 'age': 3, 'hobby': '摸鱼', 'independent': False}
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
age: int
hobby: str = "摸鱼"
independent: bool = field(default=False, init=True, repr=False)
xm = Student("小满", 3)
print(xm) # Student(name='小满', age=3, hobby='摸鱼')
print(vars(xm)) # {'name': '小满', 'age': 3, 'hobby': '摸鱼', 'independent': False}
__post_init__
方法
__post_init__
是一个特殊方法(special method),在数据类(data class)实例创建后自动调用。在数据类中,
__post_init__
方法可以用来执行一些初始化操作或对属性进行进一步处理。它接受没有参数(除了self)的方法。当数据类实例创建后,__post_init__
方法会被调用,允许你在对象初始化完成后执行一些额外的逻辑。
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
age: int
hobby: str = "摸鱼"
independent: bool = field(default=False, init=False)
def __post_init__(self):
if self.age < 0:
raise ValueError("年龄不能为负数")
xm = Student("小满", -1) # ValueError: 年龄不能为负数
排序
通过
@dataclass(order=True)来支持排序
通过
opeartor.attrgetter
来支持排序 (此知识点和dataclass
无关)
使用order=True
去排序
from dataclasses import dataclass
@dataclass(order=True)
class Student:
name: str
age: int
hobby: str = "摸鱼"
xm = Student("小满", 3)
xq = Student("小乔", 4, '抢人头')
students = [xm, xq]
sorted_students = sorted(students)
print(sorted_students) # [Student(name='小乔', age=4, hobby='抢人头'), Student(name='小满', age=3, hobby='摸鱼')]
排序默认是通过第一个参数去排序的,如果把第一个参数设置成为年龄,然后不加载到init里面,就可以实现排序
from dataclasses import dataclass, field
@dataclass(order=True)
class Student:
sort_index: int = field(init=False, repr=False)
name: str
age: int
hobby: str = "摸鱼"
def __post_init__(self):
self.sort_index = self.age
xm = Student("小满", 3)
xq = Student("小乔", 4, '抢人头')
students = [xm, xq]
sorted_students = sorted(students)
print(sorted_students) # [Student(name='小满', age=3, hobby='摸鱼'), Student(name='小乔', age=4, hobby='抢人头')]
通过opeartor.attrgetter
排序
需要导入对应的模块
import operator
from dataclasses import dataclass
@dataclass(order=True)
class Student:
name: str
age: int
hobby: str = "摸鱼"
xm = Student("小满", 3)
xq = Student("小乔", 4, '抢人头')
students = [xm, xq]
students.sort(key=operator.attrgetter('age'))
print(students) # [Student(name='小满', age=3, hobby='摸鱼'), Student(name='小乔', age=4, hobby='抢人头')]
类的一些练习
ATM程序
class ATM:
def __init__(self):
self.user_dict = {} # 用于存储用户信息的字典
self.name = '' # 当前登录用户的用户名
def fetch_input(self):
username = input("输入账号:").strip() # 获取用户输入的账号
password = input("输入密码:").strip() # 获取用户输入的密码
return username, password
def register(self):
print("开始注册".center(40, "-")) # 打印注册提示信息
username, password = self.fetch_input() # 获取用户输入的账号和密码
print(f'恭喜!用户【{username}】注册成功。') # 打印注册成功信息
data = {"username":username, "password":password, "balance":1000} # 创建用户信息字典
self.user_dict.update({username: data}) # 将用户信息字典添加到用户字典中
def login(self):
print("开始登录".center(40, '-')) # 打印登录提示信息
username, password = self.fetch_input() # 获取用户输入的账号和密码
if username not in self.user_dict:
print(f"用户名【{username}】不存在,请先注册。") # 打印用户名不存在的错误信息
else:
if password == self.user_dict[username]['password']:
print(f"用户【{username}】登录成功,进入取钱功能。") # 打印登录成功信息
self.name = username # 将当前登录用户的用户名设置为类属性
self.withdraw_money() # 调用取款方法
else:
print(f"密码不对,请尝试重新登录。") # 打印密码错误的提示信息
self.login() # 重新调用登录方法
def withdraw_money(self):
print("开始取款".center(40, '-')) # 打印取款提示信息
try:
real_balance = self.user_dict[self.name]['balance'] # 获取当前用户的余额
balance = input(f"当前余额为{real_balance}元,请输入取款的金额:").strip() # 获取用户输入的取款金额
assert balance.isdigit() and float(balance) <= real_balance, "非法输入" # 断言取款金额合法性
except Exception as e:
print(e) # 打印异常信息
else:
self.user_dict[self.name]['balance'] -= float(balance) # 更新用户余额
print(f"恭喜!用户【{self.name}】取款{balance}元成功!余额{self.user_dict[self.name]['balance']}元。") # 打印取款成功信息
finally:
print("-" * 44) # 打印分隔线
print("程序已结束,感谢使用!") # 打印结束提示信息
def run(self):
self.register() # 调用注册方法
self.login() # 调用登录方法
if __name__ == '__main__':
atm = ATM() # 创建ATM对象
atm.run() # 调用run方法,开始执行ATM程序
------------------开始注册------------------
输入账号: eva
输入密码: 112233
恭喜!用户【eva】注册成功。
------------------开始登录------------------
输入账号: eva
输入密码: 112233
用户【eva】登录成功,进入取钱功能。
------------------开始取款------------------
当前余额为1000元,请输入取款的金额: 293
恭喜!用户【eva】取款293元成功!余额707.0元。
--------------------------------------------
程序已结束,感谢使用!
大家一起烤地瓜
1、地瓜有自己的状态,默认是生的,地瓜可以进行烧烤
2、地瓜有自己烧烤的总时间,由每次烧烤的时间累加得出
3、地瓜烧烤时,需要提供本次烧烤的时间
4、地瓜烧烤时,地瓜状态随着烧烤总时间的变化而改变:
[0, 3) 生的、[3, 6) 半生不熟、[6, 8) 熟了、[8 、糊了
class SweetPotato:
def __init__(self):
self.state = "生的"
self.cook_time = 0
def cakePotato(self, cooking_time):
self.cook_time += cooking_time
if 0 <= self.cook_time < 3:
self.state = "生的"
elif self.cook_time < 6:
self.state = "半生不熟"
elif self.cook_time < 8:
self.state = "熟了"
else:
self.state = "糊了"
def __str__(self):
return f"烤地瓜结束,耗费时间{self.cook_time}小时,地瓜:{self.state}"
sweet_potatos = [SweetPotato() for _ in range(4)]
cooking_times = [2.1, 4.1, 6.8, 12]
for potato, cooking_time in zip(sweet_potatos, cooking_times):
potato.cakePotato(cooking_time)
print(potato)
"""
运行结果
烤地瓜结束,耗费时间2.1小时,地瓜:生的
烤地瓜结束,耗费时间4.1小时,地瓜:半生不熟
烤地瓜结束,耗费时间6.8小时,地瓜:熟了
烤地瓜结束,耗费时间12小时,地瓜:糊了
"""
塑料姐妹花
题目要求:用面向对象编程的方法让两个角色决斗,分出胜负。
小乔(xq)
姓名(name):小乔
生命值(HP):100
攻击力(ATK,attack):25
小满(xm)
姓名(name):小满
生命值(HP):150
攻击力(ATK,attack):15
决斗说明:
决斗采取 回合制,由小乔先发动攻击;
防御时有 20% 几率防御成功,完全闪避攻击,免受伤害。
from random import randint
class Player:
def __init__(self, name, HP, ATK):
self.name = name
self.HP = HP
self.ATK = ATK
print("已成功登记信息")
self.showInfo()
print('--------------------------------')
# 展示信息
def showInfo(self):
print(f"name: {self.name}\thp: {self.HP}\tatk: {self.ATK}")
# 发动攻击
def attack(self, target):
# 这里的target实际上就是实例对象
print(f"【{self.name}】向【{target.name}】发动了攻击。")
target.defend(self.ATK)
# 躲避攻击
def defend(self, damage):
if randint(1, 100) <= 20:
print(f"【{self.name}】成功防御了攻击。")
else:
print(f"【{self.name}】防御攻击失败,受到了【{damage}】点伤害。")
self.HP -= damage
# 创建两个选手对象
xq = Player("小乔", 100, 25)
xm = Player("小满", 150, 15)
while xq.HP >= 0 and xm.HP >= 0:
# 小乔攻击小满
xq.attack(xm)
if xm.HP <= 0:
print(f"{xm.name}死了")
print('--------------------------------')
# 小满攻击小乔
xm.attack(xq)
if xq.HP <= 0:
print(f"{xq.name}死了")
# 打印当前选手信息
xq.showInfo()
xm.showInfo()
print('--------------------------------')
结果太长 加载失败 ^_^
狼人之夜
朋友聚会都要做些什么?一起玩狼人杀是个不错的选择。
按照最经典的角色配置,游戏分为两大阵营,分别是狼人阵营和好人阵营。狼人阵营每晚刀杀一个人;人类阵营里,除了普通人类“村民”之外,还有预言家、女巫和猎人三种特殊角色,每个角色都有特殊技能。
要求
1.定义若干个类,用于表示两大阵营中的五种角色,每名角色都有技能 skill、卡牌 card,并且能够展示自己的卡牌 show_card();
2.九人局中共有 3 狼、3 民、1 预言家、1 女巫、1 猎人,请你用合适的数据结构表示所有角色卡;
3.游戏开始后,你需要从九张角色卡中随机抽取一张,并调用 show_card() 方法查看自己的身份卡。
# card.py的内容
wolf_symbol = ''' 🐺🐺🐺 狼人 🐺🐺🐺
╔══════════════════════════════════════╗
║ __ ___ ║
║ | | / \ | |__ ║
║ |/\| \__/ |___ | ║
║ ║
╚══════════════════════════════════════╝
'''
prophet_symbol = ''' 🔮🔮🔮 预言家 🔮🔮🔮
╔══════════════════════════════════════╗
║ __ __ __ __ ___ ___ ║
║ |__) |__) / \ |__) |__| |__ | ║
║ | | \ \__/ | | | |___ | ║
║ ║
╚══════════════════════════════════════╝
'''
witch_symbol = ''' 🧙🧙🧙 女巫 🧙🧙🧙
╔══════════════════════════════════════╗
║ ___ __ ║
║ | | | | / ` |__| ║
║ |/\| | | \__, | | ║
║ ║
╚══════════════════════════════════════╝
'''
hunter_symbol = ''' 🏹️🏹️🏹️ 猎人 🏹️🏹️🏹️
╔══════════════════════════════════════╗
║ ___ ___ __ ║
║ |__| | | |\ | | |__ |__) ║
║ | | \__/ | \| | |___ | \ ║
║ ║
╚══════════════════════════════════════╝
'''
villager_symbol = ''' 🌾🌾🌾 村民 🌾🌾🌾
╔══════════════════════════════════════╗
║ __ ___ __ ║
║ \ / | | | /\ / _` |__ |__) ║
║ \/ | |___ |___ /~~\ \__> |___ | \ ║
║ ║
╚══════════════════════════════════════╝
'''
# 主程序
import random
from card import *
# 狼人阵营
class Wolf:
skill = "每晚可以和同伴一起杀死一名玩家"
card = wolf_symbol
def show_card(self):
print("你的身份卡是".center(40, '='))
print(self.card)
print(f"你拥有的技能是:{self.skill}")
# 人类阵营
class Human:
skill = "没有特殊技能"
def show_card(self):
print("你的身份卡是".center(40, '='))
print(self.card)
print(f"你拥有的技能是:{self.skill}")
# 预言家是人类阵营
# 每晚可查验任意一名在座玩家的身份
class Prophet(Human):
def __init__(self):
self.card = prophet_symbol
self.skill = "每晚检查任意一名在座玩家的身份"
# 女巫是人类阵营
# 有一瓶毒药、一瓶解药
class Witch(Human):
def __init__(self):
self.card = witch_symbol
self.skill = "有一瓶毒药、一瓶解药"
# 猎人是人类阵营
# 被投票出局或中刀身亡时,可开枪带走任意一名玩家
class Hunter(Human):
def __init__(self):
self.card = hunter_symbol
self.skill = "被投票出局或中刀身亡时,可开枪带走任意一名玩家"
# 村民是人类阵营
# 没有特殊技能
class Villager(Human):
def __init__(self):
self.card = villager_symbol
self.skill = "没有特殊技能"
# 狼人杀九人局标准配置:3 狼、3 村民、1 预言家、1 女巫、1 猎人
players = [Wolf(), Wolf(), Wolf(), Prophet(), Witch(), Hunter(), Villager(), Villager(), Villager()]
# 游戏开始时,你可从所有角色牌中抽取一张,作为自己的身份卡
player = random.choice(players)
# 请调用 show_card() 方法,观察自己的角色卡
player.show_card()
"""
=================你的身份卡是=================
🏹️🏹️🏹️ 猎人 🏹️🏹️🏹️
╔══════════════════════════════════════╗
║ ___ ___ __ ║
║ |__| | | |\ | | |__ |__) ║
║ | | \__/ | \| | |___ | \ ║
║ ║
╚══════════════════════════════════════╝
你拥有的技能是:被投票出局或中刀身亡时,可开枪带走任意一名玩家
"""
本文作者:小满三岁啦
本文链接:https://www.cnblogs.com/ccsvip/p/17944119
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。