0%

Python面向对象—类和实例的属性及方法

类属性和实例属性区别

类是模板,而实例则是根据类创建的对象
绑定在一个实例上的属性不会影响其他实例,但是,类本身也是一个对象,如果在类上绑定一个属性,则所有实例都可以访问类的属性,并且,所有实例访问的类属性都是同一个。即实例属性每个实例各自拥有,互相独立,而类属性有且只有一份,所有实例共同拥有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定义类属性可以直接在class中定义
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name
# 因为类属性是直接绑定在类上的,所以,访问类属性不需要创建实例,就可以直接访问
print(Person.address) # => Earth
# 对一个实例调用类的属性也是可以访问的,所有实例都可以访问到它所属的类的属性
p1 = Person('Bob')
p2 = Person('Alice')
print(p1.address) # => Earth
print(p2.address) # => Earth

# 类属性也是可以动态添加和修改的
#因为类属性只有一份,所以,当Person类的address改变时,所有实例访问到的类属性都改变了
Person.address = 'China'
print(p1.address) # => 'China'
print(p2.address) # => 'China'

类属性和实例属性冲突

修改类属性会导致所有实例访问到的类属性全部都受影响。下面测试在实例对象上修改类属性,

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
# 当p1的address实例属性删除后,p1.address就又返回类属性的值'Earth'了:
del p1.address
print(p1.address) # => Earth

实例方法

私有属性无法从类外部访问,但是从类的内部可以访问。除了可以定义实例的属性外,还可以定义实例的方法。
实例方法就是在类中定义的函数,它的第一个参数永远是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()) # self不需要显式传入
# => Bob

在实例方法内部,可以访问所有实例属性,这样,如果外部需要访问私有属性,可以通过方法调用获得,这种数据封装的形式除了能保护内部数据一致性外,还可以简化外部调用的难度。

方法也是属性

在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) # => <bound method Person.get_grade of <__main__.Person object at 0x109e58510>>
print(p1.get_grade()) # => A

如上,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()) # => A
p2 = Person('Alice', 65)
print(p2.get_grade()) # ERROR: AttributeError: 'Person' object has no attribute 'get_grade'
# 因为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
因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获得类的引用

天生我材必有用,千金散尽还复来~
  • 本文作者: XTLei
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
-------------本文结束感谢您的阅读-------------