Python基础-类变量和实例变量

Python基础-类变量和实例变量

写在前面

如非特别说明,下文均基于Python3

大纲:

outline

1. 类变量和实例变量

Python Tutorial中对于类变量和实例变量是这样描述的:

Generally speaking, instance variables are for data unique to each instance and class variables are for attributes and methods shared by all instances of the class:

通常来说,实例变量是对于每个实例都独有的数据,而类变量是该类所有实例共享的属性和方法。

其实我更愿意用类属性和实例属性来称呼它们,但是变量这个词已经成为程序语言的习惯称谓。一个正常的示例是:

class Dog:

    kind = 'canine'         # class variable shared by all instances

    def __init__(self, name):
        self.name = name    # instance variable unique to each instance

Dog中,类属性kind为所有实例所共享;实例属性name为每个Dog的实例独有。

2. 类对象和实例对象

2.1 类对象

Python中一切皆对象;类定义完成后,会在当前作用域中定义一个以类名为名字,指向类对象的名字。如

class Dog:
    pass

会在当前作用域定义名字Dog,指向类对象Dog

类对象支持的操作
总的来说,类对象仅支持两个操作:

  1. 实例化;使用instance_name = class_name()的方式实例化,实例化操作创建该类的实例。
  2. 属性引用;使用class_name.attr_name的方式引用类属性。
2.2 实例对象

实例对象是类对象实例化的产物,实例对象仅支持一个操作:

  1. 属性引用;与类对象属性引用的方式相同,使用instance_name.attr_name的方式。

按照严格的面向对象思想,所有属性都应该是实例的,类属性不应该存在。那么在Python中,由于类属性绑定就不应该存在,类定义中就只剩下函数定义了。

Python tutorial关于类定义也这么说:

In practice, the statements inside a class definition will usually be function definitions, but other statements are allowed, and sometimes useful.

实践中,类定义中的语句通常是函数定义,但是其他语句也是允许的,有时也是有用的。

这里说的其他语句,就是指类属性的绑定语句。

3. 属性绑定

在定义类时,通常我们说的定义属性,其实是分为两个方面的:

  1. 类属性绑定
  2. 实例属性绑定

绑定这个词更加确切;不管是类对象还是实例对象,属性都是依托对象而存在的。

我们说的属性绑定,首先需要一个可变对象,才能执行绑定操作,使用

objname.attr = attr_value

的方式,为对象objname绑定属性attr

这分两种情况:

  1. 若属性attr已经存在,绑定操作会将属性名指向新的对象;
  2. 若不存在,则为该对象添加新的属性,后面就可以引用新增属性。
3.1 类属性绑定

Python作为动态语言,类对象和实例对象都可以在运行时绑定任意属性。因此,类属性的绑定发生在两个地方:

  1. 类定义时;
  2. 运行时任意阶段。

下面这个例子说明了类属性绑定发生的时期:

class Dog:

    kind = 'canine'

Dog.country = 'China'

print(Dog.kind, ' - ', Dog.country) # output: canine  -  China
del Dog.kind
print(Dog.kind, ' - ', Dog.country) # AttributeError: type object 'Dog' has no attribute 'kind'

在类定义中,类属性的绑定并没有使用objname.attr = attr_value的方式,这是一个特例,其实是等同于后面使用类名绑定属性的方式。
因为是动态语言,所以可以在运行时增加属性,删除属性。

3.2 实例属性绑定

与类属性绑定相同,实例属性绑定也发生在两个地方:

  1. 类定义时;
  2. 运行时任意阶段。

示例:

class Dog:

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

dog = Dog('Lily', 3)
dog.fur_color = 'red'

print('%s is %s years old, it has %s fur' % (dog.name, dog.age, dog.fur_color))
# Output: Lily is 3 years old, it has red fur

Python类实例有两个特殊之处:

  1. __init__在实例化时执行
  2. Python实例调用方法时,会将实例对象作为第一个参数传递

因此,__init__方法中的self就是实例对象本身,这里是dog,语句

self.name = name
self.age = age

以及后面的语句

dog.fur_color = 'red'

为实例dog增加三个属性name, age, fur_color

