类属性和实例属性区别
类是模板,而实例则是根据类创建的对象。
绑定在一个实例上的属性不会影响其他实例,但是,类本身也是一个对象,如果在类上绑定一个属性,则所有实例都可以访问类的属性,并且,所有实例访问的类属性都是同一个。即实例属性每个实例各自拥有,互相独立,而类属性有且只有一份,所有实例共同拥有。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Person(object): address = 'Earth' def __init__(self, name): self.name = name
print(Person.address)
p1 = Person('Bob') p2 = Person('Alice') print(p1.address) print(p2.address)
Person.address = 'China' print(p1.address) print(p2.address)
|
类属性和实例属性冲突
修改类属性会导致所有实例访问到的类属性全部都受影响。下面测试在实例对象上修改类属性,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Person(object): address = 'Earth' def __init__(self, name): self.name = name
p1 = Person('Bob') p2 = Person('Alice')
print('Person.address = ' + Person.address)
p1.address = 'China' print('p1.address = ' + p1.address)
print('Person.address = ' + Person.address) print('p2.address = ' + p2.address)
Person.address = Earth p1.address = China Person.address = Earth p2.address = Earth
|
在设置了p1.address = 'China'后,p1访问address确实变成了'China',但是,Person.address和p2.address仍然是'Earch'。原因是p1.address = 'China'并没有改变Person的address,而是给p1这个实例绑定了实例属性address,对p1来说,它有一个实例属性address(值是'China'),而它所属的类Person也有一个类属性address,所以访问p1.address时,优先查找实例属性,返回'China';访问p2.address时,p2没有实例属性address,但是有类属性address,因此返回'Earth'。
可见,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问。
1 2 3
| del p1.address print(p1.address)
|
实例方法
私有属性无法从类外部访问,但是从类的内部可以访问。除了可以定义实例的属性外,还可以定义实例的方法。
实例方法就是在类中定义的函数,它的第一个参数永远是self,指向调用该方法的实例本身,其他参数和一个普通函数是完全一样的,
1 2 3 4 5 6 7
| class Person(object):
def __init__(self, name): self.__name = name
def get_name(self): return self.__name
|
如上,get_name(self)就是一个实例方法,它的第一个参数是self。__init__(self, name)其实也可看做是一个特殊的实例方法。
调用实例方法必须通过实例调用,
1 2 3
| p1 = Person('Bob') print(p1.get_name())
|
在实例方法内部,可以访问所有实例属性,这样,如果外部需要访问私有属性,可以通过方法调用获得,这种数据封装的形式除了能保护内部数据一致性外,还可以简化外部调用的难度。
方法也是属性
在class中定义的实例方法其实也是属性,它实际上是一个函数对象,
1 2 3 4 5 6 7 8 9 10
| class Person(object): def __init__(self, name, score): self.name = name self.score = score def get_grade(self): return 'A'
p1 = Person('Bob', 90) print(p1.get_grade) print(p1.get_grade())
|
如上,p1.get_grade返回的是一个函数对象,p1.get_grade()才是方法调用。
因为方法也是一个属性,所以,它也可以动态地添加到实例上,只是需要用types.MethodType()把一个函数变为一个实例方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import types def fn_get_grade(self): if self.score >= 80: return 'A' if self.score >= 60: return 'B' return 'C'
class Person(object): def __init__(self, name, score): self.name = name self.score = score
p1 = Person('Bob', 90) p1.get_grade = types.MethodType(fn_get_grade, p1) print(p1.get_grade()) p2 = Person('Alice', 65) print(p2.get_grade())
|
给一个实例动态添加方法并不常见,直接在class中定义要更直观。
类方法
在class中定义的全部是实例方法,实例方法第一个参数self是实例本身。在class中定义类方法,
1 2 3 4 5 6 7 8 9 10 11 12
| class Person(object): count = 0 @classmethod def how_many(cls): return cls.count def __init__(self, name): self.name = name Person.count = Person.count + 1
print(Person.how_many()) p1 = Person('Bob') print(Person.how_many())
|
通过标记一个@classmethod,该方法将绑定到Person类,而非类的实例上。类方法的第一个参数将传入类本身,通常将参数名命名为cls,上面的cls.count实际上相当于Person.count。
因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获得类的引用。