python中属性_Python中的属性管理

2 特性

在Python中,除了重载操作符外来管理类实例属性的访问控制外,也可以使用特性(property)。

2.1 property类

在没有讲解使用特性来管理类实例的属性访问控制时,我们先来探讨一下“什么是特性”。

其实,在Python中,隐式的存在一种类型,它就是property类,可以把它看成是int类型,并可以使用它来定义变量(在面向对象里,一般称为

“对象”,也就是类实例)。而用来管理类实例的属性访问控制的“特性”正是使用这个property类。该类可以管理自定义类的实例的属性访问控制。

在property类中,有三个成员方法和三个装饰器函数。三个成员方法分别是:fget、fset、fdel,它们分别用来管理属性访问;三个装饰器函数分别是:getter、setter、deleter,它们分别用来把三个同名的类方法装饰成property。其中,fget

方法用来管理类实例属性的获取,fset方法用来管理类实例属性的赋值,fdel方法用来管理类实例属性的删除;getter装饰器把一个自定义类方法装

饰成fget操作,setter装饰器把一个自定义类方法装饰成fset操作,deleter装饰器把一个自定义类方法装饰成fdel操作。

以上的说明,笔者有点把fget、fset、fdel的功能说死了,其它这三个函数中不仅仅可以分别管理自定义类实例属性的获取、赋值、删除,它们还可以

做其它的任何事情,就像普通函数一样(普通函数能完成什么样的功能,它们也都可以),甚至不做它们的本职工作也行(不过,我们一般都会完成自定义类实例属

性的获取、赋值、删除等操作,有时,可能还会完成一些其它额外的工作)。其实,不管这三个函数完成什么的功能,只要在获取自定义类实例的属性时就会自动调

用fget成员方法,给自定义类实例的属性赋值时就会自动调用fset成员方法,在删除自定义类实例的属性时就会自动调用fdel成员方法。

2.2 property类的使用

根据property类中的成员类别,笔者把有关“特性”的使用分成两部分:一般成员方法和装饰器方法。

2.2.1 使用一般成员方法

我们上面提到“property类是隐式的”,所以,我们不能直接使用这个property类。那怎么办呢?没关系,Python为我们提供了一个标准的

内置函数property,该函数通过我们传递给它的参数自动帮我们创建一个property类实例,并将该类实例返回。所以,我要想创建

property类实例,就需要调用标准的内置函数property。

2.2.1.1 property函数

property函数的原型:property(fget=None, fset=None, fdel=None, doc=None)

其中,前三个参数分别是自定义类中的方法名,property函数会根据这三个参数自动创建property类中的三个相应的方法。第四个参数文档(也就

是字符串),把它作为property类的说明文档;我们知道,在Python中,可以为一个类或函数添加一个文档说明;当doc参数为None

时,property函数会提取第一个参数fget的说明文档,如果fget参数的说明文档也为None,那么doc参数的值就为None。

property函数返回一个property类型的实例。

2.2.1.2 property的使用方法

关于“特性”的使用方法,是在自定义类中通过定义一个类属性而完成的;虽然property类实例是自定义类的类属性,但对property类实例的操作

将作用于自定义类的实例的属性上。其实,自定义类的实例的属性的信息仍然是存储于自定义类的实例中,而“特性”(即property类实例)只不过在管理

如何对它进行访问(获取、赋值、删除)。

有些读者可能会对上述的“自定义类的实例的属性”有所疑惑。就像开头所述的,其实它就相当于C++中类的实例的成员。

由property创建的属性(也就是管理自定义类实例的“特性”)是属于自定义类的,而不是属于自定义类实例的;但是,它却是管理自定义类实例的属性的,而不管理自定义类的类属性。下面,我们具体地再把“特性”的执行流程阐述一下。

由以上,我们可以知道,要用“特性”来管理自定义类的实例的属性访问控制,必须有两个属性,一个是自定义类的类属性(也就是“特性”,porperty类

的实例),另一个自定义类的实例的属性。既然“特性”是自定义类的属性,那么可以我们可以通过自定义类对象(因为类型也是一个对象,所以自定义类自然也是

一个对象,我们称为自定义类对象)来访问“特性”;又由于“特性”是property类的实例,因此,此时我们得到的是一个实例或者说是对象,而它有

fget、fset、fdel三个成员方法,那么我们可以来间接的访问这些方法,而我们知道这三个成员方法是用来管理自定义类的实例的属性访问控制,此时

调用这些方法将会间接地管理自定义类的实例的属性访问控制。因此,这种通过自定义类对象来调用其成员方法来控制自定义类的实例的属性访问控制是一种间接性

的,我们可以这么做,但实际应用中,我们是不会这么用,这里只是说明一下,让读者对“特性”有个更深层次的了解;在实际应用中使用的是下面的一种方法。

