值得高兴的是,经过一段时间的学习,《Fluent Python》一书的内容只剩下最后一个部分了:元编程。
当然,我同时也发现这本书在中后部的内容难度陡然增加,但是随着书页的变薄,任何读者想必都难免会有喜悦和轻松之感。
闲话少说,进入今天的主题。
任何语言对面向对象的学习都是先介绍类,而类中最开始学的内容必然是属性。Python作为一门动态语言,相比静态语言,在类和对象的属性上面有更多“花样”,值得我们专门花时间总结一下。
Python的类和对象中有一些特殊属性,可以帮助我们实现类似Java中的反射功能,直接“嗅探”一个未知类或者对象的内部结构。
__class__
这个属性其实之前多次使用过,它关联到一个实例的类对象,其和type
函数获取到的对象是一致的。
class TestClass:
def __init__(self) -> None:
pass
tc = TestClass()
print(tc.__class__)
print(type(tc))
print(tc.__class__ == type(tc))
print(type(tc.__class__))
#
#
# True
#
可以看到“类对象”实质上是一个type
类型的实例,用于存储类定义中的相关信息。
__dict__
__dict__
属性是一个字典,包含实例中的所有可变属性,我们可以直接操作这个属性来动态地给实例添加新的属性。
- 之前我们讨论过,事实上Python不存在类似
const
的关键字,所有属性都是可变的,但是我们可以通过特性(property)来实现不可变属性,这一点在之后会详细说明。- 操作
__dict__
来修改实例属性似乎多此一举,因为我们可以使用常规的.
操作符,但是在之后介绍完特性之后你就会明白其价值。
class TestClass:
def __init__(self) -> None:
pass
tc = TestClass()
print(tc.__dict__)
tc.newOpt = 'new'
print(tc.__dict__)
newOpts = {'opt1': 1, 'opt2': 2, 'opt3': 3}
tc.__dict__.update(newOpts)
print(tc.__dict__)
print(tc.newOpt)
print(tc.opt1)
print(tc.opt2)
print(tc.opt3)
# {}
# {'newOpt': 'new'}
# {'newOpt': 'new', 'opt1': 1, 'opt2': 2, 'opt3': 3}
# new
# 1
# 2
# 3
这个示例展示了通过.
操作符和直接修改__dict__
属性来给实例添加新属性,可以看到结果并无区别,本质上都是在修改__dict__
字典。此外,使用__dict__
还有个好处是我们可以通过调用字典的update
方法来批量添加、覆盖实例的属性。
__slots__
之前在Python学习笔记26:符合Python风格的对象有介绍过__slots__
,我们可以利用这个特殊的类属性优化对象的存储性能,将散列式的字典结构变成紧密排列的元组结构。
但我们也提到过,使用这一优化技术的后果就是无法再动态添加实例的属性:
class TestCls:
__slots__=('opt1','opt2')
def __init__(self) -> None:
self.opt1 = 1
self.opt2 = 2
tc = TestCls()
# tc.opt3 = 3
# Traceback (most recent call last):
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 8, in
# tc.opt3 = 3
# AttributeError: 'TestCls' object has no attribute 'opt3'
# print(tc.__dict__)
# Traceback (most recent call last):
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 13, in
# print(tc.__dict__)
# AttributeError: 'TestCls' object has no attribute '__dict__'
我们可以看到,在使用__slots__
以后实例甚至就没有__dict__
这个属性。
class TestCls:
__slots__=('opt1','opt2','__dict__')
def __init__(self) -> None:
self.opt1 = 1
self.opt2 = 2
tc = TestCls()
tc.opt3 = 3
print(tc.opt3)
print(tc.__dict__)
# 3
# {'opt3': 3}
如果我们将__dict__
这个特殊属性加入__slots__
,就可以正常添加属性了。
当然这么做可能没有实际意义,只是作为
__slots__
和__dict__
机制的说明。
同样的,之前的学习中其实我们已经使用过很多有关属性的内建函数,这里做一个汇总。
这个内建函数会返回实例中大部分对开发者有用的属性和方法,包括从父类继承的部分。
class TestClass:
def __init__(self) -> None:
pass
tc = TestClass
print(dir(tc))
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
# '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
getattr(obj,name[,default])
的作用是从指定实例obj
获取一个名称为name
的属性,default
是可选的。
class TestClass:
def __init__(self) -> None:
self.opt1 = 1
self.opt2 = 2
tc = TestClass()
print(getattr(tc,'opt1'))
print(getattr(tc,'opt3',3))
print(getattr(tc,'opt3'))
# 1
# 3
# Traceback (most recent call last):
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 9, in
# print(getattr(tc,'opt3'))
# AttributeError: 'TestClass' object has no attribute 'opt3'
可以看到,如果属性不存在,在指定了default
的前提下会返回default
值,如果没有指定,会抛出AttributeError
异常。
可以用hasattr(obj,name)
判断实例obj
是否有名称为name
的属性。
class TestClass:
def __init__(self) -> None:
self.opt1 = 1
self.opt2 = 2
tc = TestClass()
print(hasattr(tc,'opt1'))
print(hasattr(tc,'opt3'))
# True
# False
有Python文档显示,
hasattr
是通过检查getattr
是否抛出异常来判断的。
setattr(obj,name,value)
用于设置实例obj
的属性。
class TestClass:
def __init__(self) -> None:
self.opt1 = 1
self.opt2 = 2
tc = TestClass()
setattr(tc, 'opt1', 111)
setattr(tc, 'opt3', 333)
print(tc.opt1)
print(tc.opt3)
# 111
# 333
如果没有该属性,将添加一个新的属性,这点和使用.
操作符是一致的。
vars([obj])
会返回实例obj
的__dict__
属性。
class TestClass:
def __init__(self) -> None:
self.opt1 = 1
self.opt2 = 2
tc = TestClass()
print(vars(tc))
print(vars())
# {'opt1': 1, 'opt2': 2}
# {'__name__': '__main__', '__doc__': None, '__package__': '', '__loader__': None, '__spec__': None, '__file__': 'D:\\workspace\\python\\python-learning-notes\\note36\\test.py', '__cached__': None, '__builtins__': {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__':
如果没有参数,就会返回当前上下文加载的属性。内容很多,这里只展示部分。
我们可以在类中定义一些作用于属性的魔术方法,这些魔术方法将改变类实例对属性的操作行为。
其实我们在Python学习笔记27:类序列对象中简单介绍和使用过这类魔术方法了,这里同样做一个总结。
__getattribute__
这个魔术方法会改变从类实例获取属性的行为。
from typing import Any
class TestClass:
def __init__(self) -> None:
self.opt1 = 1
self.opt2 = 2
def __getattribute__(self, name: str) -> Any:
try:
attr = super().__getattribute__(name)
except AttributeError:
setattr(self, name, 1)
return super().__getattribute__(name)
else:
return attr
tc = TestClass()
print(tc.opt2)
print(tc.opt3)
# 2
# 1
这里我们使用__getattribute__
实现了如果试图读取一个不存在的实例属性,我们就创建,并且给其赋值为1。
- 可能更好的方式是将这个默认值可以通过
__init__
方法指定。- 从命名上可以看出
__getattribute__
其实并不如__getattr__
有用,这点在之后的处理JSON的示例中会看到。
__setattr__
__setattr__(self,name,value)
魔术方法会改变类实例对属性的赋值行为:
class TestClass:
def __init__(self) -> None:
pass
def __setattr__(self, name, value):
newVlue = 'new_'+str(value)
super().__setattr__(name, newVlue)
tc = TestClass()
tc.opt1 = 1
print(tc.opt1)
# new_1
注意,__setattr__
内部不能使用setattr(obj,name,newVlue)
,而是要用父类的__setattr__
方法实现对属性的赋值操作,因为前者会陷入无限递归。
__delattr__
__delattr__
魔术方法是和del
关键字相关的,当我们使用del
关键字删除类实例的某个属性时候,如果定义了该魔术方法,就会调用,而非执行原本的操作。
class TestClass:
def __init__(self) -> None:
self.opt1 = 1
self.opt2 = 2
def __delattr__(self, name: str) -> None:
value = getattr(self, name)
super().__delattr__(name)
newName = 'deled_'+str(name)
setattr(self, newName, value)
tc = TestClass()
del tc.opt1
del tc.opt2
print(tc.__dict__)
# {'deled_opt1': 1, 'deled_opt2': 2}
这里通过设置__delattr__
魔术方法,实现了在用户删除实例属性后,依然保留一个名称为del_XXX
的属性作为备份,将“硬删除”操作变为了“软删除”,甚至可以在这基础上进行属性的还原操作。
__getattr__
可能很多人在看到这里都会困惑,不是已经有__getattribute__
了吗,这个又是干什么用的?
通过官方文档我们可以知道,__getattribute__
是所有对类实例属性的访问行为都会触发,而__getattr__
只有在获取实例属性时候触发AttributeError
的时候才会被调用,所以上面那个如果属性不存在就创建默认属性的行为更适合放在__getattr__
中:
from typing import Any
class TestClass:
def __init__(self, default=None) -> None:
self.default = default
def __getattr__(self, name):
setattr(self, name, self.default)
return self.__dict__[name]
def __getattribute__(self, name: str) -> Any:
return super().__getattribute__(name)
tc = TestClass()
tc.opt1 = 1
print(tc.opt2)
print(tc.__dict__)
# None
# {'default': None, 'opt1': 1, 'opt2': None}
其实这里并不需要创建__getattribute__
方法,只是为了对比说明。在这里这个方法继承了基类方法的行为,什么也没做。
在__getattr__
中没有使用getattr
或者__getattribute__
,是为了防止可能会发生的无限递归。
__dir__
这个很好理解,就是调用dir
函数时候触发的,给其提供一个返回值。
from collections.abc import Iterable
class TestClass:
def __init__(self) -> None:
self.opt1 = 1
self.opt2 = 2
def __dir__(self) -> Iterable[str]:
res = list(super().__dir__())
res.append('del_XXX')
return res
tc = TestClass()
print(dir(tc))
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
# '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'del_XXX', 'opt1', 'opt2']
这里使用res.append('del_XXX')
添加了一个不存在的属性说明,可能并不合适。仅用于说明魔术方法的用途。
下面我们展示如何使用之前介绍的属性相关魔术方法来实现动态属性。
我们来看一个处理JSON字符串的例子:
jsonStr = '''{
"sites": [
{ "name":"菜鸟教程" , "url":"www.runoob.com" },
{ "name":"google" , "url":"www.google.com" },
{ "name":"微博" , "url":"www.weibo.com" }
]
}'''
import json
jsonObj = json.loads(jsonStr)
print(jsonObj)
print(jsonObj['sites'][0]['name'])
print(jsonObj.sites)
#
# 'www.weibo.com'}]}
# 菜鸟教程
# Traceback (most recent call last):
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 12, in
# print(jsonObj.sites)
# AttributeError: 'dict' object has no attribute 'sites'
可以看到,使用json.loads
处理后的json字符串转变成了一个字典,我们可以使用字典的方式去访问其中的值。
这里的示例JSON字符串来自于www.runoob.com。
但假如我们不想使用[]
,而是想像访问对象属性一样访问里边的值呢?
from typing import Any
from collections.abc import Mapping
from collections.abc import MutableSequence
class JSONReader:
def __init__(self, dictData:dict) -> None:
self.__dictData = dict(dictData)
def __getattr__(self, name: str) -> Any:
if hasattr(self.__dictData, name):
return getattr(self.__dictData, name)
elif name in self.__dictData:
attrValue = self.__dictData[name]
if isinstance(attrValue, Mapping):
return self.__class__(attrValue)
elif isinstance(attrValue, MutableSequence):
return [self.__class__(item) for item in attrValue]
else:
return attrValue
else:
raise AttributeError
jsonStr = '''{
"sites": [
{ "name":"菜鸟教程" , "url":"www.runoob.com" },
{ "name":"google" , "url":"www.google.com" },
{ "name":"微博" , "url":"www.weibo.com" }
]
}'''
import json
jsonDict = json.loads(jsonStr)
jReader = JSONReader(jsonDict)
print(jReader.sites[0].name)
print(jReader.sites[1].url)
# 菜鸟教程
# www.google.com
注意,这里只能使用__getattr__
,不能使用__getattribute__
,否则会陷入无限递归。因为我们在__getattr__
中使用了大量的.
运算符来进行属性访问,如果使用的是后者,就会再次触发__getattribute__
,然后又使用.
运算符,又触发,以此往复。
这也就是为什么官方文档中说__getattr__
与__setattr__
名称对应,而__getattribute__
反而命名更另类的原因。从上面这个示例的角度上看,__getattr__
显然更有用。
此外就是对JSON字数据的处理中我们只考虑了字典和可变数组这两种数据类型,事实上JSON字符串也只包含这两种类型的数据。
现在通过这个精巧的自定义类我们就可以像使用对象属性那样读取JSON中的数据了,但是我们还可以进一步改进这段代码,让其更优雅一点。
这里我们需要使用到__new__
。
__new__
和__init__
学习Python的时候我们习惯性地称呼__init__
方法为构造方法,这是从其他编程语言中借鉴过来的概念。事实上这是不准确的,准确地讲__init__
只是一个“初始化方法”。
这点从__init__
的函数签名也能看出来:
class JSONReader:
def __init__(self, dictData:dict) -> None:
self.__dictData = dict(dictData)
这段代码中__init__
的帮助文档是VSCode插件自动生成的,很明显地说明了一个事实:__init__
方法本身并不会返回任何实例,而且当进入__init__
方法的时候,对象实例self
已经创建。
这都是真正的“构造方法”__new__
的功劳。
__new__
的用途是创建类实例并返回,这点和其它语言中的构造函数是一样的。不同的是,Python中的__new__
不仅能创建当前类的对象实例,还能创建并返回其它类型。
如果__new__
创建并返回的是当前类型,将会调用__init__
方法进行初始化,但如果是返回的其它类型,将不会调用__init__
。
因为这个特点,Python中使用类名产生实例就可以相当灵活,根据情况,可以产生非自己类型的结果。
对于之前的例子,我们在__getattr__
方法中编写了大量代码对数据类型判断,并使用JSONReader
进行包装或者产生一个JSONReader
的列表,亦或者直接返回。
除了这种方式外,我们现在有了另一个选择:借助__new__
来让JSONReader
类自己“智能”地判断传给它的数据如何进行处理,并返回一个恰当的结果,而__getattr__
就简单很多了,只要把数据丢给构造方法即可。
from typing import Any
from collections.abc import Mapping
from collections.abc import MutableSequence
class JSONReader:
def __new__(cls, jsonData:Any) -> Any:
if isinstance(jsonData, Mapping):
return super().__new__(cls)
elif isinstance(jsonData, MutableSequence):
return [cls(item) for item in jsonData]
else:
return jsonData
def __init__(self, dictData:Mapping) -> None:
self.__dictData = dict(dictData)
def __getattr__(self, name: str) -> Any:
if hasattr(self.__dictData, name):
return getattr(self.__dictData, name)
elif name in self.__dictData:
attrValue = self.__dictData[name]
return self.__class__(attrValue)
else:
raise AttributeError
jsonStr = '''{
"sites": [
{ "name":"菜鸟教程" , "url":"www.runoob.com" },
{ "name":"google" , "url":"www.google.com" },
{ "name":"微博" , "url":"www.weibo.com" }
]
}'''
import json
jsonDict = json.loads(jsonStr)
jReader = JSONReader(jsonDict)
print(jReader.sites[0].name)
print(jReader.sites[1].url)
# 菜鸟教程
# www.google.com
这里需要注意的是,在__new__
中不能使用cls(jsonData)
的方式创建实例,因为__new__
本身就是cls
的构造方法,cls(jsonData)
会再次触发__new__
,陷入无限递归调用中。
在我写这几段代码后我的体会是,如果你在面临这种问题,首先应该尝试使用父类的方法,比如super().__new__(cls)
,这里实质上是调用基类object
的__new__
方法,需要传入一个参数cls
,告诉其创建一个什么类型的实例。
Python是有意没有使用类似C++和Java中的
new
关键字,在Python的理念中,构造函数本身就是一个普通的可执行对象,和别的方法以及可执行实例是没有区别的,所以使用new
反而会限制Python中类构造方法的灵活性,在Python中通过构造方法创建实例和通过工厂方法创建,是完全可以互相替换的,只不过因为类名使用首字母大写的驼峰形式而方法不是,命名上两者有区别而已。
我们在Python学习笔记26:符合Python风格的对象中有简单介绍和使用过特性(property)。现在我们全面了解一下。
class TestClass:
def __init__(self) -> None:
self.__opt1 = 1
@property
def opt1(self):
return self.__opt1
tc = TestClass()
print(tc.opt1)
print(tc.__dict__)
tc.opt1 = 2
# 1
# {'_TestClass__opt1': 1}
# Traceback (most recent call last):
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 13, in
# tc.opt1 = 2
# AttributeError: can't set attribute
创建一个特性很简单,只要给普通的getter方法前加上@property
装饰器即可,这样就可以简单创建一个只读的特性,就像示例代码展示的那样。
此外需要注意的是,特性(property)和属性(attribute)有很大区别,特性并不保存在__dict__
属性中,而且特性可以只读,尝试对一个只读特性赋值就会产生一个AttributeError
异常。
此外,特性也支持写和删除操作,声明方式与读类似:
class TestClass:
def __init__(self) -> None:
self.__opt1 = 1
@property
def opt1(self):
return self.__opt1
@opt1.setter
def opt1(self, value):
self.__opt1 = value
@opt1.deleter
def opt1(self):
del self.__opt1
tc = TestClass()
print(tc.opt1)
print(tc.__dict__)
tc.opt1 = 2
print(tc.opt1)
del tc.opt1
print(tc.opt1)
# 1
# {'_TestClass__opt1': 1}
# 2
# Traceback (most recent call last):
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 24, in
# print(tc.opt1)
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 7, in opt1
# return self.__opt1
# AttributeError: 'TestClass' object has no attribute '_TestClass__opt1'
设置特性的写和读的时候使用的装饰器是opt1.setter
和opt1.deleter
,这是刚刚创建的只读特性的两个装饰器方法,利用这两个装饰器我们可以给只读特性增加写和删除操作。
需要注意的是同一个特性的三个操作的方法名要和特性名称一致,在上面都是opt1
,如果不一致就可能在进行相应的操作时产生AttributeError
异常。
除了通过上面的方式创建特性,还可以用下面的方式:
class TestClass:
def __init__(self) -> None:
self.__opt1 = 1
def get_opt1(self):
return self.__opt1
def set_opt1(self, value):
self.__opt1 = value
def del_opt1(self):
del self.__opt1
opt1 = property(fget=get_opt1, fset=set_opt1, fdel=del_opt1)
tc = TestClass()
print(type(TestClass.opt1))
print(tc.opt1)
print(tc.__dict__)
tc.opt1 = 2
print(tc.opt1)
del tc.opt1
print(tc.opt1)
#
# 1
# {'_TestClass__opt1': 1}
# 2
# Traceback (most recent call last):
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 23, in
# print(tc.opt1)
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 6, in get_opt1
# return self.__opt1
# AttributeError: 'TestClass' object has no attribute '_TestClass__opt1'
事实上property
是一个类,从输出
可以看出,我们通过opt1 = property(fget=get_opt1, fset=set_opt1, fdel=del_opt1)
是创建了一个property
的实例,并将其赋值给命名为opt1
的类变量。
特性具体的读写删除操作是通过函数式编程的方式通过参数传入property
初始化方法的。
在使用上两者当然完全一致。
这种方式比上面直接通过装饰器“绑定”方法的方式更灵活,我们可以利用其构建一个满足具体使用需求的特性工厂方法,并使用工厂方法批量创建特性。
def propertyFactory(name):
def getter(instance):
return instance.__dict__[name]
def setter(instance, value):
if value > 0:
instance.__dict__[name] = value
else:
raise ValueError("value must > 0")
def deleter(instance):
del instance.__dict__[name]
return property(fget=getter, fset=setter, fdel=deleter)
class TestClass:
opt1 = propertyFactory("opt1")
opt2 = propertyFactory("opt2")
opt3 = propertyFactory("opt3")
def __init__(self, opt1, opt2, opt3) -> None:
self.opt1 = opt1
self.opt2 = opt2
self.opt3 = opt3
def __str__(self) -> str:
return str((self.opt1, self.opt2, self.opt3))
tc1 = TestClass(1, 2, 3)
print(tc1)
tc2 = TestClass(0, 1, 0)
# (1, 2, 3)
# Traceback (most recent call last):
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 32, in
# tc2 = TestClass(0, 1, 0)
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 22, in __init__
# self.opt1 = opt1
# File "D:\workspace\python\python-learning-notes\note36\test.py", line 9, in setter
# raise ValueError("value must > 0")
# ValueError: value must > 0
这里通过创建特性工厂方法propertyFactory
创建了三个特性,而且这三个特性的写操作都会检查新值是否为大于零的数,如果不是就抛出ValueError
异常。
这里需要注意的要点有:
setter/getter/deleter
方法中要用到特性绑定的类的实例引用instance
,但这个引用不需要声明到propertyFactory
的参数列表中,而是会在实际使用特性的时候由解析器直接传入相应方法。getter
等方法中通过instance.__dict__[name]
的方式访问真实属性,而非使用setattr
等内建函数,这是因为setattr
等内建函数会触发特性,这又可能导致无限递归。instance.__dict__[name]
的名称name
与特性同名,这样做的好处是除非外部程序直接操作__dict__
,否则是访问不到真实属性的,所有对name
的访问都会由特性处理。opt1 = propertyFactory("opt1")
等特性创建语句在__init__
方法之前,这是为了能在初始化方法中就使用特性:self.opt1 = opt1
。在Python学习笔记26:符合Python风格的对象我们讨论过实例属性对类属性的覆盖问题。事实上特性、实例属性和类属性的确有类似于访问优先级的问题。
特性、实例属性和类属性的优先级依次降低,顺序是特性>实例属性>类属性。
下面我们用实际代码来验证:
class TestClass:
def __init__(self) -> None:
self.__dict__['opt1'] = 1
@property
def opt1(self):
return 42
tc = TestClass()
print(tc.opt1)
del TestClass.opt1
print(tc.opt1)
# 42
# 1
这里可以清楚地看到,在删除特性(特性本质就是类属性)后,使用.
运算符访问属性就可以直接访问到__dict__
中的属性值了。
这个示例必须是通过特性直接返回固定值,而非利用
__dict__
对真实值进行读写操作,原因是那样做是将实例的同名属性与特性等价,无法说明特性和属性的访问优先级差异。
关于实例属性和类属性的优先级,Python学习笔记26:符合Python风格的对象中有详细说明,这里不再赘述。
其实特性和普通的类属性谈不上优先级,因为如果是同名的话他们根本就是同一个类属性。
通过上面的讨论我们可以看到,在访问实例的属性的时候,解释器会先去查看类定义中是否存在特性,如果没有,再查看实例中有没有该属性,如果还没有,再查看类定义中是否有普通的类属性。
也就是说按特性>实例属性>类属性的先后顺序进行检索,这也就是一开始说的这三者的优先级差异。
在python中,可以通过help
内建函数来读取函数的帮助文档,比如:
help(dir)
# Help on built-in function dir in module builtins:
# dir(...)
# dir([object]) -> list of strings
# If called without an argument, return the names in the current scope.
# Else, return an alphabetized list of names comprising (some of) the attributes
# of the given object, and of attributes reachable from it.
# If the object supplies a method named __dir__, it will be used; otherwise
# the default dir() logic is used and returns:
# for a module object: the module's attributes.
# for a class object: its attributes, and recursively the attributes
# of its bases.
# -- More --
对于普通函数,帮助文档就是函数声明下的那一行信息:
def my_test():
'''this is a test function'''
pass
help(my_test)
# Help on function my_test in module __main__:
# my_test()
# this is a test function
对于特性,我们也可以设置相关帮助文档:
class TestClass:
def __init__(self) -> None:
self.__opt1 = 1
def get_opt1(self):
return self.__opt1
opt1 = property(fget=get_opt1,doc='this is a property doc')
help(TestClass.opt1)
# Help on property:
# this is a property doc
对于通过装饰器设置的特性,就更简单了:
class TestClass:
def __init__(self) -> None:
self.__opt1 = 1
@property
def opt1(self):
'''this is a property doc'''
return self.__opt1
help(TestClass.opt1)
# Help on property:
# this is a property doc
这种情况下特性的帮助文档就是读操作方法的帮助文档。
好了,关于动态属性和特性的内容介绍完毕。
这部分内容难倒是不难,就是很繁琐细碎,我的老腰…
谢谢阅读,我要去歇一会了ORZ。