-----------支持作者请转发本文-----------
李宁老师已经在「极客起源」 微信公众号推出《Python编程思想》电子书,囊括了Python的核心技术,以及Python的主要函数库的使用方法。读者可以在「极客起源」 公众号中输入 160442 开始学习。
-----------正文-----------
在类体内定义的变量,默认属于类本身。如果把类当成类命名空间,那么该类变量其实就是定义在类命名空间内的变量。
1. 类变量和实例变量
在类命名空间内定义的变量就属于类变量,Python可以使用类来读取、修改类变量。例如,下面代码定义了一个 Teacher类,并为该类定义了多个类变量。
示例代码:class_var.py
class Teacher :
name = '李宁'
salary = 66666.66
post_code = '12345678'
def print_info (self):
# 尝试直接访问类变量
# print(name) # 报错
# 通过类来访问类变量
print(Teacher.name) # 输出 广州
print(Teacher.post_code) # 输出 510660
# 通过类来访问Address类的类变量
print(Teacher.name)
teacher = Teacher()
teacher.print_info()
# 修改Teacher类的类变量
Teacher.name = '王军'
Teacher.post_code = '87654321'
teacher.print_info()
在这段代码中为Teacher类定义了两个类变量。
对于类变量而言,它们就是属于在类命名空间内定义的变量,因此程序不能直接访问这些变量,程序必须使用类名来调用类变量。不管是在全局范围内还是函数内访问这些类变量,都必须使用类名进行访问。
当程序第1次调用 Teacher对象的print_info()方法输出两个类变量时,将会输出这两个类变量的初始值。接下来程序通过 Teacher类修改了两个类变量的值,因此当程序第2次通过print_info方法输出两个类变量时,将会输出这两个类变量修改之后的值。
运行上面代码,将会看到如下输出结果:
李宁
12345678
87654321
实际上,Python完全允许使用对象来访问该对象所属类的类变量。看下面的程序:
示例代码:class_instance_access_classvar
class Country:
# 定义两个类变量
value1 = '中国'
value2 = 960
def print_info (self):
print('info方法中: ', self.value1)
print('info方法中: ', self.value2)
country = Country()
print(country.value1) # 中国
print(country.value2) # 960
country.print_info()
# 修改Country类的两个类变量
Country.value1 = '美国'
Country.value2 = 1234
# 调用print_info()方法
country.print_info()
这段代码的Country类中定义了两个类变量,接下来程序完全可以使用 Country对象来访问这两个类变量。
在这段代码的Country类的print_info方法中,程序使用self访问 Country类的类变量,此时self代表print_info方法的调用者,也就是 Country对象,因此这是合法的。
在主程序代码区,程序创建了 Country对象,并通过对象调用Country对象的value1和value2类变量,这也是合法的。
实际上,程序通过对象访问类变量,其本质还是通过类名在访问类变量。运行上面程序,将看到如下输出结果:
中国 960 info方法中: 中国 info方法中: 960 info方法中: 美国 info方法中: 1234
由于通过对象访问类变量的本质还是通过类名在访问,因此如果类变量发生了改变,当程序访问这些类变量时也会读到修改之后的值。例如为程序增加如下代码(接前面的代码),修改Country类的两个类变量。
Country.value1='韩国'
Country.value2 = 250
# 调用info()方法
country.print_info()
上面程序修改了Country类的两个类变量,然后通过对象调用print)info实例方法。运行上面代码,将看到如下输出结果。
info方法中: 韩国
info方法中: 250
从上面的输出结果可以看到,通过实例访问类变量的本质依然是通过类名在访问。需要说明的是,Python允许通过对象访问类变量,但如果程序通过对象尝试对类变量赋值,此时性质就变了。Python是动态语言,赋值语句往往意味着定义新变量。因此,如果程序通过对象对类变量赋值,其实不是对“类变量赋值”,而是定义新的实例变量。
看下面的代码:
实例代码:new_class_var.py
class Product:
# 定义两个类变量
name = 'iMac'
price = 11000
# 定义实例方法
def buy(self, name, price):
# 下面赋值语句不是对类变量赋值,而是定义新的实例变量
self.name = name
self.price = price
# 创建Product对象
product = Product()
product.buy('iPhone', 8000)
# 访问product的name和price实例变量
print(product.name) # iPhone
print(product.price) # 8000
# 访问Product的name和price类变量
print(Product.name) # iMac
print(Product.price) # 11000
Product.name = '类变量name'
Product.price = '类变量price'
# 访问product的name和price实例变量
print(product.name)
print(product.price)
product.name = '实例变量name'
product.price = '实例变量price'
print(Product.name)
print(Product.price)
在这段代码中通过实例对name和price变量赋值,看上去很像是对类变量赋值,但实际上不是,而是重新定义了两个实例变量(如果第1次调用该方法)。
在这段diamante中调用 Product对象的 buy()方法之后,访问 Product对象的name和price变量。由于该对象本身已有这两个实例变量,因此程序将会输出该对象的实例变量的值。接下来程序通过Product访问它的name和price类变量,此时才是真的访问类变量。
运行上面程序,将看到如下输出结果:
iPhone
11000
iPhone
类变量name
类变量price
如果程序通过类修改了两个类变量的值,程序中 Product的实例变量的值也不会受到任何影响。例如如下代码。
Product.name = '类变量name'
Product.price = '类变量price'
# 访问product的name和price实例变量
print(product.name)
print(product.price)
运行上面代码,可以看到如下输出结果
iPhone
8000
上面程序开始就修改了Product类中两个类变量的值,但这种修改对 Inventory对象的实例变量没有任何影响。同样,如果程序对一个对象的实例变量进行了修改,这种修改也不会影响类变量和其他对象的实例变量。例如如下代码。
product.name = '实例变量name'
product.price = '实例变量price'
print(Product.name)
print(Product.price)
运行上面代码,将会看到如下输出结果。
类变量name
类变量 price
从上面的输出结果来看,程序输出的依然是之前对类变量所赋的两个值。
2. 使用 property函数定义属性
如果为 Python类定义了getter和setter等访问器方法,可使用 property函数将它们定义成属性(相当于实例变量)。
property函数的语法格式如下:
property(fget=None, fset=None, fdel-None, doc=None)
从上面的语法格式可以看出,在使用 property函数时,可传入4个参数,分别代表 getter方法、 setter方法、del方法和doc,其中doc是一个文档字符串,用于说明该属性。当然,开发者调用 property也可传入0个(既不能读,也不能写的属性)、1个(只读属性)、2个(读写属性)、3个(读写属性,也可删除)和4个(读写属性,也可删除,包含文档说明)参数。
例如,如下程序定义了一个 Rectangle类,该类使用 property函数定义了一个size属性。
示例代码:rectangle.py
class Rectangle:
# 定义构造方法
def __init__(self, width, height):
self.width = width
self.height = height
# 定义set_size()函数
def set_size (self , size):
self.width, self.height = size
# 定义getsize()函数
def get_size (self):
return self.width, self.height
# 定义getsize()函数
def del_size (self):
self.width, self.height = 0, 0
# 使用property定义属性
size = property(get_size, set_size, del_size, '用于描述矩形大小的属性')
# 访问size属性的说明文档
print(Rectangle.size.__doc__)
# 通过内置的help()函数查看Rectangle.size的说明文档
help(Rectangle.size)
rect = Rectangle(5, 6)
# 访问rect的size属性
print(rect.size) # (5, 6)
# 对rect的size属性赋值
rect.size = 10, 12
# 访问rect的width、height实例变量
print(rect.width) # 10
print(rect.height) # 12
# 删除rect的size属性
del rect.size
# 访问rect的width、height实例变量
print(rect.width) # 0
print(rect.height) # 0
这段代码使用 property函数定义了一个size属性,在定义该属性时一共传入了4个参数,这意味着该属性可读、可写、可删除,也有说明文档。所以,该程序尝试对Rectangle对象的size属性进行读、写、删除操作,其实这种读、写、删除操作分别被委托给 get_size()、 set_size()和 del_size()方法来实现。
运行上面程序,将会看到如下输出结果:
用于描述矩形大小的属性
Help on property:
用于描述矩形大小的属性
(5, 6)
0
在使用 property函数定义属性时,也可根据需要只传入少量的参数。例如,如下代码使用property函数定义了一个读写属性,该属性不能删除。
class Person :
def __init__ (self, first, last):
self.first = first
self.last = last
def get_fullname(self):
return self.first + ',' + self.last
def set_fullname(self, fullname):
first_last = fullname.rsplit(',');
self.first = first_last[0]
self.last = first_last[1]
# 使用property()函数定义fullname属性,只传入2个参数
# 该属性是一个读写属性,但不能删除
fullname = property(get_fullname, set_fullname)
p = Person('子龙', '赵')
# 访问fullname属性
print(p.fullname)