Python面向对象程序设计进阶

Python面向对象程序设计进阶

继承自Object类的四个特殊方法

  1. __init__()

    在创建对象时(比如p = Shape.Point()),首先调用特殊方法__new__()来创建该对象,之后调用特殊方法__init__()对其进行初始化。

  2. __eq__()

    默认情况下,自定义类的所有实例都支持==,这种比较总是返回False——通过对特殊方法__eq__()进行重新实现,可以改变这一行为。如果我们实现了__eq__()但没有实现__ne__(),Python会自动提供__ne__()(不等于)以及不等于操作符(!=)。但是如果我们重新实现了__eq__(),实例就不再是可哈希运算的。我们可以将Point对象与那些恰好也包含x属性的对象进行比较 (这归功于Python的duck typing),但可能会产生奇怪的结果。

  3. __repr()__

    内置的repr()函数会对给定的对象调用__repr__()特殊方法,并返回相应结果。结果字符串有两种类型,一种可以使用内置的eval()函数进行评估,并生成一个与repr()调用对象等价的对象,如果不能这样做就返回另一种字符串。

    特殊应用: 使用eval()函数将字符串转化为具体的实例对象

    p = Shape.Point(3, 9)
    repr(p) # returns: 'Point(3, 9)'
    q = eval(p.__module__ + "." + repr(p))
    repr(q)
    

    如果使用import Shape,那么在使用eval()进行评估时,我们必须给出模块名。(如果使用其他的导入语句,比如from Shape import Point,就不需要这样做。)Python为每个对象提供了一些私有属性,__module__就是其中的一个,__module__是用于存放对象的模块名(这里是“Shape”)的一个字符串。

  4. __str__()

    内置的str()函数与repr()函数的工作机制类似,不同之处在于其调用的是对象的__str__()特殊方法,产生的结果也主要是为了便于理解,而不是为了传递给eval()函数。

使用特性进行属性存取控制

修饰器是一个函数,该函数以一个函数或方法为参数,并返回参数的“修饰后”版本,也就是对该函数或方法进行修改后的版本。property()修饰器函数是一个内置函数,至多可以接受4个参数:一个获取者函数,一个设置者函数,一个删除者函数以及一个docstring。使用@property的效果与仅使用一个参数(获取者函数)调用property()的效果是相同的。

property()修饰器函数是一个内置函数,至多可以接受4个参数:一个获取者函数,一个设置者函数,一个删除者函数以及一个docstring。使用@property的效果与仅使用一个参数(获取者函数)调用property()的效果是相同的。

@property
def radius(self):
    """The circle's radius

    >>> circle = Circle(-2)
    Traceback (most recent call last):
    ...
    AssertionError: radius must be nonzero and non-negative
    >>> circle = Circle(4)
    >>> circle.radius = -1
    Traceback (most recent call last):
    ...
    AssertionError: radius must be nonzero and non-negative
    >>> circle.radius = 6
    """
    return self.__radius

@radius.setter
def radius(self, radius):
    assert radius > 0, "radius must be nonzero and non-negative"
    self.__radius = radius  # accessing private properties

我们使用assert语句来确保radius的取值为非0以及非负值,并将该值存储于私有属性self.__radius中。

需要注意的是,获取者与设置者(如果需要,还有一个删除者)有同样的名称——用于对其进行区分的是修饰器,修饰器会适当地对其进行重命名,从而避免发生名冲突。每个创建的特性都包含getter、setter、deleter等属性,因此,在使用@property创建了radius特性之后,radius.getter、radius.setter以及radius.deleter等属性就都是可用的。radius.getter被设置为@property修饰器的获取者方法,其他两个属性由Python设置,以便其不进行任何操作(因此这两个属性不能写或删除),除非用作修饰器,这种情况下,他们实际上使用自己用于修饰的方法来替代了自身。

控制属性存取

在创建一个类而没有使用__slots__时,Python会在幕后为每个实例创建一个名为__dict__的私有字典,该字典存放每个实例的数据属性,这也是为什么我们可以从对象中移除属性或向其中添加属性。

如果对于某个对象,我们只需要访问其原始属性,而不需要添加或移除属性,那么可以创建不包含__dict__的类。这可以通过定义一个名为__slots__的类属性实现,其值是一个属性名元组。这样的类的每个对象都包含指定名称的属性,而没有__dict__,也不能向其中添加或移除属性。