对于面向对象,我们知道,类的实例可以访问类的属性(在C++中就是类中的静态成员)。所以,在Python中,我们也可以通过自定义类的实例来访问“特

性”。但是,此时的访问却与一般的类实例访问类属性的行为有些不同:自定义类实例访问到的“特性”(这里笔者没有说“类属性”,就是为了从字面上与此区

别)不是property类实例本身,而是进行自动的调用转,也就是说,Python会根据属性的访问类别(获取、赋值、删除)自动调用相应的fget、

fset、fdel成员方法并得出结果,这也正是“特性”的本质所在——“特性”能够通过自定义类实例去自动管理自定义类实例的属性访问控制(其实还可以

做些其它的工作,甚至不管理自定义类实例的属性访问控制——请参见2.1中对fget、fset、fdel的描述说明),我们一般称此为“特性拦截了属性

的访问”。

我们介绍完了“特性”(即自定义类的类属性——property类实例)的访问,我们再看看自定义类实例的属性访问控制。既然是自定义类实例的属性,那么就只能通过自定义类实例来访问。

当我们通过自定义类实例来访问其自身的属性时,其访问控制是由__getattr__、__getattribute__、__setattr__和

__delattr__四个重载操作符来完成的,与“特性”(以及后面讲到的“描述符”)无关,并且其访问方式和正常的访问一样。

至此,我们已经基本上讲完通过成员方法来使用“特性”了。最后,我们要注意两点:第一,一个“特性”只能控制自定义类的一个属性的访问控制,如果想控制多个,就必须定义多个“特性”;第二,在通过“特性”来控制自定义类实例的属性访问控制时,“特性”的名字不能和它所控制的自定义类实例的属性的名字相同。为

什么不能一样呢?我们试想,当一样时,如果我们在“特性”的成员方法中再次访问了该属性,它就会触发属性的获取、赋值或删除操作,那么由于“特性”会拦截

该访问控制,所以就会再次引发该成员方法的调用,这样,就会导致递归调用而进入死循环,直到内存耗尽。当然,如果你不会在“特性”的成员方法中再次访问该

属性,就不会导致递归调用。

2.2.1.3 例子

现在,我们已讲解完“特性”的理论知识,再看个例子或许就更清楚了。

注:此例适用于Python2.6中的新式类和Python3中的所有类(因为Python3中的类都是新式类),不适用于Python2.6旧式类。在下面例子中,“#”后面的注释是相应的语言的输出结果,除了说明不是注释外。

class A:

def __init__(self, value):

print(“init …”)

self._x = 0

def getx(self):

print(“getx …”)

return self._x

def setx(self, value):

print(“setx …”)

self._x = value

def delx(self):

print(“delx …”)

del self._x

x = property(getx, setx, delx)

a = A() # init ...

a.x # getx ...

# 0

a.x = 123 # setx ...

a.x # getx ...

# 123

a._x # 123

a._x = 456 # 这个注释不是输出,它不会输出任何东西

a.x # getx ...

# 456

del a.x # delx ...

del a._x # 这个注释不是输出,这句语句将抛出异常

a.x = 789 # setx ...

del a.x # delx ...

a._x = 100 # 这个注释不是输出,它不会输出任何东西

a.x # 100

上述代码讲解:

我们定义了一个自定义类A,然后定义了三个成员方法getx、setx、delx,这三个成员方法分别作为参数传递给property内置函数,通过

property内置函数隐式地创建了一个property类实例,并把该实例作为返回值传递给了自定义类A的成员x(即类属性),换句话说,就是x是一

个property类实例,其中getx、setx、delx三个成员方法将成为property类的三个成员方法(fget、fset、fdel)。我

们就是通过这个x类属性来管理_x实例属性的访问控制。

然后我们通过“a = A(

)”语句定义了一个A类的实例a,它将调用构造函数__init__。a.x将调用property类的fget成员方法(即A类中的getx方法,下面

直接简单地说“setx”和“delx”,而不再提“fset”和“fdel”);“a.x =

123”语句将调用“setx”方法;“a.x”语句将调用“getx”方法;“a._x”语句将调用A类中的默认的属性获取方法(你可以重载

__getattr__和__getattribute__操作符来重载默认的属性获取方法,参见上一章节的“重载操作符”);“a._x

= 456”语句将调用A类中的默认的属性赋值方法(你可以重载__setattr__操作符);“del

a.x”语句将调用“delx”方法;“del

a._x”语句将调用A类中的默认的属性删除方法(你可以重载__delattr__操作符)。当执行了“del a.x”语句后,再执行“del

a._x”语句时,将引发异常,原因是:“del a.x”语句调用“delx”方法后,a的属性_x已经被删除、不存在了,如果再执行“del

a._x”语句,就要引发“与属性不存在性有关的”异常了(删除一个不存在的属性)。当接着执行语句“a.x

