此文章为个人学习整理笔记,由于视频可能随时失效,所以特别整理。原视频如下
1. 视频讲解
- 原视频链接:https://www.bilibili.com/video/BV134411t7kF?spm_id_from=333.999.0.0&vd_source=3d36a677d560d1845c924fe0a781a44f
- 讲师B站个人空间:https://space.bilibili.com/457643342
- 视频笔记链接:https://doc.itprojects.cn/0001.zhishi/python.0003.python3hexinbiancheng/index.html#/03.02.jingtaiffleiff
类对象、实例对象、类方法、实例方法、类属性、实例属性、静态方法
2. 定义
示例代码:
"""创建类对象"""
class Province(object):
# 类属性
country = '中国'
def __init__(self, name):
# 实例属性
self.name = name
def ord_func(self):
# 定义实例方法,至少有一个self参数
print('实例方法')
@classmethod
def class_func(cls):
# 定义类方法,至少有一个cls参数
print('类方法')
@staticmethod
def static_func():
# 定义静态方法 ,无默认参数如self、cls
print('静态方法')
@property
def pro_name(self):
# 定义属性方法
return self.name
if __name__ == '__main__':
"""创建实例对象"""
obj = Province('山东省')
"""调用类属性"""
# 使用类名直接调用类属性
print(Province.country)
# 使用实例对象调用类属性
print(obj.__class__.country)
"""调用实例属性"""
# 使用实例对象调用实例属性
print(obj.name)
"""调用类方法"""
# 使用类名直接调用类方法
Province.class_func()
# 使用实例对象调用类方法
obj.class_func()
"""调用实例方法"""
# 使用实例对象调用实例方法
obj.ord_func()
"""调用静态方法"""
# 使用类名直接调用静态方法
Province.static_func()
# 使用实例对象调用静态方法
obj.static_func()
"""调用属性方法"""
obj.pro_name
|
|
| |
---|---|---|---|
类对象 | 1.类对象对具有相似属性和方法的对象进行总结抽象。 2.不同的实例对象去引用类对象的属性和方法,能减少代码的重复率。 | 如上例声明的 Province类 即为一个类对象。 |
|
实例对象 | 又称实例化对象,不是抽象而是一类对象中具体的一例对象。 | 如:obj = Province(‘山东省’) obj 即为上述Province类实例化的一个对象。 |
|
类属性 | 类属性就是类对象所拥有的属性,被该类的所有实例对象所共有。 | 如上例声明的 country变量 即为Province类的类属性。 | ● 类外部:类对象、实例对象 被调用方式:类对象.类属性 / 实例对象._class_.类属性(可访问可修改,不建议此方式) 或 实例对象.类属性(仅访问) “实例对象.类属性=xx”是新定义一个和类属性同名的实例变量 ● 类内部:所有类方法、实例方法 被调用方式:cls.类属性 / self._class_.类属性 或 self.类属性 |
实例属性 | 1. 与类的实例相关联 的数据值,是这个实例私有的,只有这个对象自己可以访问。 2.当一个实例被释放后,它的属性同时也被清除了。 | 如:self.name = name self指代实例对象,self.xx 标识的变量一般都为实例属性,上述Province类self.name中的name即为实例属性 | ● 类外部:实例对象 被调用方式:实例对象.实例属性 ● 类内部:实例方法 被调用方式:self.实例属性 |
类方法 | 1.由类调用,也可以使用实例对象来调用(不推荐)。 2.至少一个cls参数,执行类方法时,自动将调用该方法的类赋值给cls。 3.使用@classmethod修饰符进行修饰 | 如上例 @classmethod修饰的class_func(cls) 方法。 | ● 类外部:类对象、实例对象 被调用方式:类对象.类方法 / 实例对象.类方法 ● 类内部:其他类方法 被调用方式:cls.类方法 |
实例方法 | 1.由实例对象调用。 2.至少一个self参数,执行实例方法时,自动将调用该方法的对象赋值给self。 | 如上例 ord_func(self) 方法。 | ● 类外部:实例对象 被调用方式:实例对象.实例方法 ● 类内部:实例方法 被调用方式:self.实例方法 |
静态方法 | 1.由类调用,也可以使用实例对象调用。 2.无没有类似 self、cls的默认参数,一般用于和类对象以及实例对象无关的代码。 3. 类的静态方法中无法调用类属性和类方法,实例属性和实例方法 。 可理解为,静态方法可以脱离类成为一个普通方法。 | 如上例 @staticmethod修饰的static_func() 方法。 | ● 类外部:类对象、实例对象 被调用方式:类对象.静态方法/ 实例对象.静态方法 ● 类内部:类方法、实例方法 被调用方式:cls.静态方法 / self.静态方法 |
属性方法 | 1.@property装饰器将方法变为属性方法。 2.属性方法property就是把一个方法变成一个静态的属性(变量)。 3.属性方法可以正常的访问类内的方法和属性,只是调用方式有了变化。 4.由于属性方法将方法转换成一个属性,它的调用方式是属性的调用方式,因此属性方法的参数只可传入“cls或self或无参数”,在调用@property修饰的属性方法时无法赋值。 5.通常@property 和 @.setter 会搭配使用,使得可以对被@.setter 修饰的属性方法像普通属性一样实现赋值操作。 | 如上例 @property修饰的pro_name(self) 方法。 一般调用方法后面要加(),调用属性没有()。属性方法作为一个方法调用时无需加()。 如上例调用:obj.pro_name | 根据传入的参数情况确定调用范围。 如上例pro_name(self)传入的是实例对象,与实例方法的被调用范围一致。若传入为类对象,则与类方法的被调用范围一致,若无参数,与静态方法的被调用范围一致。 |
3. 应用场景
3.1 静态方法
3.1.1 规避两个模块调用同名函数的情况
场景:为Car类和Animal类均定义一个tips 函数,分别为该类独用。代码如下,
class Car(object):
pass
def tips():
print("温馨提示:开车不喝酒")
class Animal(object):
pass
def tips():
print("温馨提示:不要伸手投食")
3.1.2 提供创建实例对象前待输入参数的合理性测试功能
场景:模拟pizza店提供输入pizza半径,输出pizza面积的测试功能,以此让顾客选择最满足自己需求大小的pizza。代码如下,
import math
class Pizza(object):
def __init__(self, ingredients, radius):
self.ingredients = ingredients # pizza名称
self.radius = radius # pizza半径
def show_size(self):
# 选购pizza大小
pizza_size = self.cal_area(self.radius)
return pizza_size
@staticmethod
def cal_area(r):
return int(math.pi * r * r)
if __name__ == '__main__':
# 顾客测试半径为20厘米的pizza有多大
print(Pizza.cal_area(20))
# 顾客发现半径为20厘米的pizza有点大,决定订购一个小一点的
# 创建pizza实例对象
my_pizza = Pizza('番茄牛肉pizza', 15)
pizza_size = my_pizza.show_size()
# pizza大小合适,满意
print(pizza_size)
Pizza类中的静态方法 cal_area,提供了pizza大小计算的测试功能。另外观察到,show_size 实例方法虽然也是计算pizza大小的功能,直接调用了静态方法 cal_area。这样的好处在于,如若日后pizza大小的逻辑有变化,只需要修改静态方法 cal_area,有利于后期代码维护。
3.2 类方法
3.2.1 提供创建实例对象时输入参数规范化处理
场景:输入格式如(年,月,日)的时间,输出“year : 2016,month : 8,day : 1”。代码如下,
class Data_test(object):
def __init__(self,year=0,month=0,day=0):
self.day=day
self.month=month
self.year=year
def out_date(self):
print("year :",self.year,",month :",self.month,",day :",self.day)
t=Data_test(2016,8,1)
t.out_date()
但是用户也有可能输入的是 “2016-8-1” 这样的字符格式,针对这种场景,需要提供一个方法对用户输入的时间字符串进行规范化处理。可以定义一个类方法,先对用户的输入进行清洗规范,再进行实例化。代码如下,
class Data_test2(object):
def __init__(self,year=0,month=0,day=0):
self.day=day
self.month=month
self.year=year
@classmethod
def get_date(cls, string_date):
year,month,day=map(int,string_date.split('-'))
date1=cls(year,month,day)
return date1
def out_date(self):
print("year :",self.year,",month :",self.month,",day :",self.day)
r=Data_test2.get_date("2016-8-1")
r.out_date()
3.2.2 已创建实例对象计数器功能
ps:除了描述类方法的计数器作用,附加介绍科学严谨设计计数器的思路和探索过程。
场景:有一个学生录入系统,每录入系统一个学生,计数器stu_num加1。
设计:创建一个学生对象,每一位学生是学生对象的实例,stu_num设置为类变量,如下所示:
class Student:
stu_num = 0
def __init__(self,name):
self.name = name # 实例变量
Student.stu_num = Student.stu_num + 1
print("添加了学生:{},已经有:{}个学生".format(self.name,self.stu_num))
s1 = Student("mrn")
s2 = Student("lyq")
s3 = Student("ww")
print(Student.stu_num)
Student.stu_num += 5
print(Student.stu_num)
输出结果如下所示:
添加了学生:mrn,已经有:1个学生
添加了学生:lyq,已经有:2个学生
添加了学生:ww,已经有:3个学生
3
8
思考:虽然实现了所需要的功能,通过创建实例对象使得类属性stu_num累加,但是还有不足之处:在无须创建实例的情况下,也可以通过类对象直接修改类属性stu_num,这是不合理的!
解决方案:将类变量stu_num变为一个私有变量__stu_num,创建一个类方法去改变它。如下所示:
class Student:
__stu_num = 0
def __init__(self,name):
self.name = name # 实例变量
print("添加了学生:{}".format(self.name))
self.add_stu()
@classmethod
def add_stu(cls):
cls.__stu_num = cls.__stu_num + 1
print("现有{}个学生".format(cls.__stu_num))
s1 = Student("mrn")
s2 = Student("lyq")
s3 = Student("ww")
print(s1._Student__stu_num)
Student.add_stu()
print(s1._Student__stu_num)
运行结果如下所示:
添加了学生:mrn
现有1个学生
添加了学生:lyq
现有2个学生
添加了学生:ww
现有3个学生
3
现有4个学生
4
思考:观察结果,此次优化并没有解决问题,仅仅是将类属性进行匿名了而已!问题变成了 在无须创建实例的情况下,也可以通过类方法间接修改私有类属性stu_num。
解决方案:既然此类方法在无须创建实例对象的情况下,调用时也能成功更改类属性stu_num,只要在类方法中限制只允许创建了实例对象才可以对stu_num进行+1的操作——类方法中增加一个参数,传入实例对象 。如下所示:
class Student:
__stu_num = 0
def __init__(self,name):
self.name = name # 实例变量
print("添加了学生:{}".format(self.name))
self.add_stu(self)
@classmethod
def add_stu(cls,obj):
if obj.name:
cls.__stu_num = cls.__stu_num + 1
print("现有{}个学生".format(cls.__stu_num))
s1 = Student("mrn")
s2 = Student("lyq")
s3 = Student("ww")
print(s1._Student__stu_num)
Student.add_stu()
print(s1._Student__stu_num)
运行结果如下所示:
添加了学生:mrn
现有1个学生
添加了学生:lyq
现有2个学生
添加了学生:ww
现有3个学生
3
Traceback (most recent call last):
File "D:\test.py", line 19, in <module>
Student.add_stu()
TypeError: add_stu() missing 1 required positional argument: 'obj'
经过修改,对实例化与否进行判断,如果没有实例化就调用的话,报错。保证了计数器仅可对实例对象进行计数。
3.3 属性方法
3.3.1 限制值
场景:创建一个 Student 类,通过实例来获取每个学生的一些情况,包括名字,成绩(满分100分)等。成绩只有等到考试结束以后才会有,所以实例化的时候不给它赋值。
class Student:
def __init__(self, name):
self.name = name
self.score = None
mike = Student('mike')
考试结束后,mike得了99分,但给mike打分时多打了一个“9”
mike.score = 999
999 是一个非法数据,不应该赋值成功。为了避免这种现象,把 score 变成私有属性,并定义一个set_score方法,限制 score 的值必须在 0-100 分。规则:如果输入的不是 0-100 的整数,就让程序报错,数据合法,score 属性修改成功。
class Student:
def __init__(self, name):
self.name = name
self.__score = None
def set_score(self, new_score):
if not isinstance(new_score, int):
raise ValueError('score must be int')
if 0 <= new_score <= 100:
self.score = new_score
return self.score
else:
raise ValueError('score invalid')
mike = Student('mike')
mike.set_score(999)
程序报错!
raise ValueError('score invalid')
ValueError: score invalid
使用 @property 的方式代替
class Student:
def __init__(self, name):
self.name = name
self.__score = None
@property
def score(self):
return self.__score
@score.setter
def score(self, new_score):
if not isinstance(new_score, int):
raise ValueError('score must be int')
if 0 <= new_score <= 100:
self.score = new_score
return self.score
else:
raise ValueError('score invalid')
mike = Student('mike')
# 获取属性
a = mike.score
# 设置属性
mike.score = 99
3.3.2 动态属性
在上面Student类基础上,添加 birth 属性和年龄属性:
from datetime import datetime
class Student:
def __init__(self, name, birth=1920):
self.name = name
self.__score = None
self.birth = birth
self.age = datetime.now().year - self.birth
mike = Student('mike')
print(mike.birth)
print(mike.age)
上述设计的不足:
- 存在数据冗余:birth 和 age 这两个是可以根据一个求出另外一个。
- mike 初始化时,age 属性已经被求出,以后都将是以固定值形式呈现,而age 属性只读属性应该是一个动态属性,即每次访问时都应该根据访问时间动态变化。
改进如下:
from datetime import datetime
class Student:
def __init__(self, name, birth=1920):
self.name = name
self.__score = None
self.birth = birth
@property
def age(self):
return datetime.now().year - self.birth
mike = Student('mike')
print(mike.birth)
print(mike.age)
4. 内存分配情况
创建类对象时,内存中划分一处区域存储该类对象,存储了类属性和所有方法。
创建实例对象时,每创建一个实例对象都会划分一处区域存储实例对象,每个区域存储对应的实例属性(【类对象中的__init__方法所有的self.*】和【该实例对象动态新定义并赋值的属性,如:类对象.属性 = 值】)以及一个__class__属性(__class__指向该实例对象对应的类对象,实例对象可以通过它调用类属性)
5. 一些思考
5.1 实例方法的两种调用方式
通常情况下,我们习惯使用实例对象调用实例方法。如下所示:
class CLanguage:
def info(self):
print("我正在学 Python")
clang = CLanguage()
clang.info()
但如果想用类调用实例方法,则需要像如下所示,通过手动将 clang 这个实例对象传给 self 参数:
class CLanguage:
def info(self):
print("我正在学 Python")
clang = CLanguage()
CLanguage.info(clang)
- 用类的实例对象访问类成员的方式称为绑定方法,即 self 在定义时需要定义,但是在调用时会自动传入
- 用类名调用类成员的方式称为非绑定方法,即允许使用类名直接调用实例方法,但必须手动为该方法的第一个 self 参数传递参数。
注:非绑定方法中,self 并没有规定必须传一个该类的对象,其实完全可以任意传入一个参数,但应该避免胡乱给 self 参数传参(可能会导致程序运行崩溃)。例如:
class CLanguage:
def info(self):
print(self,"正在学 Python")
CLanguage.info("zhangsan")
运行结果为:zhangsan 正在学 Python
思考:为什么有了绑定方法还有非绑定方法的存在?非绑定方法的应用场景如下:子类的类方法通过自身调用父类的实例方法。
class Person():
def get_random_str(self):
return "abcd"
class Student(Person):
@classmethod
def print_str(cls):
ran_str = cls.get_random_str(Person())
print(ran_str)
Student.print_str()
注意观察上例代码,Student类(继承Person类)在类方法print_str中调用了Person类的实例方法get_random_str,cls自动绑定到Student。因此是Student类调用Person类的实例方法get_random_str,传入的实例参数为Person类,这和以下代码的原理不同。
class Person():
def get_random_str(self):
return "abcd"
class Student(Person):
@classmethod
def print_str(cls):
ran_str = Person.get_random_str(Person())
print(ran_str)
Student.print_str()
此代码中将cls改为Person,是Person类调用实例方法,与Student类无关。本质上与ran_str = Person().get_random_str()
没有什么不同。另外,这样的Student类方法没有意义。
5.2 分别通过cls、self、类名本身调用类属性的关系和区别?
观察下例:
- 父类:Spam1,有一个类属性 numInstances
- 子类:Sub1,也有一个类属性 numInstances
- 子类:Other1,空类
class Spam1:
numInstances = 0
@classmethod
def count(cls):
cls.numInstances += 1
print("In count -> number of instances: cls, Spam", cls.numInstances, Spam1.numInstances)
def __init__(self):
print("-----")
print("In init, before -> number of instances: self, Spam",self.numInstances,Spam1.numInstances )
self.count()
print("In init, after -> number of instances: self, Spam",self.numInstances,Spam1.numInstances )
print("-----")
class Sub1(Spam1):
numInstances = 0
class Other1(Spam1):
pass
a=Spam1()
b=Spam1()
c=Spam1()
d=Sub1()
e=Sub1()
f=Other1()
输出结果如下所示:
a=Spam1()
-----
In init, before -> number of instances: self, Spam 0 0
In count -> number of instances: cls, Spam 1 1
In init, after -> number of instances: self, Spam 1 1
-----
b=Spam1()
-----
In init, before -> number of instances: self, Spam 1 1
In count -> number of instances: cls, Spam 2 2
In init, after -> number of instances: self, Spam 2 2
-----
c=Spam1()
-----
In init, before -> number of instances: self, Spam 2 2
In count -> number of instances: cls, Spam 3 3
In init, after -> number of instances: self, Spam 3 3
-----
d=Sub1()
-----
In init, before -> number of instances: self, Spam 0 3
In count -> number of instances: cls, Spam 1 3
In init, after -> number of instances: self, Spam 1 3
-----
e=Sub1()
-----
In init, before -> number of instances: self, Spam 1 3
In count -> number of instances: cls, Spam 2 3
In init, after -> number of instances: self, Spam 2 3
-----
f=Other1()
-----
In init, before -> number of instances: self, Spam 3 3
In count -> number of instances: cls, Spam 4 3
In init, after -> number of instances: self, Spam 4 3
-----
运行过程:
a=Spam1() : 创建Spam1类的实例a,进入__init__函数进行初始化
- 当执行到
print("In init, before -> number of instances: self, Spam",self.numInstances,Spam1.numInstances)
时,self即为实例a。self.numInstances检查实例属性,但是因为没有任何东西赋值给 self.numInstances,所以没有实例属性可读,对 self.numInstances的访问最终读取了Spam1类的numInstances,Spam1.numInstances = 0。此时 self.numInstances为0,Spam1.numInstances为0。 - 当执行到
self.count()
时,a调用Spam1的类方法count(),方法中cls.numInstances
即为 Spam1.numInstances,Spam1.numInstances = 0。 此时 self.numInstances为1,Spam1.numInstances为1。
b=Spam1() : 创建Spam1类的实例b,进入__init__函数进行初始化
- 当执行到
print("In init, before -> number of instances: self, Spam",self.numInstances,Spam1.numInstances)
时,self即为实例b。self.numInstances检查实例属性,但是因为没有任何东西赋值给 self.numInstances,所以没有实例属性可读,对 self.numInstances的访问最终读取了Spam1类的numInstances,此时Spam1.numInstances = 1。此时 self.numInstances为1,Spam1.numInstances为1。 - 当执行到
self.count()
时,b调用Spam1的类方法count(),方法中cls.numInstances
即为 Spam1.numInstances,因此执行完cls.numInstances += 1
后, 此时 self.numInstances为2,Spam1.numInstances为2。
c=Spam1() : 创建Spam1类的实例c,进入__init__函数进行初始化
- 当执行到
print("In init, before -> number of instances: self, Spam",self.numInstances,Spam1.numInstances)
时,self即为实例c。self.numInstances检查实例属性,但是因为没有任何东西赋值给 self.numInstances,所以没有实例属性可读,对 self.numInstances的访问最终读取了Spam1类的numInstances,此时Spam1.numInstances = 2。此时 self.numInstances为2,Spam1.numInstances为2。 - 当执行到
self.count()
时,c调用Spam1的类方法count(),方法中cls.numInstances
即为 Spam1.numInstances,因此执行完cls.numInstances += 1
后, 此时 self.numInstances为3,Spam1.numInstances为3。
d=Sub1() : 创建Sub1类的实例d,由于Sub1类无自己的__init__函数,继承父类Spam1类的__init__函数进行初始化
- 当执行到
print("In init, before -> number of instances: self, Spam",self.numInstances,Spam1.numInstances)
时,self即为实例d。self.numInstances检查实例属性,但是因为没有任何东西赋值给 self.numInstances,所以没有实例属性可读,对 self.numInstances的访问最终读取了Sub1类的numInstances,此时 Sub1.numInstances = 0。因此 self.numInstances为0,Spam1.numInstances仍然为3。 - 当执行到
self.count()
时,d调用方法count(),Sub1类中不存在类方法count(),因此继承父类Spam1的类方法count()。注意:count(cls)中 cls 指代Sub1类对象,因此cls.numInstances
为 Sub1.numInstances,因此执行完cls.numInstances += 1
后, 此时 self.numInstances为1,Spam1.numInstances仍然为3。
e=Sub1() : 创建Sub1类的实例e,由于Sub1类无自己的__init__函数,继承父类Spam1类的__init__函数进行初始化
- 当执行到
print("In init, before -> number of instances: self, Spam",self.numInstances,Spam1.numInstances)
时,self即为实例e。self.numInstances检查实例属性,但是因为没有任何东西赋值给 self.numInstances,所以没有实例属性可读,对 self.numInstances的访问最终读取了Sub1类的numInstances,此时 Sub1.numInstances = 1。因此 self.numInstances为1,Spam1.numInstances仍然为3。 - 当执行到
self.count()
时,e调用方法count(),Sub1类中不存在类方法count(),因此继承父类Spam1的类方法count()。注意:count(cls)中 cls 指代Sub1类对象,因此cls.numInstances
为 Sub1.numInstances,因此执行完cls.numInstances += 1
后, 此时 self.numInstances为2,Spam1.numInstances仍然为3。
f=Other1(): 创建Other1类的实例f,由于Other1类只有类定义的结构无其他属性和方法,因而继承了父类Spam1类属性和方法,首先进入__init__函数初始化
- 当执行到
print("In init, before -> number of instances: self, Spam",self.numInstances,Spam1.numInstances)
时,self即为实例f。self.numInstances检查实例属性,但是因为没有任何东西赋值给 self.numInstances,所以没有实例属性可读,对 self.numInstances的访问最终变成了试图访问类属性Other1.numInstances,但是又因为没有任何东西赋值给 Other1.numInstances,所以没有类属性可读,进而继续向上访问,最终读取了父类Spam1类的numInstances。此时 Spam1.numInstances = 3。因此 self.numInstances为3,Spam1.numInstances为3。 - 当执行到
self.count()
时,f调用方法count(),Other1类中不存在类方法count(),因此继承父类Spam1的类方法count()。注意:count(cls)中 cls 指代Other1类对象,当cls.numInstances += 1
被执行时,f查找numInstances,根据上面提到的,最终找到Spam1.numInstances,此时执行cls.numInstances = Spam1.numInstances +1
,由于此处 cls 指代Other1类对象,并且有赋值操作,因此动态新产生了Other1类的类对象numInstances,使得self.numInstances可访问Other1.numInstances。因此此时 self.numInstances为4,Spam1.numInstances仍然为3。
5.3 可否同时使用@property和@classmethod?
对上述“已创建实例对象计数器功能”的例子,还有一种优化的方法:将初始代码的类变量stu_num变为一个私有变量__stu_num,也能达到仅允许创建实例的情况下,才可以递增类属性__stu_num的目的,代码如下:
class Student:
__stu_num = 0
def __init__(self,name):
self.name = name # 实例变量
Student.__stu_num = Student.__stu_num + 1
print("添加了学生:{},已经有:{}个学生".format(self.name,self.__stu_num))
s1 = Student("mrn")
s2 = Student("lyq")
s3 = Student("ww")
输出结果如下所示:
添加了学生:mrn,已经有:1个学生
添加了学生:lyq,已经有:2个学生
添加了学生:ww,已经有:3个学生
但是我们仍然希望通过访问类属性来查看共创建了多少个类对象时,可此时的类属性已经是私有属性无法直接访问,可否通过同时使用@property和classmethod实现此需求,探索代码如下:
class Student:
__stu_num = 0
@classmethod
@property
def stu_num(cls):
return cls.__stu_num
def __init__(self,name):
self.name = name # 实例变量
Student.__stu_num = Student.__stu_num + 1
print("添加了学生:{},已经有:{}个学生".format(self.name,self.__stu_num))
s1 = Student("mrn")
s2 = Student("lyq")
s3 = Student("ww")
print(s1.stu_num)
输出结果如下所示:
添加了学生:mrn,已经有:1个学生
添加了学生:lyq,已经有:2个学生
添加了学生:ww,已经有:3个学生
3
上述代码有两个注意点:Ⅰ.适用的 Python版本为:3.8 < Python < 3.11。Ⅱ.@classmethod必须写在@property之前。
当Python < 3.9时,需要通过定义元类及定义被此元类定义的类来达到classmethod和property共同作用的目的。(由于属性方法虽然在类中定义,但是在实例中才发挥作用;另外,被元类定义的类也是元类的一个实例。因此,可在元类中定义property方法。) 观察下列代码:
class Stu(type):
__stu_num = 0
@property
def stu_num(cls):
return cls.__stu_num
@stu_num.setter
def stu_num(cls, value):
cls.__stu_num = value
class Student(metaclass=Stu):
def __init__(self,name):
self.name = name # 实例变量
Student.stu_num = Student.stu_num + 1
print("添加了学生:{},已经有:{}个学生".format(self.name,Student.stu_num))
s1 = Student("mrn")
s2 = Student("lyq")
s3 = Student("ww")
print(Student.stu_num)
输出结果如下所示:
添加了学生:mrn,已经有:1个学生
添加了学生:lyq,已经有:2个学生
添加了学生:ww,已经有:3个学生
3
上述代码有一个注意点:Stu类和Student类并非父子类( 超类)关系,而是元类与被元类定义的类的关系,metaclass 参数指定元类,因此必须用Student类对象调用或赋值 stu_num !