与其余语言相比,Python 中的类提供了很多双下划线结尾和结尾 __xxx__
的办法,这些办法是 Python 运行的根底,很多性能背地都是通过调用这些内置办法来实现的。如 len()
函数调用对象的 __len__
办法;print(obj)
函数调用对象的 __str__
办法,for item in iterable_obj
调用对象的 __next__
、__iter__
办法。
注:因为所有类内置办法都在双下划线结尾和结尾,下文形容中,为形容简洁,在无歧义状况下,局部形容中会去掉办法前后双下划线。
__new__
和__init__
new 和 init 这两个办法很容易混同,平时定义类时,通常应用的都是 init 办法,很少用到 new 办法,但他们是有着截然不同的性能的。
new 是类办法,用于创立实例对象,办法必须返回一个对象;而 init 是实例办法,执行实例初始化,在 new 返回的对象中执行。
阐明:IDE 提供的 stub 办法显示 new 是 @staticmethod,但依据办法参数中携带了 cls 参数,集体认为应该是 @classmethod
失常类的实例化过程如下。
class Obj(object):
def __new__(cls):
print("__new__ in <Obj>")
return object.__new__(Obj)
def __init__(self):
print("__init__ in <Obj>")
obj = Obj()
print(type(obj))
# 执行输入
>> __new__ in <Obj>
>> __init__ in <Obj>
>> <class '__main__.Obj'>
如果咱们在类的 new 办法中,返回成其余类型对下,则最终失去的会是新类型。
class OldObj(object):
def __new__(cls):
print("__new__ in <OldObj>")
return object.__new__(NewObj)
def __init__(self):
print("__init__ in <OldObj>")
class NewObj(object):
def __init__(self):
print("__init__ in <NewObj>")
obj = OldObj()
print(type(obj))
# 执行输入
>> __new__ in <OldObj>
>> <class '__main__.NewObj'>
这里有个疑难,为什么 new 执行之后,既没执行 OldObj 的 init 办法,也没执行 NewObj 的 init 办法,有待钻研。
在利用上,能够通过覆写 new 办法,来实现单例模式。
class Singleton(object):
instance = None
def __new__(cls):
if not cls.instance:
cls.instance = object.__new__(cls)
return cls.instance
s1 = Singleton()
s2 = Singleton()
print(s1 == s2) # 输入:True
__str__
和__repr__
str 和 repr 都返回一个对象的字符串形容,不过两者的用处不同,str 能够了解是给人浏览的,而 repr 是给程序应用的,官网对 repr 的形容如下:
repr(obj, /)
Return the canonical string representation of the object.
For many object types, including most builtins, eval(repr(obj)) == obj.
print(obj)
办法调用对象的 str 办法,而交互式 CLI 和调试时,查看对象时返回的是 repr,不过和多状况下程序员把 str 和 repr 设置为一样__str__ == __repr__
。
__call__
call 办法把一个对象变成为可调用对象,即通过 obj()
间接调用对象时,零碎执行的是对象的 call 办法,实现 call 办法的对象,callable(obj)
返回为 True。
class CallObj(object):
def __call__(self, *args, **kwargs):
print("__call__")
obj = CallObj()
print(callable(obj))
obj() # 调用了对象的__call__办法
利用方面,能够通过 call 语法糖,简化对象的调用;也能够用户实现 call 办法的对象代替基于函数的装璜器,简化代码构造。
__iter__
和__next__
在 Java 等强类型的语言中,对象的性能个性必须通过继承或实现接口来实现,比方可迭代的类,必须继承自 Iterator 或实现 Iterable 接口,并实现相干的办法。而对于动静语言的 Python 来说,它属于 鸭子类型,只有一个类实现了 iter 和 next 办法,它就是一个可迭代对象。
鸭子类型(英语:duck typing)在程序设计中是动静类型的一种格调。在这种格调中,一个对象无效的语义,不是由继承自特定的类或实现特定的接口,而是由 ” 以后办法和属性的汇合 ” 决定。这个概念的名字来源于由詹姆斯·惠特科姆·莱利提出的鸭子测试.
“鸭子测试”能够这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就能够被称为鸭子。”
class Iter:
def __init__(self, max):
self.max = max
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current < self.max:
self.current += 1
return self.current
else:
raise StopIteration("out-of-bounds")
for i in Iter(10):
print(i)
__getitem__
、__setitem__
和__delitem__
这三个办法,次要服务于类 list、dict 类型的数据结构的中括号 []
数据操作中,包含下标操作、切片操作、Key 值操作等,通过简略测试,发现 Python 作为一个合格的“鸭子类型”语言,相干办法能接管的参数类型比 list、dict 提供的更丰盛,为展现相干办法能够应用的参数,以下示例只对办法参数进行打印,不实现具体性能逻辑:
class OpItem:
def __getitem__(self, item):
print("getitem, args:", item)
def __setitem__(self, key, value):
print("setitem, args:", key, value)
def __delitem__(self, key):
print("delitem, args:", key)
item_test = OpItem()
getitem 操作:
# 下标读取操作
item_test[1] # >> getitem, args: 1
# 切片读取操作
item_test[1:10] # >> getitem, args: slice(1, 10, None)
# 带 step 的切片读取操作
item_test[1:10:2] # >> getitem, args: slice(1, 10, 2)
# 通过 key 值读取操作
item_test["str_key"] # >> getitem, args: str_key
# 切片为 key 值,非标操作,办法会忠诚的传递参数
item_test["key1":"key2"] # >> getitem, args: slice('key1', 'key2', None)
setitem 操作:
# 下标赋值操作
item_test[1] = 99 # >> setitem, args: 1 99
# 切片替换操作
item_test[1:2] = [10, 20] # >> setitem, args: slice(1, 2, None) [10, 20]
delitem 操作:
# 下标删除操作
del item_test[5] # >> delitem, args: 5
# 切片删除操作
del item_test[5:10] # >> delitem, args: slice(5, 10, None)
__getattr__
、__setattr__
、__delattr__
这组办法是在框架开发中必备神器。
当拜访对象中不存在的属性时,零碎会去调用对象的 getattr 办法,通过对此办法的解决,能够给零碎凭空发明出原来不反对的性能。如拜访obj.attr1
,而 obj 中不存在 attr1 时,会触发 getattr 办法。
setattr 在向对象属性设值时触发,如obj.attr1 = 100
;delattr 在删除属性时触发,如del obj.attr1
。
比方在一个 ORM 的模型中,咱们须要把数据库字段相干的属性保留在对象内的 dict 型字段 fields 中,实现和对象的其余字段隔离,就能够通过以上三个办法来实现。
class User:
def __init__(self):
# 保留数据库表映射字段
self.__dict__['fields'] = dict()
# 数据库字段列表
self.__dict__["fields_list"] = ["name", "age", "address"]
def __setattr__(self, key, value):
# 实现通过 user.name = xxx 赋值
if key in self.fields_list:
self.__dict__["fields"][key] = value
return
self.__dict__[key] = value
def __getattr__(self, key):
# 实现通过 user.name 取值
if key in self.fields_list:
return self.__dict__["fields"][key]
return self.__dict__[key]
user = User()
user.name = "zhangsan"
print(user.__dict__['fields']['name']) # >> zhangsan
在实现了 getattr 办法的类的外部,对对象属性进行赋值时须要特地留神,很容易引发 getattr 的有限循环。
在 init 中,对象的所有自定义属性都没有初始化,此时如果对属性赋值,会触发调用 setattr 办法,拜访属性则触发 getattr。
所以在类外部,最好通过 self.__dict__对对象属性进行赋值和取值,防止通过点(.)运算符(self.attr
和self.attr = xxx
)引发有限循环。
__getatrribute__
getatrribute 是一个属性拜访拦截器,会拦挡所有对对象属性拜访申请,不论属性是否存在,且具备最优先的拜访查问程序。
对象的属性查找程序如下:
实例的 getattribute → 实例对象字典 → 实例所在类字典 → 实例所在类的父类 (MRO 程序) 字典 → 实例所在类的 getattr → 报错
__enter__
和__exit__
enter 和 exit 能够让对象通过 with 关键字来进行应用,提供进入 with 块前的初始化工作和退出 with 块后的清理工作,罕用于文件和数据库操作中。
class DbConnect:
def connect(self):
print("Init and connect to db.")
def execute(self):
print("Execute SQL statement.")
def disconnect(self):
print("Disconnect from db.")
def __enter__(self):
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.disconnect()
with DbConnect() as conn:
conn.execute()
# 输入
# >> Init and connect to db.
# >> Execute SQL statement.
# >> Disconnect from db.