Python3 特性(property)、静态方法(@staticmethod)和类方法(@classmethod)、

tech2022-12-19  85

目录

 

1. 特性

1.1 函数property

1.2 静态方法(@staticmethod)和类方法(@classmethod)

1.3 __getattr__、__setattr__等方法*


1. 特性

我们都知道在定义类时要定义用于获取或设置属性的方法(这些属性可能是私有的)。如果访问给定属性时必须采取特定的措施,那么像这样像这样封装状态变量(属性)就很重要。下面让我们看一下Rectangle类:

class Rectangle: def __init__(self): self.width = 0 self.height = 0 def set_size(self, size): self.width, self.height = size def get_size(self): return self.width, self.height

下面的实例演示了如何使用这个类:

>>> r = Rectangle() >>> r.width = 10 >>> r.height = 5 >>> r.get_size() (10, 5) >>> r.set_size((150, 100)) >>> r.width 150

get_size和set_size是假想属性size的存取方法,这个属性是一个由width和height组成的元组。这些代码并非错误,但存在缺陷。使用这个类时,无需关心它是如何实现的(封装)。如果有一天你想修改实现,让size成为真正的属性,而width和height是动态计算出来的,就需要提供用于访问width和height的存取方法,使用这个类的程序也必须重写。应让客户端代码(使用你所编写代码的代码)能够以同样的方式对待所有的属性。

那么如何解决这个问题呢?给所有的属性都提供存取方法吗?这当然并非不可能,但如果有大量简单的属性,这样做就不现实(而且有点傻),因为将需要编写大量这样的存取方法,除了获取或设置属性外什么都不做。所幸Python能够替你隐藏存取方法,让所有的属性看起来都一样。通过存取方法定义的属性通常称为特性(property)。

在Python中,实际上有两种创建特定的机制,我将重点介绍较新的那种——函数property,它只能用于新式类。随后,我将简单说明如何使用魔法方法来实现特性。

1.1 函数property

函数property使用起来很简单。如果你编写一个如上提及的Rectangle类。只需要再添加一行代码:

class Rectangle: def __init__ (self): self.width = 0 self.height = 0 def set_size(self, size): self.width, self.height = size def get_size(self): return self.width, self.height size = property(get_size, set_size)

在这个新版的Rectangle中,通过调用函数property并将存取方法作为参数(获取方法在前,设置方法在后)创建了一个特性,然后将名称size关联到这个特性。这样,你就能以同样的方式对待width、height和size,而无需关心它们是如何实现的。

>>> r = Rectangle() >>> r.width = 10 >>> r.height = 5 >>> r.size (10, 5) >>> r.size = 150, 100 >>> r.width 150

如你所见,属性size依然受制于get_size和set_size执行的计算,但看起来就像普通属性一样。

实际上,调用函数property时,还可不指定参数、指定一个参数、指定三个参数或指定四个参数。如果没有指定任何参数,创建的特性将既不可读也不可写。如果只指定一个参数(获取方法),创建的特性将是只读的。第三个参数是可选的,指定用于删除属性的方法(这个方法不接受任何参数)。第四个参数也是可选的,指定一个文档字符串。这些参数分别名为fget、fset、fdel和doc。如果你要创建一个只可写且带文档字符串的特性,可使用它们作为关键字参数来实现。

对于新式类,应使用特性而不是存取方法。

1.2 静态方法(@staticmethod)和类方法(@classmethod)

讨论就得特性实现方式之前,先来说说另外两种方式类似于新式特性的功能。静态方法和类方法是这样创建的:将它们分别包装在staticmethod和classmethod类的对象中。静态方法的定义中没有参数self,可直接通过类来调用。类方法的定义中包含类似于self的参数,通常被命名为cls。对于类方法,可直接通过类来调用,也可以通过对象直接调用,但参数cls将自动关联到类。下面是一个简单的示例:

class MyClass: def smeth(): print('This is a static method') smeth = staticmethod(smeth) def cmeth(cls): print('This is a class method of', cls) cmeth = classmethod(cmeth)

像这样手工包装和替换方法有点繁琐。在Python 2.4中,引入了一种名为装饰器的新语法,可用于像这样包装方法。(实际上,装饰器可用于包装任何可调用的对象,并且可用于方法和函数。)可指定一个或多个装饰器,为此可在方法(或函数)前面使用运算符@列出这些装饰器(指定了多个装饰器时,应用的顺序与列出的顺序相反)。

class MyClass: @staticmethod def smeth(): print('This is a static method') @classmethod def cmeth(cls): print('This is a class method of', cls)

定义这些方法后,就可像下面这样使用它们(无需实例化类):

>>> MyClass.smeth() This is a static method >>> MyClass.cmeth() This is a class method of <class '__main__.MyClass'>

在Python中,静态方法和类方法以前一直都不太重要,主要是因为从某种程度上说,总是可以使用函数或关联的方法替代它们,而且早期的Python版本并不支持它们。因此,虽然较新的代码没有大量使用它们,但它们确实有用武之地(如工厂函数),因此你或许应该考虑使用它们。

1.3 __getattr__、__setattr__等方法*

可以拦截对对象属性的所有访问企图,其用途之一是在旧类中实现特性(在旧式类中,函数property的行为不符合预期)。要在属性被访问时执行一段代码,必须使用一些魔法方法。下面的四个魔法方法提供了你需要的所有功能(在旧式类中,只需使用后面三个)。

__getattribute__(self, name): 在属性被访问时自动调用(只适用于新式类)。__getattr__(self, name): 在属性被访问而对象没有这样的属性时自动调用。__setattr__(self, name, value)__: 试图给属性赋值时自动调用。__delattr__(self, name)__: 试图删除属性时自动调用。

相比于函数property,这些魔法方法使用起来要棘手些(从某种程度上说,效率更低),但他们更有用,以为内你可以在这些方法中编写处理多个特征的代码。然而,在可以选择的情况,还是优先使用函数property。

下面使用魔法方法试下前面的Rectangle示例:

class Rectangle: def __init__ (self): self.width = 0 self.height = 0 def __setattr__(self, name, value): if name == 'size': self.width, self.height = value else: self. __dict__[name] = value def __getattr__(self, name): if name == 'size': return self.width, self.height else: raise AttributeError()

如你所见,这个版本需要处理额外的管理细节。对于这个代码示例,需要注意如下两点。

即便涉及的属性不是size,也将调用方法__setattr__。因此这个方法必须考虑如下两种情形:如果涉及的属性为size,就执行与以前一样的操作;否则就使用魔法属性__dict__.__dict__属性是一个字典,其中包含所有的实例属性。之所以使用它而不是执行常规属性赋值,是因为旨在避免再次调用__setattr__,进而导致无限循环。仅当没有找到指定的属性时,才会调用方法__getattr__。这意味如果指定的名称不适size,这个方法将引发AttributeError异常。这在要让类能够正确地支持hasattr和getattr等内置函数时很重要。如果指定的名称为size,就使用前一个实现中的表达式。

下面是对上述使用到__setattr__与__getattr__魔法方法的Rectangle类测试:

rec_a = Rectangle() rec_a.size = (2,3) rec_a.area = 6 print(rec_a.size) # (2, 3) print(rec_a.height) # 3 print(rec_a.area) # 6

 

最新回复(0)