class Point:
  __slots__ = ("x", "y")
  def __init__(self, x=0, y=0):
    self.x = x
    self.y = y

使用__dict__来存取属性

class Const:

  def __setattr__(self, name, value):
    if name in self.__dict__:
      raise ValueError("cannot change a const attribute")
    self.__dict__[name] = value

  def __delattr__(self, name):
    if name in self.__dict__:
      raise ValueError("cannot delete a const attribute")
    raise AttributeError("'{0}' object has no attribute '{1}'"
              .format(self.__class__.__name__, name))

使用该类,可以创建常数对象,比如,const = Const(),并可以在其上设置任意属性,比如const.limit = 591。设置了某个属性的值后,虽然可以像通常一样读取,但是如果尝试改变或删除该值,就会导致ValueError异常。我们没有重新实现__getattr__(),因为基类的object.__getattr__()方法可以完成这一任务——返回给定属性的值,如果没有指定的属性,就产生AttributeError异常。在__delattr__()方法中,对不存在的属性,我们模仿__getattr__()方法的错误消息,为此,我们必须获取类名以及不存在的属性的名称。该类可以工作,是因为我们正在使用对象的__dict__——而这也是基类的__getattr__()__setattr__()__delattr__()等方法所使用的。下表列出了用于属性存取的所有特殊方法:

特殊方法 使用 描述
__ delattr __ (self, name) del x.n 删除对象x的属性
__ dir __ (self) dir(x) 返回x的属性名列表
__ getattr __ (self, name) v = x.n 返回对象x的n属性值(如果没有直接找到)
__ getattribute __ (self, name) v = x.n 返回对象x的n属性
__ setattr __ (self, name, value) x.n = v 将对象x的n属性值设置为v

关于属性存取的特殊方法,上面展示了使用只读特性对其进行存取的例子:

@property
def width(self):
  return self.__width

这种方式易于编码,但如果有大量的只读特性,就会变得非常乏味。例如给定一个Image类:

class Image:
  def __init__(self, width, height, filename="",
         background="#FFFFFF"):
    self.filename = filename
    self.__background = background
    self.__data = {}
    self.__width = width
       self.__height = height
    self.__colors = {self.__background}

除文件名外,所有数据属性都是私有属性,下面给出一种不同的解决方案来提供对这些私有属性的统一访问方法:

def __getattr__(self, name):
  if name == "colors":
    return set(self.__colors)
  classname = self.__class__.__name__
  if name in frozenset({"background", "width", "height"}):
    return self.__dict__["_{classname}__{name}".format(
        **locals())]
  raise AttributeError("'{classname}' object has no "
      "attribute '{name}'".format(**locals()))

如果我们尝试存取某个对象的属性,但没有发现该属性,Python就将调用__getattr__()方法(假定实现了该方法,并且没有重新实现__getattribute__()),并以该属性名作为一个参数。如果没有对给定的属性进行处理,__getattr__()必须产生AttributeError异常。

比如,对语句image.colors,Python将寻找colors属性,失败后将调用Image.__ getattr__(image, "colors"),这里,__getattr__()方法处理“colors”属性名,并返回图像当前使用的颜色集的副本。

其他属性是固定的,因此可以安全地返回给调用者。对每个这种属性,我们可以使用一个单独的elif语句,类似于如下的形式:

elif name == "background":
  return self.__background

我们没有使用这种方法,而是使用了一种更紧凑的方法。我们知道,某对象的所有非特殊属性都存放在self.__dict__中,因此我们直接存取这些属性。注意,对私有属性(属性名以两个下划线开始),其名称在其中的存储形式为_className__attribute Name,因此,在从对象的私有字典中取回属性的值时,必须考虑这一点。

对查询私有属性时以及提供标准的AttributeError错误文本时所需要进行的名称操纵,我们需要知道我们所在类的名称。(也可以不是Image,因为该对象也可能是Image子类的一个实例。)每个对象都有一个__class__特殊属性,因此,在方法内部,self.__class__总是可用的,并可以通过__getattr__()安全地存取,而不需要冒着不必要的递归调用的风险。

注意有一个微妙的差别,使用__getattr__()self.__class__会对实例的类(可以是一个子类)的属性进行存取,而直接存取属性时使用的类就是属性定义所在的类。




posted @ 2021-03-22 16:32  &Yhao  阅读(85)  评论(0编辑  收藏  举报