跳转到内容

用户: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以外的任何值,就会导致错误。

实例属性

[编辑 | 编辑源代码]

获取实例属性

[编辑 | 编辑源代码]

获取实例属性时,调用的是实例的 __getattribute__() 方法和 __getattr__() 方法。

object.__getattribute__(self, name)

[编辑 | 编辑源代码]

在获取实例属性时,首先调用的是此方法。此方法返回的结果就是获取到的属性。

如果未在类型中自定义此方法,则此方法会自动返回实例的内部属性,或者报错 AttributeError

如果此方法报错 AttributeError,Python 不会直接报错,而是尝试调用 __getattr__() 方法来获取属性。

此方法传入的参数 name 是属性名称的字符串。这可以从下面的测试代码看出来:

class C:
    def __getattribute__(self, name):
        print(name, type(name))  #在获取实例属性时,打印传入的参数内容和参数类型
        return 0

obj = C()
print(obj.test_attribute_name)  #获取实例属性,打印获取到的属性

输出为:

test_attribute_name <class 'str'>
0

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

注意:避免无穷递归
[编辑 | 编辑源代码]

需要注意的是,该方法会接管一切获取属性的行为。比如:

class C:
    testmsg = "这是测试信息"
    def __getattribute__(self, name):
        return "读取属性的操作被接管"

obj = C()
print(obj.testmsg)

输出为:

读取属性的操作被接管

因此,以下的代码会导致无穷递归:

class C:
    testmsg = "这是测试信息"
    def __getattribute__(self, name):
        if name == "testmsg":
            return self.testmsg  #在这个情况下,等同于 return self.__getattribute__("testmsg")

obj = C()
print(obj.testmsg)

如果要在这个方法中使用实例的属性,应该改为使用:

class C:
    testmsg = "这是测试信息"
    def __getattribute__(self, name):
        if name == "testmsg":
            #通过 object 类型的读取属性方法,来获取自身的属性。
            return object.__getattribute__(self, "testmsg")

obj = C()
print(obj.testmsg)

object.__getattr__(self, name)

[编辑 | 编辑源代码]

__getattribute__() 方法引发 AttributeError 时,会调用此方法再次尝试获取属性。

__getattribute__() 方法一样,该方法应该返回找到的属性值,或引发 AttributeError。当没有自定义此方法时,此方法默认总会抛出一个 AttributeError

注意:(再次)避免无穷递归
[编辑 | 编辑源代码]

由于该方法在 __getattribute__() 报错之后才调用。只要 __getattribute__ 方法能找到对应的属性,本方法是可以使用类似 self.name 的方式来读取属性的。比如:

class C:
    testmsg = "这是测试信息"
    def __getattr__(self, name):
        return self.testmsg

obj = C()
print(obj.testmsg)

因此,当无需接管已有属性的读取时,不去自定义 __getattribute__() 方法,而是自定义此方法是相对安全的。

但是,如果 __getattribute__() 方法找不到对应的属性,本方法同样可以引发无穷递归。

设置实例属性

[编辑 | 编辑源代码]

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'

参考文献

[编辑 | 编辑源代码]