如非特別說明,下文皆基於Python3
大綱:
在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 instancesdef __init__(self, name):self.name = name # instance variable unique to each instance
類別Dog
中,類別屬性kind
為所有實例所共用;實例屬性name
為每個Dog
的實例獨有。
Python
中一切皆物件;類別定義完成後,會在目前作用域中定義一個以類別名為名字,指向類別物件的名字。如
class Dog:pass
會在目前作用域定義名字Dog
,指向類別物件Dog
。
類別物件支援的操作:
總的來說,類別物件僅支援兩個操作:
實例化;使用instance_name = class_name()
的方式實例化,實例化作業會建立該類別的實例。
屬性參考;使用class_name.attr_name
的方式來引用類別屬性。
實例物件是類別物件實例化的產物,實例物件只支援一個操作:
屬性參考;與類別物件屬性所引用的方式相同,使用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 usefulful.在
實作中,類別定義中的語句通常是函數定義,但是其他語句也是允許的,有時也是有用的。
這裡說的其他語句,就是指類別屬性的綁定語句。
在定義類別時,通常我們說的定義屬性,其實是分成兩個面向的:
類別屬性綁定
實例屬性綁定
#用綁定這個字更確切;不管是類別物件還是實例對象,屬性都是依托對象而存在的。
我們說的屬性綁定,首先需要一個可變對象,才能執行綁定操作,使用
objname.attr = attr_value
的方式,為對象objname
綁定屬性attr
。
這分成兩種情況:
若屬性attr
已經存在,綁定操作會將屬性名稱指向新的物件;
若不存在,則為該物件新增新的屬性,後面就可以引用新增屬性。
Python
作為動態語言,類別物件和實例物件都可以在執行時間綁定任意屬性。因此,類別屬性的綁定發生在兩個地方:
類別定義時;
運行時任意階段。
下面這個範例說明了類別屬性綁定發生的時期:
class Dog: kind = 'canine'Dog.country = 'China'print(Dog.kind, ' - ', Dog.country) # output: canine - Chinadel Dog.kindprint(Dog.kind, ' - ', Dog.country) # AttributeError: type object 'Dog' has no attribute 'kind'
在類別定義中,類別屬性的綁定並沒有使用objname.attr = attr_value
的方式,這是一個特例,其實是等同於後面使用類別名稱綁定屬性的方式。
因為是動態語言,所以可以在執行時增加屬性,刪除屬性。
與類別屬性綁定相同,實例屬性綁定也發生在兩個地方:
類別定義時;
運行時任意階段。
範例:
class Dog:def __init__(self, name, age):self.name = nameself.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
類別實例有兩個特殊之處:
__init__
在實例化時執行
#Python
實例呼叫方法時,會將實例物件作為第一個參數傳遞
因此,__init__
方法中的self
就是實例物件本身,這裡是dog
,語句
self.name = nameself.age = age
以及后面的语句
dog.fur_color = 'red'
为实例dog
增加三个属性name
, age
, fur_color
。
属性的引用与直接访问名字不同,不涉及到作用域。
类属性的引用,肯定是需要类对象的,属性分为两种:
数据属性
函数属性
数据属性引用很简单,示例:
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进阶 - 命名空间与作用域
使用实例对象引用属性稍微复杂一些,因为实例对象可引用类属性以及实例属性。但是实例对象引用属性时遵循以下规则:
总是先到实例对象中查找属性,再到类属性中查找属性;
属性绑定语句总是为实例对象创建新属性,属性存在时,更新属性指向的对象。
示例1:
class Dog: kind = 'canine'country = 'China'def __init__(self, name, age, country):self.name = nameself.age = ageself.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 = nameself.age = ageself.country = country dog = Dog('Lily', 3, 'Britain')print(dog.name, dog.age, dog.kind, dog.country) # Lily 3 canine Britainprint(dog.__dict__) # {'name': 'Lily', 'age': 3, 'country': 'Britain'}dog.kind = 'feline'print(dog.name, dog.age, dog.kind, dog.country) # Lily 3 feline Britainprint(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 = namedef 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)
并不是属性绑定语句,因此还是在类属性上修改可变对象。
与数据成员不同,类函数属性在实例对象中会变成方法属性。
先看一个示例:
class MethodTest:def inner_test(self):print('in class')def outer_test():print('out of class') mt = MethodTest() mt.outer_test = outer_testprint(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()
除了方法与函数的区别,其引用与数据属性都是一样的
虽然Python
作为动态语言,支持在运行时绑定属性,但是从面向对象的角度来看,还是在定义类的时候将属性确定下来。
以上是Python基礎-類別變數和實例變數的詳細內容。更多資訊請關注PHP中文網其他相關文章!