跳转到内容

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

实例属性

[编辑 | 编辑源代码]

获取实例属性

[编辑 | 编辑源代码]

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

具体来讲,下面两者(在最终没报错的情况下)是等价的:

a = ins.att
try:
    a = type(ins).__getattribute__(ins, 'att')
except AttributeError:
    a = type(ins).__getattr__(ins, 'att')
详细说明

我们希望确认过程中调用的到底是ins.__getattr__('att')还是type(ins).__getattr__(ins, 'att'),因此做以下实验:

class test:
    def __getattr__(self, name):
        print('__getattr__', end=' ')
        return 0
    
    def __getattribute__(self, name):
        print('__getattribute__', end=' ')
        return super().__getattribute__(name)

ins = test()

print('以下方法被依次调用:', end='')
ins.att
print()  #输出:以下方法被依次调用:__getattribute__ __getattr__ 

print('以下方法被依次调用:', end='')
try:
    ins.__getattribute__('att')
except AttributeError:
    ins.__getattr__('att')
print()  #输出:以下方法被依次调用:__getattribute__ __getattribute__ __getattribute__ __getattr__ 

print('以下方法被依次调用:', end='')
try:
    type(ins).__getattribute__(ins, 'att')
except AttributeError:
    type(ins).__getattr__(ins, 'att')
print()  #输出:以下方法被依次调用:__getattribute__ __getattr__

不难发现,第一组和第三组的行为是一致的。而第二组的逻辑则类似于:

try:
    _func = type(ins).__getattribute__(ins, '__getattribute__')
    _func('att')
except AttributeError:
    _func = type(ins).__getattribute__(ins, '__getattr__')
    _func('att')

默认情况

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

此方法默认会自动返回实例的内部属性,或者报错 AttributeError

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

此方法默认会直接报错 AttributeError

注意:避免无穷递归

[编辑 | 编辑源代码]

在自定义上述两个方法时,一定要注意避免无限递归。

编程要点

这里给出几个避免出错的要点:

1. 尽可能不要修改 __getattribute__ 对已有属性的行为
如果对这些行为进行修改,则该对象的一切属性你都将读不到。

class test:
    def __getattribute__(self, name):
        return 0

a = test()
print(a.__dict__) #输出:0

最好使用形如下面的方法:

class test:
    def __getattribute__(self, name):
        try:
            # 这样就不会改变对已有属性的行为
            return super().__getattribute__(name)
        except AttributeError:
            return 0

特别的。如果像上面一样、想要实现的功能只靠自定义__getattr__也能实现,则建议读者不要自定义 __getattribute__

class test:
    def __getattr__(self, name):
        return 0

2. 不要在 __getattribute__ 中用常规方法获取任何属性内容
如果用常规方法获取属性内容,则会造成死循环。

class test:
    default = 0
    def __getattribute__(self, name):
        try:
            # 这样就不会改变对已有属性的行为
            return super().__getattribute__(name)
        except AttributeError:
            return self.default

a = test()
print(a.not_defined) #报错:无限循环

建议改用:

class test:
    default = 0
    def __getattribute__(self, name):
        try:
            # 这样就不会改变对已有属性的行为
            return super().__getattribute__(name)
        except AttributeError:
            return super().__getattribute__('default')

3. 尽可能不要在 __getattr__ 中用常规方法获取任何不能通过 __getattribute__ 获取的属性内容

原因和上面类似。但总体来讲,自定义 __getattr__ 总是更安全的。比如下面的代码不会报错:

class test:
    default = 0
    def __getattr__(self, name):
        if name == 'a': return self.b     # 最终会调用 __getattr__(self, 'b')
        if name == 'b': return self.default # 在 __getattribute__ 时就输出了结果,所以不会报错
        raise AttributeError

设置实例属性

[编辑 | 编辑源代码]

获取实例属性 类似。在设置实例属性时,同样也调用了方法。

具体来讲,下面两者(在最终没报错的情况下)是等价的:

ins.att = a
del ins.att
type(ins).__setattr__(ins, 'att', a)
type(ins).__delattr__(ins, 'att')

在默认情况下,这两个方法都应该返回 None

实例属性与方法列表

[编辑 | 编辑源代码]

object.__dir__(self)

[编辑 | 编辑源代码]

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

描述器

[编辑 | 编辑源代码]

我们之前学习“私有量”的时候提到过 property。property 是一种描述器。

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

__get__(self, instance, owner=None)

[编辑 | 编辑源代码]

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

__set__(self, instance, value)

[编辑 | 编辑源代码]

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

__delete__(self, instance)

[编辑 | 编辑源代码]

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

描述器如果不作为类的属性、而作为一般的变量使用时,不会有 __getattribute__ 等方法来调用描述器方法。

我们看以下示例:

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'

參考文獻

[编辑 | 编辑源代码]