User:Xyy23330121/Python/类的方法与类实例

来自维基学院


实例创建[编辑 | 编辑源代码]

我们之前已经提到过 __init__ 方法,这里将详细讲解创建实例的过程。

object.__new__(cls[, ...])[编辑 | 编辑源代码]

备注
如果读者在学习时,对于文档中不明确的部分查阅过 Python 内置模块的源代码,可以发现一些模块的源代码中,__new__方法所达成的事项(甚至连语句的表述)都完全可以用__init__方法达成。比如 decimal.Decimal[1]类的方法,其代码(删除掉注释部分)节选如下:
def __new__(cls, value="0", context=None):
    self = object.__new__(cls)
    ...
    if isinstance(value, str):
        ...
        self._sign = 1
        ...
        return self

可以看出,自定义的 __new__ 方法,它满足以下特征:

  1. 第一步就是用 object(所有类的父类)定义的 __new__ 方法,把结果赋值给变量 self。
  2. 之后是对 self 创建各种属性,由于使用的变量名称是 self,所以语法和 __init__(self) 中完全一致。
  3. 最后 return self

本方法是创建实例的第一步。它会在使用时创建一个新实例。

本方法默认为静态方法、类方法(而无需用装饰器),它会将所属的类自动传入第一个参数。

在自定义该方法时,一般会使用 super().__new__ 访问父类定义的 __new__ 方法,并修改父类 __new__ 方法返回的结果。

super(type, object_or_type=None)[编辑 | 编辑源代码]

此函数[2]会返回一个特殊对象。任何对该对象的方法都会被重定向到 type 的父类中定义的方法。比如以下示例:

class C:
    def method(self): print("C.method()")

class D(C):
    def method(self): print("D.method()")
    def usemethod(self):
        self.method()
        super().method()

obj = D()
obj.usemethod()

输出为:

D.method()
C.method()

从而,在子类中也可以调用已经被子类作用域所覆盖的、父类的方法。

该方法查找父类方法的顺序,和子类继承父类方法的顺序是一致的(但跳过了调用super()的子类本身)。

object.__init__(self[, ...])[编辑 | 编辑源代码]

如果 __new__ 方法在构造对象期间被发起调用,并返回了一个 cls 的实例。则新实例的 __init__ 方法将会被调用。调用时,会传入所有传入 __new__ 方法的参数。比如以下示例:

class test:
    def __new__(cls, value):
        print(value)
        return object.__new__(test)
    def __init__(self, value):
        print(value)

a = test("传入的参数")

该实例输出两遍 传入的参数,可见传入 __new__ 的 value 参数也被传入了 __init__ 的 value 参数。

__init__ 方法直接修改 __new__ 方法返回的实例,只要它返回除了None以外的任何值,就会导致错误。

实例属性[编辑 | 编辑源代码]

获取实例属性[编辑 | 编辑源代码]

object.__getattribute__(self, name)[编辑 | 编辑源代码]

此方法是调用实例属性时使用的方法。自定义此方法会影响对已有属性的读取。

该方法应该返回找到的属性值,或引发 AttributeError

由于只要使用 self.name 访问属性就会调用该方法。自定义该方法时,为了避免无限递归,应当注意不要使用 self.name 的方式来访问实例的属性,而是使用其基类的方法 object.__getattribute__(self, name) 来访问实例属性。

object.__getattr__(self, name)[编辑 | 编辑源代码]

__getattribute__ 方法引发 AttributeError 时,会调用此方法再次尝试获取属性。因此,自定义此方法不会影响对已有属性的读取。

该方法应该返回找到的属性值,或引发 AttributeError

由于本方法是在 __getattribute__ 方法之后调用,只要 __getattribute__ 方法能找到对应的属性,本方法是可以使用 self.name 来读取属性的。

设置实例属性[编辑 | 编辑源代码]

object.__setattr__(self, name, value)[编辑 | 编辑源代码]

此方法用于支持 self.name = value 的操作。和获取实例属性时不同,设置实例属性只有这一个函数。

类似 __getattribute__,在自定义该方法时,应当注意不要使用 self.name = value 的形式。如果有需要,就调用基类的方法 object.__setattr__(self, name, value)

object.__delattr__(self, name)[编辑 | 编辑源代码]

此方法用于支持 del self.name 的操作。

实例属性与方法列表[编辑 | 编辑源代码]

object.__dir__(self)[编辑 | 编辑源代码]

该方法用于支持 dir(self) 的操作。它会返回实例中、属性与方法名称的字符串组成的列表。

inspect 模块[编辑 | 编辑源代码]

inspect 模块列出了所有可获得的内置特殊属性。详情请参见 Python 文档。

描述器[编辑 | 编辑源代码]

我们之前学习“私有量”的时候提到过 property。property 是一种描述器。以下所示的方法仅当它作为类的属性时才会起作用。

