实例属性和类属性
在下面的例子中,Dog类的属性,比如height(身高),是属于一条具体的狗,如大黄,二黑等。他们各自有各自的height。
这种属性我们成为实例属性,实例属性通常是在init方法中通过http://self.xxx = yyy的形式创建的。在init中创建的实例属性的好处是,所有的实例都有这些实例属性。
也可以在后续代码中通过实例名再额外添加,比如d1.nickname = '二黑子',但这种实例属性只给当前实例d1添加了。其他实例没有,访问d2.nickname会报错。
class Dog:
#构造方法
def __init__(self, name, height, power):
self.name = name
self.height = height
self.power = power
self.blood = 10
# --省略--
d1 = Dog('大黄', 0.7, 3) #创建第1个实例
d2 = Dog('二黑', 0.5, 4) #创建第2个实例
还有一些属性,它们不属于一个特定的实例,而是所有的实例所共享的。比如狗的数量这个值,他是属于整个狗类的,而不是属于某一条狗。这种属性,我们称为类属性。
添加类属性
我们现在给Dog类添加狗的数量属性(num_of_dogs)。
类属性不能通过http://self.xxx = yyy的形式创建,因为这样创建出来的是实例属性。
类属性的创建方式很简单:直接写在类中,不要写在init函数中,也不要加self.:
#类是一个模板
class Dog:
num_of_dogs = 0 # 类属性
#构造方法 - 添加实例属性,做其他的初始化工作
def __init__(self, name, height, power):
self.name = name
self.height = height
self.power = power
self.blood = 10
print(f"{self.name}出生了,汪汪!")
使用类属性
类属性是属于类的,访问类属性要通过类名访问。下面的代码做了几件事情:
- 在init函数中,一旦创建一个新的Dog,给num_of_dogs加一.
- 添加了一个die()方法,表示一个Dog去世了,一旦调用了die(),num_of_dogs就会减1。
- 创建了多个dog,测试numer_of_dogs数量变化;循环30次,随机选择一个Dog,调用die方法。
这里用到了随机模块random,还有自加(+=),自减(-+)运算符,不熟悉请自行补充相关知识,或者加入讨论群讨论。
本案例有点血腥,请动物爱好者不要入戏太深,一切为了学习编程。可以把Dog改成其他,比如蟑螂(小强)。说到这里,我好想念我曾经的那些狗狗 。
import random
#类是一个模板
class Dog:
num_of_dogs = 0 # 类属性
#构造方法 - 添加实例属性,做其他的初始化工作
def __init__(self, name, height, power):
self.name = name
self.height = height
self.power = power
self.blood = 10
print(f"{self.name}出生了,汪汪!")
Dog.num_of_dogs += 1
def die(self):
print(f"{self.name}已安息!")
Dog.num_of_dogs -= 1
# 创建100条狗,放到列表中
dogs = []
for i in range(100):
d = Dog(f"dog{i}", random.randint(30, 80), random.randint(1,12))
print(Dog.num_of_dogs)
dogs.append(d)
# 循环30次,每次随机选择一条狗,让它死掉
for i in range(30):
dog = random.choice(dogs)
dog.die()
print(Dog.num_of_dogs)
再加1个类属性
假设我们要判定一条狗是否可以成为警犬,我们用身高height来判定,如果height超过了60就可以。这个60就是警犬的标准。这个数字是对所有的Dog是通用的,是一个类属性。
import random
#类是一个模板
class Dog:
num_of_dogs = 0 # 类属性
police_height = 60 # 成为警犬的身高标准
# --省略init和die方法
# 判定是否可以成为警犬,返回True或者False
def can_be_police(self):
return self.height > Dog.police_height
# 创建100条狗,放到列表中
dogs = []
for i in range(100):
d = Dog(f"dog{i}", random.randint(30, 80), random.randint(1,12))
print(Dog.num_of_dogs)
dogs.append(d)
print(f'成为警犬的身高标准是:{Dog.police_height}')
for d in dogs:
if(d.can_be_police()):
print(f'{d.name} 可以成为警犬')
代码说明:
- 添加了一个police_height类变量
- 添加了一个实例方法,判定当前的dog是否可以成为警犬
- 代码最下方打印出可以成为警犬的狗的名字
代码实践技巧
你可能会想,这个60直接写在代码里不可以吗?还要定义成变量?
直接写数字60不是不可以,但有诸多弊端:
- 多个地方用到,可能会写错,出现不一致。
- 如果标准从60提高到了62,要修改多个地方
- 定义成了变量,代码更容易懂。要不然看到60个这个数字,不一定理解是什么意思。
实际上,polic_height通常不会改变,我们也可以称他为常量。
常量和变量没什么区别,一般常量的名字都是全大写的,仅此而已。看到全大写就知道这个值是不会改变的,实际上是可以改变的,只是一个约定。
这里的不会改变是指不会在程序运行中动态改变。把常量的值从60改成62属于修改代码,任何时候都可以的。
类方法
仔细看一下前面定义的方法,他们都有两个特征:
- 方法的第一个参数都是self
- 它们都使用了实例变量,脱离了具体的实例,这些方法是无法运行的,是没有意义的
这些方法虽然都是共同的,但是他们的运行过程依赖了实例变量,所以他们都是实例方法。类中的方法默认就是实例方法。
import random
#类是一个模板
class Dog:
num_of_dogs = 0 # 类属性
police_height = 60 # 成为警犬的身高标准
#构造方法 - 添加实例属性,做其他的初始化工作
def __init__(self, name, height, power):
self.name = name
self.height = height
self.power = power
self.blood = 10
print(f"{self.name}出生了,汪汪!")
Dog.num_of_dogs += 1
def die(self):
print(f"{self.name}已安息!")
Dog.num_of_dogs -= 1
# 判定是否可以成为警犬,返回True或者False
def can_be_polic(self):
return self.height > Dog.police_height
这3个方法都是实例属性有关,都是实例方法。
但有的方法和具体的实例无关,而是和整个狗类有关。比如有方法狗类宣言,它的功能是:
- 打印狗类宣言
- 介绍狗类的数量
看代码:
import random
#类是一个模板
class Dog:
num_of_dogs = 0 # 类属性
police_height = 60 # 成为警犬的身高标准
#--省略--
# 类方法
@classmethod
def wangwang(cls):
print('我们是狗,我们是人类的朋友')
print('''
^..^ /
/_/_____/
/ /
/ /
''')
print(f'我们共有{cls.num_of_dogs}个成员')
# --省略--
Dog.wangwang()
代码说明:
- 添加的类方法wangwang()
- 类方法的前面要添加:@classmethod。这是一个装饰器。不懂装饰器?请看本文最后的文章列表。
- 类方法的第一个参数是cls,是class的缩写,表示当前类。使用cls可以访问类属性或者其他类方法。
- 调用类方法使用类名:Dog.wangwang()
静态方法
我们可以看到类方法对类属性有所依赖,有些方法对实例属性和类属性都没有依赖,也不需要传入self或者cls,这些方法就是静态方法。
假设我们有另外几个方法:只是打印狗类的字符画,不用打印狗的数量或者其他。没有任何类属性或者实例属性的依赖。
看代码:
import random
#类是一个模板
class Dog:
num_of_dogs = 0 # 类属性
police_height = 60 # 成为警犬的身高标准
# --省略--
# 类方法
@classmethod
def wangwang(cls):
print('我们是狗,我们是人类的朋友')
print('''
^..^ /
/_/_____/
/ /
/ /
''')
print(f'我们共有{cls.num_of_dogs}个成员')
#静态方法:小狗的图像
@staticmethod
def pic_little():
print('''
/^ ^
/ 0 0
V Y /V
/ -
/ |
V__) ||
''')
#静态方法:大狗的图像
@staticmethod
def pic_big():
print('''
___
__/_ `. .-"""-.
_,` | -' / )`-')
"") `"` ((`"`
___Y , .'7 /|
(_,___/...-` (_/_/
''')
#静态方法:长的图像
@staticmethod
def pic_long():
print('''
.-.
(___________________________() `-,
( ______________________ /''"`
// //
"" "" "" ""
''')
#--省略--
Dog.wangwang()
Dog.pic_little()
Dog.pic_big()
Dog.pic_long()
代码说明:
- 添加了3个静态方法,分别打印3种不同的狗的图像
- 静态方法前面必须加:@staticmethod,这是一个装饰器。不懂装饰器,看本文最后的文章列表。
- 静态方法不强制要求传入self或者cls。
- 调用静态方法通过类名。
练习:
今天的练习:
1. 给Dog类添加一个类属性dog_list,它是一个列表,用来保存所有创建出来的类。
2. 修改init函数,把每个新建的Dog添加到dog_list中。
3. 修改die函数,从dog_list中移除去世的Dog。
4. 删掉num_of_dogs属性,添加一个类方法num_of_dogs,返回狗的数量。提示:通过dog_list的长度确定狗的数量。
5. 修改所有值钱用到num_of_dogs的地方,改成使用新定义的方法,而不是变量名。
下一节课学习继承,请保持关注!
https://mp.weixin.qq.com/s/yDOv4z_iw8rXX_i2s2Kj1Qmp.weixin.qq.com你可以在文章下面打卡,分享你的看法。
我是麦叔:教你学编程,陪你走职场的路!