一些基础的定义方式、概念啥的不再赘述,我们直接进入稍微复杂一点的地方。
三特性
先从类的三个特性说起:封装、继承、多态。
封装
封装其实字面理解就很好理解,就是把一些东西封存起来,不让外部看到,有点黑匣子的意思。核心目的就是隐藏内部实现细节,只暴露接口。
属性定义一般来说有个公有和私有(双下划线,例如__self_name)的概念,不过python很特殊的一点是没有绝对的私有,实际上可以强制访问(_类名__变量名),这个也叫名称修饰。
class school:
name = "喵喵大学"
__self_name = "哈吉米大学"
a = school()
print(a.name)
print(a._school__self_name)
# 输出
# 喵喵大学
# 哈吉米大学
继承
继承也很好理解,就是代码复用,子类可以继承父类的属性和方法。
不过需要注意的是,如果重写了__init__,需要显式调用 super().__init__(),否则父类初始化逻辑不会执行,而是直接子类覆盖父类。如下代码所示。
class school:
def __init__(self, num):
self.name = "喵喵大学"
self.num = num
def get_name(self):
print(self.name)
def get_num(self):
print(self.num)
a = school(100)
a.get_name()
a.get_num()
class school_new(school):
def __init__(self, num, area):
super().__init__(num) # 调用父类构造,如果没有这句话,school_new定义的实例就不会有num和name属性,后续报错。
self.area = area
def get_area(self):
print(self.area)
b = school_new(10,"bj")
b.get_name()
b.get_num()
b.get_area()
多态
唯一看起来复杂一点的概念。粗暴点理解,就是同样一个类函数方法,但是不同子类调用的结果会不同。其实就是简化了调用的操作。也就是把实现逻辑和调用区分开来。如下代码所示,假如获取学校名称有不同的来源:
class School:
def get_name(self):
raise NotImplementedError
import random
import time
class SchoolA(School):
def get_name(self):
time.sleep(0.5) # 模拟计算
return "喵喵大学(本地算法)"
class SchoolB(School):
def get_name(self):
time.sleep(1) # 模拟网络
return "汪汪大学(远程接口)"
class SchoolC(School):
def get_name(self):
time.sleep(0.2) # 模拟数据库
return "咕咕大学(数据库)"
多态可用的话,调用方完全不操心:
def print_school(school: School):
print("学校信息:", school.get_name())
而如果不用多态,那么调用方就要复杂起来:
def print_school(school_type):
if school_type == "A":
time.sleep(0.5)
print("喵喵大学(本地算法)")
elif school_type == "B":
time.sleep(1)
print("汪汪大学(远程接口)")
elif school_type == "C":
time.sleep(0.2)
print("咕咕大学(数据库)")
例子可能不太好,但是写代码多了总归会遇到,就是我改前面的类,然后后面调用的也要跟着改,代码量上来后就很容易出现牵一发而动全身的感觉,而多态就是解决此类的问题,甭管你父类、子类怎么折腾,我调用方式不用变。
属性
我们定义一个类常见的属性有类属性、实例属性,实例属性又有局部和全局啥的区别。本来写一下,感觉又没啥好写的,大概就是作用域、修改后影响一类的,可以自行写代码试试看一下就明白了。
类方法、实例方法、静态方法
类方法用于在没有实例时操作类本身,典型是工厂与构造替代;静态方法用于逻辑上属于类、但不依赖类或实例的工具逻辑;实例方法用于依赖对象状态的行为。
实例方法应该是最常见也是最常用的:
class User:
def __init__(self, username):
self.username = username
# 这是一个实例方法
def upgrade_username(self, new_name):
self.username = new_name # 它操作的是“这个”用户的名字
print(f"名字已改为:{self.username}")
# 使用:
user_a = User("张三")
user_a.upgrade_username("张三三") # 必须先有实例,才能改名
类方法:
class User:
def __init__(self, username, age):
self.username = username
self.age = age
@classmethod
def from_json(cls, data):
# 假设 data 是 {"name": "李四", "age": 20}
# cls(name, age) 相当于调用 User(name, age)
return cls(data['name'], data['age'])
# 使用:
json_data = {"name": "李四", "age": 20}
# 不需要先创建 user 对象,直接用类来调用
user_b = User.from_json(json_data)
其实初看有点脱裤子放屁的感觉,就是我已经有实例方法了,为什么还要搞个类方法?实际上二者应用范围有很大差异。类函数实际上是在说明,怎么样构造一个实例。而实例函数更多的是我已经有了一个实例,可以怎么用。上述我们类函数通过from_json构造了一个实例,如果后续我们有更多的数据类型,比如数据库、表单一类的,我们可以写User.from_db(…)、User.from_form(…),而不是局限于一个固定的实例创建方式。此外,从继承角度来看,类函数可以不用改代码,便捷的被继承。
静态方法:
class User:
@staticmethod
def check_age(age):
return age >= 18
User.check_age(20) # True
User.check_age(10) # False
这个其实更好理解,就是一个不需要类、实例的方法,但是呢,从逻辑上来讲又和这个类有关系。当然也可以写出去,单独创建一个函数,个人觉得这个单纯看个人喜好,可能在中大型项目上归到一个类里比较好阅读理解。