关于python的类成员、实例成员、静态成员的对比与联系

六眼飞鱼酱① 提交于 2020-02-20 02:15:39

preface:本文是https://blog.csdn.net/qq_31780525/article/details/72639491一文的补充。


Python中的静态成员、非静态成员,与C#的静态成员、非静态成员有着一种千丝万缕的区别,此文专门针对这种区别,进行详细讨论。

推荐大家先看代码段,再看结论。边看代码段边思考。


首先,我们不妨将这种不带get、set方法的类成员属性称作为“字段”(C++,C#的说法)。此时,有如下代码段:

 

 代码段_1

class people:
    name="Skar"    #这个“字段”,既是类的,也是实例的,本文给它起个名字,叫“调皮字段”
    age=20         #同样,这个“字段”,既是类的,也是实例的
 
p=people()         #p是people的一个实例
print(p.name)      #打印结果:"skar"。所以,p的名字叫“skar”,符合认知
print(people.name) #打印结果:"skar"。所以,people的名字叫“skar”,但不符合认知

print(p.age)       #打印结果:20。所以,p的年龄为20,符合认知
print(people.age)  #打印结果:20。所以,people的年龄为20,但不符合认知

 

所以,在代码段_1中,并没有真正的“类字段”,也就是C#中的静态字段。同样的,也没有真正的“实例字段”,也就是C#中的“非静态字段”。

但是代码段_1真实存在一个“字段”,它既是类的,也是实例的。本文给它起个名字,不妨叫“调皮字段”。

此时,我们在代码段_1中,加入一些真正的“实例字段”,如下:


代码段_2

class people:
    name="skar"
 
p=people()
p.age=20           #这个age是名正言顺的实例字段,类不能直接调用它。如下:
  
print(people.age)  #打印失败,实例字段不能通过类对象调用
print(people.name) #打印成功:"skar",原因见 代码段_1

print(p.age)       #打印成功:20,实例字段被实例成功调用
print(p.name)      #打印成功:"skar",原因见 代码段_1

 “实例字段”还可以通过构造函数的方式添加,如下:

代码段_3

class people:
    name="skar"
    def  __init__(self,age):
        self.age=age   #这个age也是名正言顺的实例字段,类不能调用它
 
p=people(20)
 
print(p.name)      #打印成功:"skar",原因见代码段_1
print(p.age)       #打印成功:20,实例属性被实例成功调用,天经地义
 
print(people.name) #打印成功:"skar",原因见代码_1
print(people.age)  #打印失败:实例字段不能被类对象调用

如果把代码段_1的调皮字段加入到构造函数中,会有什么结果呢?来看代码段_4:

代码段_4

class people:
    name="skar"
    def  __init__(self,age,name):
        self.age=age   #这个age也是名正言顺的实例字段,类不能调用它
        self.name = name
p=people(20,"Wang")
 
print(p.name)      #打印成功:"Wang",通过实例调用“调皮字段”,构造函数外面的“调皮字段”被构造函数里面的“调皮字段”覆盖
print(p.age)       #打印成功:20,原因见代码段_3
 
print(people.name) #打印成功:"skar",通过类调用“调皮字段”,原来的“调皮字段”重新出现
print(people.age)  #打印失败,原因见代码段_3

或者这样直接弄,更简单:

代码段_5

class people:
    name="skar"
 
p=people()
p.name="Wang"

print(people.name) #打印成功:"skar"
print(p.name)      #打印成功:"Wang"

由以上的种种实验现象可见,“调皮字段”本身是由两部分字段合二为一的。一个专门由实例调用,另一个专门由类调用,这两个字段的初始值相同(在本文为skar)。如果有实例想主动调用这个“调皮字段”,或者说,我们创建了一个实例字段,但恰巧这个实例字段与“调皮字段”的名字相同,那么调皮字段会自动分配给实例自己本应该属于实例字段的那一部分,留下了专门由类调用的调皮字段。

如果,我们把代码段_4的第4行的self改为people,会出现以下神奇的结果:

代码段_6

class people:
    name="skar"
    def  __init__(self,age,name):
        self.age=age   #这个age也是名正言顺的实例字段,类不能调用它
        people.name = name
p=people(20,"Wang")
 
print(p.name)      #打印成功:"Wang"。原来的“调皮字段”重新出现,即使实例调用“调皮字段”,也无效。
print(p.age)       #打印成功:20,原因见代码段_3
 
print(people.name) #打印成功:"wang"。原来的“调皮字段”重新出现
print(people.age)  #打印失败,原因见代码段_3

或者这样直接改:

代码段_7

class people:
    name="skar"
 
p=people()
people.name="Wang"

print(people.name) #打印成功:"Wang"
print(p.name)      #打印成功:"Wang"

由此可见,如果调用“调皮字段”的是类,并赋新值,那么调皮字段的两部分会全部分配给类(连实例本该调用的那部分也变成了类赋给的值)。

如果我们想“抢回来”:

代码段_8

class people:
    name="skar"
 
p=people()
people.name="Wang"
p.name = "Wang"

print(people.name) #打印成功:"Wang",很遗憾,没从类手里抢回来(如果是"skar",就是抢回来了)
print(p.name)      #打印成功:"Wang"

普通方法:没有任何修饰器、self参数、cls参数

class people:

    def setAndGetCountry():
       print("I'm a Chinese")

p=people()

#p.setAndGetCountry()#打印失败,实例不可调用普通方法
people.setAndGetCountry()#打印成功:I'm a Chinese。类可以调用普通方法。

实例不可调用普通方法! 

class people:
    country="china"

    def setAndGetCountry(country):
        people.country = country
        return people.country

p=people()
#print(p.setAndGetCountry('Huaxia')) #打印失败。实例不能调用普通方法
print(people.setAndGetCountry('Huaxia')) #打印成功:china。类可以调用普通方法,返回的是people.country

普通方法也可以对实例进行设置,但是由于没有了self参数,就不能对任意实例进行设置了,只能对特定的实例进行设置,如下:

class people:
    country="china"

    def setAndGetCountry(country):
        p.counrty = country#用具体的p替代了self
        return p.country

p=people()

print(people.setAndGetCountry('Huaxia')) #打印成功:china,返回的是people.country

普通方法是名副其实的静态方法!

那么,我们现在看所谓的“类方法”:

代码段_9

class people:
    country="china"
 
    @classmethod 
    #类可以调用类方法,实例也可以调用类方法
    def getCountry(cls):  #这个类方还相当于一个只读属性,可读不可写
        return cls.country
    
p=people()
print(people.getCountry())  #打印成功:"china",实例对象调用类方法
print(p.getCountry())   #打印成功:"china",实例对象调用类方法

这里的类方法我们用了声明属性的格式(这里是只读属性,可读取,不可修改),毕竟属性是一种特殊类型的成员方法。

由此可见,类方法既是类的,还是实例的。有点儿名不副实,比较坑爹。

但是,类方法既然叫类方法,肯定是有原因的,不然Python的发明者会被人喷死。

class people:
    country="china"#调皮字段
 
    @classmethod
    def getCountry(cls):
        return cls.country

p=people()
p.country = 'Huaxia' #调皮字段的实例字段被这条语句覆盖

print(p.getCountry())   #打印成功:china,返回的是people.country
print(people.getCountry())  #打印成功:china,返回的是people.country

#p和people都可以调用类方法getCountry()。无论二者谁调用,效果一样。返回的都是people.country

print(p.country)#打印成功:Huaxia,调皮字段的实例字段被p.country = 'Huaxia'语句覆盖
print(people.country)#打印成功:"china",这个和print(people.getCountry())  打印的完全是一个东西

我们可以看到,类方法返回的是cls.country,所以,类方法返回的一定是类的字段,跟实例没关系。self.country和cls.country就是不一样。

所以,类方法的第一个形式参数必须是类对象。约定俗成,用“cls”作为第一个形参。

但是,类方法不一定只能返回cls.country,我们试试只返回p.country,居然也成功了:

class people:
    country="china"
    @classmethod
    def getCountry(cls):
        return p.country

p=people()
p.country = "Huaxia"
print(p.getCountry())   #打印成功:"Huaxia",实例对象调用静态方法
print(people.getCountry())  #打印成功:"Huaxia",类对象调用静态方法

接下来,我们用类方法写一个可读可写(可读取,可修改)的属性:

代码段_10

class people:
    country="china"
 
    @classmethod
    def getCountry(cls):
        return cls.country
    @classmethod
    def setCountry(cls,country):
        cls.country=country
  
p=people()
print(p.getCountry())   #打印成功:"china",原因见代码段_9
print(people.getCountry())  #打印成功:"china",原因见代码段_9
 
p.setCountry("Huaxia") #实例调用并修改了“类方法”
print(p.getCountry())#打印成功:"Huaxia"。
print(people.getCountry())#打印成功:"Huaxia"

如果用类调用类方法

代码段_11

class people:
    country="china"
 
    @classmethod
    def getCountry(cls):
        return cls.country
    @classmethod
    def setCountry(cls,country):
        cls.country=country
  
p=people()
print(p.getCountry())   #打印成功:"china",原因见代码段_9
print(people.getCountry())  #打印成功:"china",原因见代码段_9
 
people.setCountry("Zhonghua") #实例调用并修改了“类方法”
print(p.getCountry())#打印成功:"Zhonghua"。
print(people.getCountry())#打印成功:"Zhonghua"

可见,实例对象和类对象二者,不论谁修改类属性cls.country,结果是相同的。

那么,静态方法呢?

代码段_12

class people:
    country="china"
 
    @staticmethod
    def getCountry():
        return people.country
p=people()
print(p.getCountry())   #打印成功:"china",实例对象调用静态方法
print(people.getCountry())  #打印成功:"china",类对象调用静态方法

 

静态方法也名不副实。虽说是静态,类对象可以调用,实例对象也能调用。无论谁调用,返回的结果是完全相同的。

代码段_12中我们返回的是people.country,那么我们不妨试试返回p.country:

class people:
    country="china"
 
    @staticmethod
    def getCountry():
        return p.country #与普通方法类似,没有了self,只能用特定的p来代替
    
p=people()
p.country = "Huaxia"
print(p.getCountry())   #打印成功:Huaxia,实例对象调用静态方法,返回的是p.country
print(people.getCountry())  #打印成功:Huaxia,类对象调用静态方法,返回的是p.country

总结:类对象和实例对象都可以调用类方法和静态方法,造成的结果完全一致,没有区别。静态方法和类方法都可以返回调皮字段的类字段,也可以返回调皮字段的实例字段。只是类方法必须有参数cls

对于实例方法:

class people:
    country="china"

    def getCountry(self):
        return self.country

p=people()
p.country = "Huaxia"
print(p.getCountry())   #打印成功:"Huaxia",实例对象调用静态方法
print(people.getCountry())  #打印失败,若使用对象调用普通方法,括号里必须填入一个实例
print(people.getCountry(p))  #打印成功:"Huaxia",类对象调用普通方法

p2 = people()
p2.country = "Zhonghua"
print(people.getCountry(p2)) #打印成功:"Zhonghua",类对象调用普通方法

实例方法也可以只返回调皮字段的类字段:

class people:
    country="china"

    def getCountry(self):
        return people.country

p=people()
print(p.getCountry()) #打印成功:china,返回的是people.country

总结: 

  是否可以使用实例字段 是否可以使用类字段 是否可以被类直接调用 是否可以被实例直接调用
实例方法 是,但必须在括号里写上实例的名字
类方法
静态方法

Python的方法共有四种:实例方法(不加任何修饰器的方法),类方法,静态方法,特殊方法(魔法方法)

Python的属性就是个特殊方法,唯一体现属性不同的地方就是 :需要加上这条语句:

value = property(__get,__set)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!