User:Xyy23330121/Python/类的方法与类实例
我们之前已经提到过 __init__
方法,这里将详细讲解创建实例的过程。
def __new__(cls, value="0", context=None):
self = object.__new__(cls)
...
if isinstance(value, str):
...
self._sign = 1
...
return self
可以看出,自定义的 __new__ 方法,它满足以下特征:
- 第一步就是用 object(所有类的父类)定义的 __new__ 方法,把结果赋值给变量 self。
- 之后是对 self 创建各种属性,由于使用的变量名称是 self,所以语法和 __init__(self) 中完全一致。
- 最后 return self
本方法是创建实例的第一步。它会在使用时创建一个新实例。
本方法默认为静态方法、类方法(而无需用装饰器),它会将所属的类自动传入第一个参数。
在自定义该方法时,一般会使用 super().__new__
访问父类定义的 __new__
方法,并修改父类 __new__
方法返回的结果。
此函数[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()
的子类本身)。
如果 __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__() 方法。
在获取实例属性时,首先调用的是此方法。此方法返回的结果就是获取到的属性。
如果未在类型中自定义此方法,则此方法会自动返回实例的内部属性,或者报错 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)
当 __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__()
方法找不到对应的属性,本方法同样可以引发无穷递归。
此方法用于支持 self.name = value
的操作。和获取实例属性时不同,设置实例属性只有这一个函数。
类似 __getattribute__
,在自定义该方法时,应当注意不要使用 self.name = value
的形式。如果有需要,就调用基类的方法 object.__setattr__(self, name, value)
。
此方法用于支持 del self.name
的操作。
该方法用于支持 dir(self)
的操作。它会返回实例中、属性与方法名称的字符串组成的列表。
inspect 模块列出了所有可获得的内置特殊属性。详情请参见 Python 文档。
我们之前学习“私有量”的时候提到过 property。property 是一种描述器。以下所示的方法仅当它作为类的属性时才会起作用。
这是因为获取、设置或删除类的属性时,类的 __getattribute__ 等方法会尝试调用描述器的方法。描述器如果不作为类的属性、而作为一般的变量使用时,不会有 __getattribute__ 等方法来调用描述器方法。
只要设置了下面方法中的一个,该类型就是一种描述器。
其中,instance 是描述器属性所在的实例,而 owner 是 instance 所属的类型。
其中,instance 是描述器属性所在的实例,而 value 是要赋的值。
其中,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
在创建描述器时,还可以添加一个可选的属性。该属性应当设置为描述器属性所在的实例类型。该属性会被 inspect 模块所使用,有助于动态类属性的运行时内省。
为了代码的易于扩展,有时需要自定义子类创建的行为。
在创建子类时会调用父类的此方法,其中, 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 来为整数作进一步优化。增加了代码的可扩展性。
在创建类的同时创建属性时会调用此方法。我们看以下示例:
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'