在了解了类和对象的关系之后,来了解一下类和对象的属性,以及针对属性的操作.
建立类并且实例化2个对象:
class Data(): '''这是一个测试用数据类型''' default_type = 'int' default_value = 42 default_name = 'essense' def __init__(self, name, value, type): self.name = name self.value = value self.type = type def show_obj(self): print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type)) data1 = Data('first_data',629,'int') data2 = Data('second_data','709','str')
访问类的数据属性:
print(Data.default_name,id(Data.default_name)) print(data1.default_name,id(Data.default_name)) print(data2.default_name,id(Data.default_name))结果完全相同,类的数据属性可以被所有的对象共享,内存地址也是同一个,说明只存放一份,在类里.
# 显示一下方法的地址看看: print(Data.show_obj,id(Data.show_obj)) print(data1.show_obj,id(data1.show_obj)) print(data2.show_obj,id(data2.show_obj)) # <function Data.show_obj at 0x0000020DCB586158> 2258269397336 # <bound method Data.show_obj of <__main__.Data object at 0x0000020DCB57EB70>> 2258264734728 # <bound method Data.show_obj of <__main__.Data object at 0x0000020DCB57EBE0>> 2258264734728 # 类的函数属性就不是静态存放的,而是绑定给对象进行使用的.这里发现地址并不相同,这是因为python的id还不是真正的内存地址,但实际上,方法在内存中存放是肯定是只有一份的.
属性的命名,一般数据类型的属性,命名规则就是这是什么东西,而方法的命名一般是动作+客体的方式来命名.
类属性的增删改查
对象的属性一律用 对象名.属性名进行访问.如果属性是方法,就像执行函数一样.
# 修改类的数据属性: Data.default_name = 'jenny' print(data1.default_name) # jenny print(data2.default_name) # jenny
# 增加类的数据属性: Data.new_name = 'cony' print(data1.new_name) # cony print(data2.new_name) # cony # 成功向类内添加了一个数据属性
# 删除类的数据属性 del Data.new_name print(data1.new_name) print(data2.new_name) # AttributeError: 'Data' object has no attribute 'new_name'
直接修改类的数据属性会影响所有使用类的数据属性的对象,所以直接修改类的数据属性并不推荐使用,而且这么做也很容易会破坏类的结构.
# 增加方法 def get_data(self): return self.value Data.get = get_data print(data1.get()) # 629
增加方法可以自定义函数然后将给函数赋一个类的属性名.注意自定义的函数第一个位置参数不管名称是什么,赋给类的方法名之后,第一个位置会传入self,所以要注意参数的个数,必须显式的在最前边保留一个给self的位置.其他修改删除操作与数据属性相同.
对象=实例属性的增删改查
对象和类类似,可以增删改查数据属性,注意,对象的数据属性只能在自己的范围内被引用,类的空间里没有实例的数据属性.# 查看对象的属性 print(data1.value) # 629 print(data2.show_obj) # <bound method Data.show_obj of <__main__.Data object at 0x000001D5330DEB70>>可见类的方法和对象的方法显示不同,类就显示一个函数,而对象显示的是一个绑定的类方法.
# 增加数据属性 data1.new_value = 709 print(data1.new_value) #709 # 增加函数属性 def get_data(self): return self.value data1.get = get_data print(data1.get()) # 运行后报错TypeError: get_data() missing 1 required positional argument: 'self',由于增加的方法并不是通过class定义的,所以不会自动传入参数.这个时候的方法就相当于一个普通函数. # 一般不推荐直接给对象增加方法.
修改和删除对象的属性与类类似,不再详述.这里注意的是既然有属性字典,是不是可以通过直接操作属性字典的方式增加属性呢?
# 修改属性字典 data1.__dict__['name'] = '1st_data' print(data1.name) #1st_data # 可以成功操作,但不推荐使用此种方法修改属性.
HINT:如果有时候有很多变量要使用,又不想定义太多的全局变量,可以单独定义一个和若干个类,相当于一个作用域,需要保持独立的变量可以放入类内,类似C语言里的结构体.
类属性和对象属性的关系
在知道了基本的操作属性的基础上,对象和类的属性之间有什么关系呢?data1.default_name = 'cony' print(data1.default_name) # cony print(Data.default_name) # essense按照属性查找的优先顺序,有同名的属性,对象的属性会被优先查找到,没有才会返回类的属性,所以这个例子里对象的default_name和类的default_name是独立的.这也符合类的任职,即属于同一类的个体在具体属性上会有差异.这个寻找的过程最多不超出类的范围,所以即使设置同名全局变量也没有用途.
HINT:函数一定要与功能相关,不要把输入和输出与逻辑耦合在一起,涉及到输入输出的内容,一般不要写到逻辑函数内.输入输出一般是在外层去写.
HINT:注意下面的例子:
name_fa = 'minko' class Data(): '''这是一个测试用数据类型''' def __init__(self, name, value, type): self.name = name self.value = value self.type = type print(name_fa) # 这个地方会引用全局变量 def show_obj(self): print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type)) data1 = Data('first_data',629,'int') # 会显示minko,表明init函数的执行过程没有去类内寻找作用域.
name_fa = 'minko' class Data(): '''这是一个测试用数据类型''' name_fa = 'minko in class' #在类内增加一个同名参数 def __init__(self, name, value, type): self.name = name self.value = value self.type = type print(name_fa) def show_obj(self): print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type)) data1 = Data('first_data',629,'int') # 依然显示minko print(Data.name_fa) # 显示minko in class print(data1.name_fa) # 显示minko in class
这个例子说明了只要通过对象.属性的方法去访问属性,会去找类内的属性,也就是类的作用域内.在定义类的属性的时候采用普通方式写的变量,遵循普通的标准.所以init函数的country根本不从类内去寻找,即使是被写在了类定义之内和同名变量之后,类内的作用域对于直接写的变量没有任何影响.
静态属性 静态方法 类方法
之前是统称所有的数据属性和函数属性都叫属性,然后单独叫函数属性为方法.再来看其他描述属性的术语. 这里有一篇文章提到了属性的划分.静态属性
静态属性就是通过一个class 语法内建的装饰器,来讲函数属性加()调用改成直接通过函数名调用.显然,这对一点定需要传参数的方法是无效的.但对隐藏内部实现细节,取数据的时候很有效.# @property装饰器 class Data(): '''这是一个测试用数据类型''' name_fa = 'minko in class' def __init__(self, name, value, type): self.name = name self.value = value self.type = type @property def show_obj(self): print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type)) data1 = Data('first_data',629,'int') data1.show_obj() # 会报错TypeError: 'NoneType' object is not callable data1.show_obj #可以执行,这样看起来就很像数据属性.这也可以让类的使用方法变的统一.如果这里修改show_obj的方法为return 一个值,则使用起来和普通的数据属性没有什么区别,这就隐藏了其中的细节,用户在调用的时候就像是在取一个数据属性.
类方法
看如下示例:class Data(): '''这是一个测试用数据类型''' tag_name = 'minko in class' def __init__(self, name, value, type): self.name = name self.value = value self.type = type @property def show_obj(self): print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type)) def test(self): print(""From test is {}"".format(self.name)) # 如果用类本身调用类方法,就像上边的test方法一样 Data.test() # 会报错TypeError: test() missing 1 required positional argument: 'self'
说明漏了一个参数,实际上,这个参数只能传入对象,也就是说test这种形式的函数,在使用的时候一定是与对象绑定的,类是无法调用这个方法的.
但是很多情况下,类本身也需要展示一些信息或者进行一些动作,这个时候就不能采取加self参数的方法定义,而需要只和类绑定的方法.
这个时候就需要@classmethod装饰器,定义一个专门供类使用的方法.上述代码修改如下:
class Data(): '''这是一个测试用数据类型''' tag_name = 'minko in class' def __init__(self, name, value, type): self.name = name self.value = value self.type = type @property def show_obj(self): print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type)) @classmethod def test(cls,x): #写好@classmethod之后,一打括号会自动补一个cls参数,表示类 print(""From test is {} and {}"".format(cls.tag_name,x)) Data.test(10) #不需要传参数,自动传入当前的类. # From test is minko in class and 10
设置好类方法之后,会发现类方法里只能访问类的属性,无法访问实例的属性(不会自动传入实例self).
如果用实例来调用类方法,结果依然只和类有关.一般根本不用实例去调用类方法.
静态方法
直接看示例,加了@staticmethod装饰器:class Data(): '''这是一个测试用数据类型''' tag_name = 'minko in class'def __init__(self, name, value, type): self.name = name self.value = value self.type = type @property def show_obj(self): print( ""{}'s value is {}, type is {}."".format( self.name, self.value, self.type)) @classmethod def test(cls, x): print(""From test is {} and {}"".format(cls.tag_name, x)) @staticmethod def test_static(a,b,c): print('正在处理数据:',a,b,c)
data1 = Data('first_data', 629, 'int')
Data.test_static(1,2,3) # 正在处理数据 1 2 3
data1.test_static(1,2,3) # 正在处理数据 1 2 3
静态方法不跟类绑定也不和具体的实例绑定.由于默认参数没有self和cls,所以不会自动传入self和cls,所以和类与实例都没关系(自行编写然后将对象传入不属于此种情况).如果将@static去掉,则是普通方法,自动与对象绑定,如果用实例去调用,自动会传实例进去,这个函数就达不到原来的目的了.静态方法名义上由类管理,实际上可操作的内容与类和对象没有关系,相当于一个工具,无论类还是对象来调用,都只根据显式传入的参数进行处理,不会自动传入self或者cls(也就是不绑定).
概括总结:
静态属性是一种包装.
普通方法与对象绑定.
类方法只与类绑定.
静态方法不与类也不与对象绑定.
所谓绑定,可以理解为自行传参数.