01. 类属性
1.1 类属性与对象属性的概念
- 对象属性:
- 就是在类的
__init__()
方法中使用self.属性名 = 属性值
的方式给类的对象添加的属性。 - 对象属性存储在内存的堆中,同一个类的不同对象之间,属性数据相互独立不共享。
- 就是在类的
类属性:
定义一个Dog类,要求有品种、年龄、性别、所属科目四个属性。定义完成后,实例化四个对象。 ```python class Dog: def init(self, variety, age, gender, type):
# 这里的属性都是对象属性
self.variety = variety # 品种
self.age = age # 年龄
self.gender = gender # 性别
self.type = type # 所属科目
d1 = Dog(“哈士奇”, 2, “雄”, “犬科”) d2 = Dog(“边牧”, 5, “雌”, “犬科”) d3 = Dog(“拉布拉多”, 3, “雌”, “犬科”) d4 = Dog(“金毛”, 1, “雄”, “犬科”)
- 此时,Dog中所有的属性都是对象属性,都存储在堆中并且相互之间是独立的。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2692415/1662279947510-c367c7dd-ba23-4215-a97c-4fa6596d0235.png#averageHue=%23a6917f&clientId=u4c4f8de9-f970-4&from=paste&height=491&id=u3884240c&originHeight=1040&originWidth=1059&originalType=binary&ratio=1&rotation=0&showTitle=false&size=84286&status=done&style=none&taskId=u8d271dab-1c57-41ba-a4d8-4ee20952527&title=&width=500)
<a name="a0eWV"></a>
#### 1.2.2 使用类属性描述共享的数据
- 在1.2.1的程序中,我们发现其实Dog类的所有对象的所属科目都应该是犬科的,因为狗就是属于犬科的。
- 使用对象属性可以表述所属科目这个信息,但是所有的对象都会有一份,数据还是完全相同的,这实际上是浪费了内存空间出现数据冗余的体现。
- 因此,可以将Dog类的type属性定义为类属性
```python
class Dog:
type = "犬科" # 所属科目,类属性
# 此时在__init__()方法中不需要再定义所属科目。
def __init__(self, variety, age, gender):
self.variety = variety # 品种
self.age = age # 年龄
self.gender = gender # 性别
d1 = Dog("哈士奇", 2, "熊")
d2 = Dog("边牧", 5, "雌")
d3 = Dog("拉布拉多", 3, "雌")
d4 = Dog("金毛", 1, "熊")
# Dog类的所有对象都共享唯一的type属性数据。
print(d1.type, d2.type, d3.type, d4.type) # 犬科 犬科 犬科 犬科
- 此时,在方法区的静态池中就出现了一个
Dog.type = "犬科"
的数据,Dog类的所有对象都共享这个Dog.type
,以此节约了内存空间。
注意:类属性不仅可以通过类的对象调用,还可以直接通过类调用。
print(d1.type) # 犬科
print(Dog.type) # 犬科
1.3 类属性只能通过类修改
调用类属性时可以通过
类.类属性
或者类的对象.类属性
来调用,但是要修改类属性时只能通过类.类属性 = 新值
的方式修改。Dog.type = "猫科"
print(d1.type, d2.type, d3.type, d4.type) # 猫科 猫科 猫科 猫科
注意:
对象.类属性 = 新值
在语法上并不报错,但是这种方式只会修改对象本身属性值,对类的其他对象并没有影响。- 本质上就是说在对象中创建了一个与类属性同名的对象属性,该对象属性的值覆盖了类属性的值(在堆中动态增加了属性,通过
__dict__
可查看)。 - 但实际方法区静态池中的类属性的值并没有被修改。 ```python d1 = Dog(“哈士奇”, 2, “熊”) d1.type = “猫科” print(d1.dict) # {‘variety’: ‘哈士奇’, ‘age’: 2, ‘gender’: ‘熊’, ‘type’: ‘猫科’},发现d1中增加了type属性,且值为“猫科”。
- 本质上就是说在对象中创建了一个与类属性同名的对象属性,该对象属性的值覆盖了类属性的值(在堆中动态增加了属性,通过
d2 = Dog(“边牧”, 5, “雌”) print(d2.dict) # {‘variety’: ‘边牧’, ‘age’: 5, ‘gender’: ‘雌’},而d2中没有。
d3 = Dog(“拉布拉多”, 3, “雌”) d4 = Dog(“金毛”, 1, “熊”)
print(d1.type, d2.type, d3.type, d4.type) # 猫科 犬科 犬科 犬科 print(Dog.type) # 犬科
<a name="LAJm0"></a>
## 02. 类方法
<a name="nMHJC"></a>
### 2.1 类方法与对象方法的概念
- 对象方法:
- 对象方法是指定义在类中,并且方法的首个参数是self的方法。
- 当通过对象调用对象方法时,Python解释器会自动将调用该方法的对象的地址赋值给self。
- 当通过类来调用对象方式时,解释器不会自动给self赋值。
- 类方法:
- 类方法是指被`@classmethod`装饰器修饰的方法,并且类方法的第一个参数为cls,用来接收当前类的地址。
- 类方法可以直接被类调用,也可以被类的对象调用,这两种调用方式Python解释器都会自动给cls赋值。
- 类方法的使用场景:当函数中某个方法要来操作类属性时,建议将这个方法设置为类方法。
<a name="BytXh"></a>
### 2.2 类方法的应用示例
- 应用需求:
- 定义一个Person类,有一个name属性。
- 要求实例化对象后,可以查看到该Person共实例化了多少个对象。
- 实现思路:
- 定义一个类变量来记录Person类实例化对象的个数,因为这个属性不能随意的被普通对象修改,因此该属性应该被私有化。
- 定义一个方法来完成计数操作,当对象被初始化时,就调用该方法进行计数。
- 代码实现:
```python
class Person:
__count = 0 # 定义类属性,记录当前类实例化对象的个数。
def __init__(self, name):
self.name = name
self.counter() # 实例化完成后,调用计数器进行计数。
@classmethod
def counter(cls):
cls.__count += 1 # 类属性需要用cls(当前类)来访问修改。
@classmethod
def get_count(cls):
return cls.__count
p1 = Person("小明")
print(f"Person类已经实例化了{Person.get_count()}个对象") # Person类已经实例化了1个对象
p2 = Person("小张")
print(f"Person类已经实例化了{Person.get_count()}个对象") # Person类已经实例化了2个对象