= 789”,将会调用“setx”方法;“del a.x”语句将调用“delx”方法方法;“a._x =

100”语句将调用A类中默认的属性赋值方法;“a.x”语句将调用“getx”方法。

2.2.2 使用装饰器方法

我们可以使用装饰器来使自定义类的方法成为“特性”,装饰器的接口比较简洁。

property类有三个装饰器方法(getter、setter、deleter),按照装饰器的一般使用方法,如果我们把一个方法成为

property类的fget方法,就需要调用property的getter装饰器方法。但是,上面我们已经知道,property类是隐式的,所以,

我们不能直接使用getter装饰器(就是想使用也没有办法使用——因为我们不能直接获取到property类),所以,我们还需要借助property

内置函数。

2.2.2.1 使用方法

property不仅是可以作为内置函数来使用,而且还可以作为装饰器来使用。当我们把property内置函数当作装饰器来使用时,它将会隐式的创建一

个property类实例,并调用该实例的getter装饰器,使得property装饰器所装饰的方法成为property类的fget成员方法。最

终,由property将重新绑定它所装饰的方法,使它所装饰的方法名重新绑定到新创建的property类实例,而它所装饰的方法成为该

property类实例的fget方法。

假设property所装饰的方法的名字为name,那么,从此以后,我们可以使用name.setter和name.deleter来装饰其它的方法,

使其成为fset和fdel成员方法,因为,此时的name被property装饰器重新绑定到新创建的property类实例,也就是说,name是一

个property类实例。注:在用name.setter和name.deleter装饰其它方法时,其它方法必须是name。总之,用setter和

deleter装饰器修饰的方法的名字必须和property装饰器修饰的方法的名字相同。

2.2.2.2 例子

注:此例适用于Python2.6中的新式类和Python3中的所有类(因为Python3中的类都是新式类),不适用于Python2.6旧式类。

class Person:

def __init__(self, a_name):

self._name = a_name

@property

def name(self):

return self._name

@name.setter

def name(self, value):

self._name = value

@name.deleter

def name(self):

del self._name

person = Person(“student”)

person.name

person.name = “teacher”

del person.name

person._name # 会抛出异常

person._name = “123”

del person._name

在类Person中,property装饰器将隐式地创建一个property类实例,然后把它所装饰的方法(第一个name方法)重新绑定到该

property类实例,并把第一个name方法变成该property类实例的fget方法。接着分别用name.setter和

name.deleter装饰器分别第二个、第三个name方法变成name的fset和fdel方法。在类Person定义结束后,我们定义了一个变量

person,然后对person对象中name属性的访问控制就分别调用相应的三个name方法。实际上,我们对name属性的操作,最终会转移到类

Person实例的_name属性身上。

2.3 小结

特性是在自定义类的成员中创建一个property类的实例(该property类实例是自定义类的属性,不是自定义类实例的属性),然后通过该

property类实例来管理自定义类实例中的某个特定的属性。该property类实例是自定义类中的一个属性(我们在这称之为“类属性”),当通过自

定义类对象访问它时,是直接访问该类属性;当通过自定义类实例访问它时,该类属性的三个相应的属性管理方法(fget,fset,fdel)将会自动调

用。在这三个属性管理方法中,我们可以像其他普通函数或类方法一样做任何事情,不过,我们一般是把对该类属性的访问操作(获取、赋值、删除)转移到自定义

类实例的属性身上。

在访问通过特性创建的类的属性时,装饰特性的三个方法会自动调用。

特性是在类的属性(注:该类属性也是一个对象,因此也是可以有属性的)中创建一个属性,对该属性的所有操作(获取、赋值、删除)都将作用在类实例的属性身上。特性是用来管理类实例的属性访问的,而不是管理类本身的属性访问的(即,特性不会作用在类属性上,只会作用在类实例的属性上)。当

然,我们也可以跳过特性而直接访问类实例的属性,此时,访问操作(获取、赋值、删除)与特性无关,只受__getattr__、

__getattribute__、__setattr__、__delattr__四个重载操作符的影响。当使特性时,其属性管理只受特性(的三个方

法)所影响,不受那些重载操作符的影响。

一旦一个自定义类使用了“特性”来管理自定义类实例某个属性,那么凡是通过“特性”来对该属性的所有访问(获取、赋值、删除)都有“特性”的三个成员方法(fget、fset、fdel)来控制。这里隐含着一个事实,前面没有说明,这里必须说明一下:如果“特性”中的三个成员方法有任何一个没有被定义,那么,就不能通过“特性”来进行相应的操作;如果进行了这样的操作,那么将会引发AttributeError异常(Python找不到相应的方法来调用)。比如:如果没有定义fset成员方法,那么就不能通过“特性”来对被该“特性”所管理的属性进行赋值操作;否则,将引发AttributeError异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值