• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

Python类内置方法简介

互联网 diligentman 2周前 (10-14) 16次浏览

与其他语言相比,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.attrself.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.

喜欢 (0)