类的成员可以分为三大类:字段、方法和属性:
注:所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段。而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份。
(一)字段
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同
1 class Province: 2 # 静态字段 3 country = "China" 4 5 def __init__(self, name): 6 # 普通字段 7 self.name = name 8 9 obj_henan = Province("HeNan") 10 11 print(obj_hunan.name) # 使用对象访问跑一趟那个字段 12 print(Province.country) # 使用类访问静态字段
由上述代码可以看出【普通字段需要通过对象来访问】【静态字段通过类访问】,在使用上可以看出普通字段和静态字段的归属是不同的。其在内容的存储方式类似如下图:
由上图可是:
- 静态字段在内存中只保存一份
- 普通字段在每个对象中都要保存一份
应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段
(二)方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
- 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
- 类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
- 静态方法:由类调用;无默认参数;
1 class Foo: 2 def __init__(self, name): 3 self.name = name 4 5 # 普通方法,由对象调用 6 def ord_fun(self): 7 print(self.name) 8 9 # 静态方法 10 # 普通方法经过两步变成静态字段,1,去掉参数self,2,加上装饰器 @staticmethod 11 # 如果类里的方法用不到对象(self)里的字段,静态方法可以有除了self的参数 12 # 静态方法其实就是面向过程里的普通函数,之所以放到类里就是表示方法和该类有关 13 @staticmethod 14 def static_func(arg1): 15 print(arg1) 16 17 # 类方法 18 # 静态方法的一种特殊形式,自带特殊参数cls,使用装饰器 @classmethod 19 @classmethod 20 def class_func(cls): # cls = class 21 print(cls) 22 23 obj_foo = Foo("foo object") 24 obj_foo.ord_fun() 25 Foo.static_func("static func") 26 Foo.class_func()
相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。
不同点:方法调用者不同、调用方法时自动传入的参数不同。
(三) 属性
为了理解Python的属性,我们先来看一个例子,
假如我们有一个学生类:
1 class Student(object): 2 def __init__(self): 3 self.score = 0 4 5 s1 = Student() 6 s1.score = 9999
这个赋值 s1.score = 9999 这显然不合逻辑,score的正常取值范围应该是0-100, 为了限制score的范围,我们可能会通过定义一个set_score()
方法来设置成绩,再通过一个get_score()
来获取成绩,这样,在set_score()
方法里,就可以检查参数:
1 class Student(object): 2 def __init__(self): 3 pass 4 5 def set_score(self, value): 6 if not isinstance(value, int): 7 raise ValueError("Score must be int type") 8 elif score < 0 or value> 100: 9 raise ValueError("Score must between 0 - 100") 10 else: 11 self.score = value 12 13 s1 = Student() 14 s1.set_score(9999)
这时 s1.set_score(9999) 就会有如下的报错:
1 raise ValueError("Score must between 0 - 100") 2 ValueError: Score must between 0 - 100
但是,上面的调用方法又略显复杂
有没有既能检查参数,又可以类似于访问字段一样设置score的方法呢?(使用score = 9999,而不用s1.set_score(9999)), 对于追求完美的Python程序员来说,这是必须要做到的!
还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用。Python内置的@property
装饰器就是负责把一个方法变成属性调用的:
于是我们对上面的Studeng类做修改:
1 class Student(object): 2 def __init__(self): 3 pass 4 5 @property 6 def score(self, score): 7 return self.score 8 9 @score.setter 10 def score(self, value): # 函数名和@property一致 11 if not isinstance(value, int): 12 raise ValueError("Score must be int type") 13 elif value < 0 or value > 100: 14 raise ValueError("Score must between 0 - 100") 15 else: 16 self.score = value 17 18 s1 = Student() 19 s1.score = 9999 # 这里用 "="访问score
@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
1 raise ValueError("Score must between 0 - 100") 2 ValueError: Score must between 0 - 100
注意到这个神奇的@property
,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
1 @property 2 def birth(self): 3 return self._birth 4 5 @birth.setter 6 def birth(self, value): 7 self._birth = value 8 9 @property 10 def age(self): 11 return 2017 - self._birth
上面的birth
是可读写属性,而age
就是一个只读属性,因为age
可以根据birth
和当前时间计算出来。
@property
广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。