这是因为获取、设置或删除类的属性时,类的 __getattribute__ 等方法会尝试调用描述器的方法。描述器如果不作为类的属性、而作为一般的变量使用时,不会有 __getattribute__ 等方法来调用描述器方法。

只要设置了下面方法中的一个,该类型就是一种描述器。

object.__get__(self, instance, owner=None)[编辑 | 编辑源代码]

其中,instance 是描述器属性所在的实例,而 owner 是 instance 所属的类型。

object.__set__(self, instance, value)[编辑 | 编辑源代码]

其中,instance 是描述器属性所在的实例,而 value 是要赋的值。

object.__delete__(self, instance)[编辑 | 编辑源代码]

其中,instance 是描述器属性所在的实例。

我们看以下示例:

class ReadOnly:
    def __init__(self, value):
        self.__value = value
    def __get__(self, instance, owner=None):
        print(instance.name, "and", owner.clsname)
        return self.__value
    def __set__(self, instance, value):
        raise AttributeError("readonly attribute")
    def __delete__(self, instance):
        raise AttributeError("readonly attribute")

class C:
    clsname = "Class C"
    def __init__(self, name):
        self.name = name
    x = ReadOnly(0)

c = C("Instance c")
output = c.x  #输出:Instance c and Class C
print(output) #输出:0
c.x = 2       #报错:AttributeError: readonly attribute

object.__objclass__[编辑 | 编辑源代码]

在创建描述器时,还可以添加一个可选的属性。该属性应当设置为描述器属性所在的实例类型。该属性会被 inspect 模块所使用,有助于动态类属性的运行时内省。

子类创建[编辑 | 编辑源代码]

为了代码的易于扩展,有时需要自定义子类创建的行为。

object.__init_subclass__(cls[, ...])[编辑 | 编辑源代码]

在创建子类时会调用父类的此方法,其中, cls 参数会被自动传入新的子类。

这里的参数是在创建类的时候,通过“关键词参数”方法传入的,比如:

class test:
    def __init_subclass__(cls, arg=0):
        print(arg)
        pass

class test1(test, arg="传入的参数"):
    pass

以上示例的输出是传入的参数。如果使用位置参数,则会与“父类的继承”产生混淆。

此方法是子类被创建完毕后,对子类作修改。而非从头创建子类。此方法返回任何值都会u报错。

示例[编辑 | 编辑源代码]

random.Random[3]类就有此方法,其代码节选如下:

def __init_subclass__(cls, /, **kwargs):
    for c in cls.__mro__:
        if '_randbelow' in c.__dict__:
            # just inherit it
            break
        if 'getrandbits' in c.__dict__:
            cls._randbelow = cls._randbelow_with_getrandbits
            break
        if 'random' in c.__dict__:
            cls._randbelow = cls._randbelow_without_getrandbits
            break

random.Random 类对于随机数有不同的处理。有的方法使用随机整数以规避浮点误差,而有的方法使用随机浮点数。其中,使用随机整数的方法调用的是 _randbelow 方法。

此处 __init_subclass__ 的处理,在子类定义了 getrandbits (随机整数方法)的情况下,让 _randbelow 变为 _randbelow_with_getrandbits 方法,再在 _randbelow_with_getrandbits 中调用 getrandbits 方法。从而实现了随机整数方法的调用。

如果子类没有定义 getrandbits,则查询是否定义了 random (随机浮点数方法)。如果有,让 _randbelow 变为 _randbelow_without_getrandbits 方法,再在 _randbelow_without_getrandbits 方法中调用 random 方法,并把结果转化为整数。

如果子类两者都没有定义,则认为子类没有修改随机数生成器,此时按继承顺序继续查找父类的方法。

通过这样的处理,子类可以只定义 random 方法就修改 random.Random 中所有类型随机数的生成,也可以附加定义 getrandbits 来为整数作进一步优化。增加了代码的可扩展性。

object.__set_name__(self, owner, name)[编辑 | 编辑源代码]

在创建类的同时创建属性时会调用此方法。我们看以下示例:

class AttrTest:
    def __set_name__(self, owner, name):
        print(self, owner, name)
    def __str__(self):
        return "AttrTest 的实例"
    __repr__ = __str__

attr = AttrTest()

class test:
    x = attr     #在创建类的同时创建属性

test.y = attr    #在创建类之后,再创建属性
attr.__set_name__(test,"z") #手动调用该方法

print(test.x, test.y)
print(test.z)

输出为:

AttrTest 的实例 <class '__main__.test'> x
AttrTest 的实例 <class '__main__.test'> z
AttrTest 的实例 AttrTest 的实例
Traceback (most recent call last):
  File "...", ..., in <module>
    print(test.z)
          ^^^^^^
AttributeError: type object 'test' has no attribute 'z'

參考文獻[编辑 | 编辑源代码]