Python类对象、实例对象、类属性、实例属性、类方法、实例方法、静态方法、属性方法的区别


此文章为个人学习整理笔记,由于视频可能随时失效,所以特别整理。原视频如下

1. 视频讲解

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. 内存分配情况

类对象Province
country = '中国'
def __init__(self, name)
def ord_func(self)
def class_func(cls)
def static_func()
实例对象obj1= Province('山东省')
name='山东省'
默认的属性等(指该对象新定义属性)
__class__(指向类对象)
实例对象obj2= Province('山西省')
name='山西省'
默认的属性等(指该对象新定义属性)
__class__(指向类对象)

  创建类对象时,内存中划分一处区域存储该类对象,存储了类属性和所有方法
  创建实例对象时,每创建一个实例对象都会划分一处区域存储实例对象,每个区域存储对应的实例属性(【类对象中的__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__函数进行初始化

  1. 当执行到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。
  2. 当执行到self.count()时,a调用Spam1的类方法count(),方法中cls.numInstances即为 Spam1.numInstances,Spam1.numInstances = 0。 此时 self.numInstances为1,Spam1.numInstances为1。

b=Spam1() : 创建Spam1类的实例b,进入__init__函数进行初始化

  1. 当执行到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。
  2. 当执行到self.count()时,b调用Spam1的类方法count(),方法中cls.numInstances即为 Spam1.numInstances,因此执行完cls.numInstances += 1后, 此时 self.numInstances为2,Spam1.numInstances为2。

c=Spam1() : 创建Spam1类的实例c,进入__init__函数进行初始化

  1. 当执行到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。
  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__函数进行初始化

  1. 当执行到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。
  2. 当执行到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__函数进行初始化

  1. 当执行到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。
  2. 当执行到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__函数初始化

  1. 当执行到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。
  2. 当执行到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 !


参考:
Python同时使用@property和classmethod
Python类调用类的实例方法的方式

  • 9
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值