4. 属性引用

属性的引用与直接访问名字不同,不涉及到作用域。

4.1 类属性引用

类属性的引用,肯定是需要类对象的,属性分为两种:

  1. 数据属性
  2. 函数属性

数据属性引用很简单,示例:

class Dog:

    kind = 'canine'

Dog.country = 'China'

print(Dog.kind, ' - ', Dog.country) # output: canine  -  China

通常很少有引用类函数属性的需求,示例:

class Dog:

    kind = 'canine'

    def tell_kind():
        print(Dog.kind)
        
Dog.tell_kind() # Output: canine

函数tell_kind在引用kind需要使用Dog.kind而不是直接使用kind,涉及到作用域,这一点在我的另一篇文章中有介绍:Python进阶 - 命名空间与作用域

4.2 实例属性引用

使用实例对象引用属性稍微复杂一些,因为实例对象可引用类属性以及实例属性。但是实例对象引用属性时遵循以下规则:

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

示例1:

class Dog:

    kind = 'canine'
    country = 'China'

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

dog = Dog('Lily', 3, 'Britain')
print(dog.name, dog.age, dog.kind, dog.country)

# output: Lily 3 canine Britain

类对象Dog与实例对象dog均有属性country,按照规则,dog.country会引用到实例对象的属性;但实例对象dog没有属性kind,按照规则会引用类对象的属性。

示例2:

class Dog:

    kind = 'canine'
    country = 'China'

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

dog = Dog('Lily', 3, 'Britain')
print(dog.name, dog.age, dog.kind, dog.country) # Lily 3 canine Britain
print(dog.__dict__) # {'name': 'Lily', 'age': 3, 'country': 'Britain'}

dog.kind = 'feline'
print(dog.name, dog.age, dog.kind, dog.country) # Lily 3 feline Britain
print(dog.__dict__) 
print(Dog.kind) # canine 没有改变类属性的指向
# {'name': 'Lily', 'age': 3, 'country': 'Britain', 'kind': 'feline'}

使用属性绑定语句dog.kind = 'feline',按照规则,为实例对象dog增加了属性kind,后面使用dog.kind引用到实例对象的属性。

这里不要以为会改变类属性Dog.kind的指向,实则是为实例对象新增属性,可以使用查看__dict__的方式证明这一点。

示例3,可变类属性引用:

class Dog:
    
    tricks = []

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

    def add_trick(self, trick):
        self.tricks.append(trick)

d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
print(d.tricks) # ['roll over', 'play dead']

语句self.tricks.append(trick)并不是属性绑定语句,因此还是在类属性上修改可变对象。

4.2.2 方法属性引用

与数据成员不同,类函数属性在实例对象中会变成方法属性。

先看一个示例:

class MethodTest:

    def inner_test(self):
        print('in class')

def outer_test():
    print('out of class')

mt = MethodTest()
mt.outer_test = outer_test

print(type(MethodTest.inner_test))  # <class 'function'>
print(type(mt.inner_test))          #<class 'method'>
print(type(mt.outer_test))          #<class 'function'>

可以看到,类函数属性在实例对象中变成了方法属性,但是并不是实例对象中所有的函数都是方法。

Python tutorial中这样介绍方法对象:

When an instance attribute is referenced that isn’t a data attribute, its class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.

引用非数据属性的实例属性时,会搜索它对应的类。如果名字是一个有效的函数对象,Python会将实例对象连同函数对象打包到一个抽象的对象中并且依据这个对象创建方法对象:这就是被调用的方法对象。当使用参数列表调用方法对象时,会使用实例对象以及原有参数列表构建新的参数列表,并且使用新的参数列表调用函数对象。

那么,实例对象只有在引用方法属性时,才会将自身作为第一个参数传递;调用实例对象的普通函数,则不会。
所以可以使用如下方式直接调用方法与函数:

mt.inner_test()
mt.outer_test()

除了方法与函数的区别,其引用与数据属性都是一样的

5. 最佳实践

虽然Python作为动态语言,支持在运行时绑定属性,但是从面向对象的角度来看,还是在定义类的时候将属性确定下来。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容