Last Updated on

在Python中,类和实例都是对象(虽然实例是类创建的),有其自己的属性和方法,我们称之为类变量和实例变量,其实用类属性和实例属性来称呼它们也是可以的,但是变量这个词已经成为程序语言的习惯称谓。

类变量

​是在类的所有实例之间共享的属性和方法(也就是说,它们不是单独分配给每个实例的)

实例变量

实例化之后,每个实例单独拥有的变量。


怎么理解呢,看下面示例:

class Test(object):
    num_of_instance = 0

    def __init__(self, name):
        self.name = name
        Test.num_of_instance += 1

print("before:", Test.num_of_instance)
t1 = Test('jack')
print("create t1 after:", Test.num_of_instance)
t2 = Test('lucy')
print("create t2 after:", Test.num_of_instance)


# 输出结果:
before: 0
create t1 after: 1
create t2 after: 2

可以看到,在类中直接定义的属性num_of_instance就是类属性,在__init__方法下定义的self.name就是实例属性,__init__方法是创建实例时的初始化方法,会将实例self作为第一个参数传入,所以在其中通过self即可定义实例属性。

而且由于Python是动态语言,可以在运行的时候,可以新定义或者修改,删除类属性和实例属性,比如:

class Test(object):
    num_of_instance = 0

Test.new_attr = 'new_attr'
print(Test.new_attr)

Test.num_of_instance = 1
print(Test.num_of_instance)

del Test.num_of_instance
print(Test.num_of_instance)


# 输出结果:
new_attr
1
AttributeError: type object 'Test' has no attribute 'num_of_instance'

可以看到,在类定义好后,在运行中,同样可以新增,修改,删除类属性。

同样的,与类属性相同,实例属性,同样的除了在定义时指定,也可以在运行中进行新增,修改,删除等:

class Test(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age


amos = Test('Amos', 24)
amos.sex = 'male'
print(amos.sex)

amos.age = 25
print(amos.age)

del amos.sex
print(amos.sex)


# 输出结果:
male
25
AttributeError: 'Test' object has no attribute 'sex'

类变量和实例变量的引用

类变量的引用,也就是两部分,方法和属性,看下面示例:

class Test(object):
    name = 'myname'
    age = 20

    def __init__(self, age):
        self.age = age

    def work(self):
        print("{} {} is working".format(self.name, self.age))

    @classmethod
    def sleep(cls):
        print("{} {} is sleeping".format(cls.name, cls.age))


amos = Test(24)
amos.work()
amos.sleep()

Test.work()
Test.sleep()


# 输出结果:
myname 24 is working
myname 20 is sleeping

myname 24 is working
myname 20 is sleeping

细心的朋友会发现,虽然定义的name是类属性,但是通过实例self.name却能够输出该类属性,这是因为实例对象可引用类属性以及实例属性,其引用遵循以下规则:

  1. 总是先到实例对象中查找属性,再到类属性中查找属性;
  2. 属性绑定语句总是为实例对象创建新属性,属性存在时,更新属性指向的对象。

所以在实例方法work()中,可以通过self.name获取到类属性,但是由于实例的self.age属性,在实例化时定义了,所以在使用self.age时,会使用实例属性而不是类属性。

我们还发现,实例amos是可以调用类方法的,默认会将创建此实例的类作为输入,使用的属性则是类属性,即使实例的age已经变化,但是依然类属性依然存在且可以使用。

至于类方法自然也可以直接使用类型进行调用,此方法与实例没有关系,不会使用到实例的属性和方法。通过类名调用实例方法时,则会需要传入self参数(即使需要传入实例),比如我将实例amos传入,才能正常调用,直接是无法调用的。

至于@classmethod定义类方法的相关问题,不了解的可以查我的另一篇博文:

《Python @staticmethod和@classmethod》


我们再来看一个例子:

class Test(object):
    name = 'myname'
    likes = ['travel']


amos = Test()
amos.name = amos.name.strip('my')
amos.likes.append('read')

print(amos.name)
print(Test.name)

print(amos.likes)
print(Test.likes)


# 输出结果:
name
myname
['travel', 'read']
['travel', 'read']

可以发现,我们只定义了类变量,因为实例可以引用类变量,我们直接通过实例修改类变量时,默认是会优先创建新的实例变量,但是如果如果存在类变量,且类变量是可变对象,则会直接修改类变量,不会创建新的实例变量,如果是不可变对象,则会创建新的实例变量。

如上,字符串变量为不可变对象,所以修改后,会创建新的实例变量。而列表是可变对象,所以可以直接修改类属性。

在python中,strings, tuples, 和numbers是不可更改的对象,而 list, dict, set 等则是可以修改的对象。