什么是属性
在Python中,简单来说,凡是存在于类里的,不论是数据、方法还是类,都叫做属性。例如我们定义一个类:
classA:
def__init__(self,a):
self.a=a
self.b=1
def__str__(self):
returnstr(
self.a+self.b
)
defadd(self):
returnself.a+self.b
这里,类A有5个属性,分别是两个数值和,两个特殊方法和以及一个普通方法:。注意到这些属性都是属于类A的实例,只能通过实例利用点运算符来访问:
a=A(2)
print(a.a)
# 2
print(a.add)
# >
# 尝试类访问:
print(A.a)
# AttributeError: type object 'A' has no attribute 'a'
通过类是无法访问实例属性的。那么,如何定义类的属性和方法呢?很简单,去掉:
classB:
a=2
defb():
print(B.a)
这里为类定义了两个类的属性和。类的属性的访问由的方式进行:
print(B.a)
# 2
B.b()
# 2
所有类的实例共享同一份类的属性:
b1=B()
b2=B()
print(b1.a)
# 2
B.a=10
print(b2.a)
# 10
可能有人会好奇,能不能通过实例来修改类的属性呢?试一下:
b2.a=5
print(b1.a)
# 10
失败了。为啥呢?因为是给这个实例定义了一个属性它的值为,而不是修改。
组合
在面向对象编程过程中,除去继承(inheritance)之外,另一类比较重要的委托(delegation)方式是组合(composition)。所谓组合,即将一个类的对象(设为类A)做为另一个类(设为类B)的属性使用。这样,可以使用所提供的方法,而不必完全继承。例如:
classB:
defp(self,obj):
print(obj)
# 继承方式:
classA(B):
pass
a=A()
a.p('hi')
# hi
# 组合方式:
classA:
def__init__(self):
self.b=B()
a=A()
a.b.p('hi')
面向对象设计的一大方式是委托,即一个类只负责实现它自己相关的功能,其他功能委托给其他类实现。这样能够更好地拆解问题。委托的方式有两类:组合和继承。它们各有优缺点,都是OO中不可或缺的机制。关于组合和继承,可以这样理解:动物和猫的关系是继承关系,因为猫包含了动物的所有特性;而猫腿、猫尾、猫须等部位和猫的关系就是组合关系,它们是猫的组成部分,分管了猫的不同功能。
(端午和妞妞图片来自微博@回忆专用小马甲,已通知博主,如涉及侵权问题立即删除)知道了组合这种方式,还有一个问题。在上面例子里,想要使用的方法,还需要显式地访问再调用。好像太麻烦了一点,能不能让直接做为的方法来调用呢?例如。这样接口更加统一,使用者也不必关心内部实现机制究竟是Inheritance还是Composition。想要解决这个问题,需要对Python的属性访问机制有个比较深刻的认识。我们都知道属性访问采用点运算符,找不到这个属性就会抛出异常。Python提供了一个机制,允许我们在抛出异常前再尝试调用方法再寻找一次。这个特殊方法接收一个参数作为属性名,并返回该属性。
__getattr__
我们先写一个打印函数来看看它什么时候调用的:
classA:
def__getattr__(self,name):
print('Here')
returnNone
a=A()
# 访问一个不存在的属性
b=a.a
# Here
print(b)
# None
我们看到首先访问一个不存在的属性并没有报错;其次,访问这个属性的过程中调用了方法。所以,我们可以利用来控制我们的属性访问机制,从而实现上面提到的那个问题:
classB:
defp(self,obj):
print(obj)
classA:
def__init__(self):
self.b=B()
def__getattr__(self,name):
returngetattr(self.b,name)
a=A()
a.p('hi')
# hi
这样,组合来的类完美融入了类中。注意到里面用到了一个方法,这个方法和点运算符的作用一样,只不过它是函数的形式,因而属性名可以传递一个变量。熟悉JavaScript的朋友都知道,在js中,对象的属性访问非常方便:
// JavaScript
constobj={
a:'a',
b:2,
c:function() {},
};
console.log(obj.a);
// a
obj.d=3;
console.log(obj);
// { a: 'a', b: 2, c: [Function: c], d: 3 }
而在Python中,字典项的访问不得不使用中括号加字符串完成:
# Python
obj= {
'a':'a',
'b':2,
'c':lambda_:_,
}
print(obj['a'])
# a
obj['d'] =3
print(obj)
# {'d': 3, 'b': 2, 'c':
# at 0x000002D94A3EBF28>,
# 'a': 'a'}
print(obj.b)
# AttributeError: 'dict'
# object has no attribute 'b'
有了刚刚的方法,我们可以改写一下Python的字典,让他能够支持点运算符访问:
classDotDict(dict):
def__getattr__(self,name):
returnself.__getitem__(name)
obj=DotDict({
'a':'a',
'b':2,
'c':lambda_:_,
})
print(obj.a)
# a
这里面我们使用了另一个特殊方法,下面介绍一下它:
__getitem__
同样接收一个参数,只不过它返回的是以索引方式(中括号)访问的属性。例如,序列对象值的访问,背后的方法就是。同样,字典项的访问也是它来实现的。所以在上面例子里,我们仅仅是把的结果通过方法返回,即实现了点运算符访问字典项的功能。来看一个例子:
# 只能访问序列的偶数项
classEvenList:
def__init__(self,lst):
self.lst=lst
def__getitem__(self,key):
returnself.lst[2*key]
l=EvenList([xforxinrange(10)])
print(l[2])
# 4
Ok,访问的问题解决了,修改和删除怎么办呢?
# 上面的DotDict实例obj
obj.e='e'
print(obj)
# {'b': 2, 'a': 'a', 'c': at 0x000001A25A163730>}
根本没有。怎么办呢?Python提供了和访问配套的修改和删除操作,只要把对应的换成和即可:
def__setattr__(self,name,val):
self.__setitem__(name,val)
def__delattr__(self,name):
self.__delitem__(name)
DotDict.__setattr__=__setattr__
DotDict.__delattr__=__delattr__
obj.e='e'
delobj.c
print(obj)
# {'b': 2, 'a': 'a', 'e': 'e'}
友情链接: https://vonalex.github.io/
欢迎关注 它不只是Python
领取专属 10元无门槛券
私享最新 技术干货