Python-魔法方法详解

__new__

__new__是在一个对象实例化的时候所调用的第一个方法,它的第一个参数是它的类,其他参数用于传递给__init__方法,__new__决定是否要使用__init__方法,因为__new__可以调用其他类的构造方法或者返回别的实例对象来作为本类的实例对象,如果__new__没有返回实例对象,那么__init__将不会被调用,__new__方法主要是当你继承一些不可变的class时(int, str, tuple),提供给你一个自定义这些类的实例化过程的途径。如下:

class Student(int):

    def __new__(cls, value):
        return super(Student, cls).__new__(cls, abs(value))

s = Student(-3)
print(s)

通过重载__new__方法,我们实现了我们想要的需求!我们还可以通过重载__new__方法,可以简单的实现单例模式:

class Student(object):
    instance = None
    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super(Student, cls).__new__(cls, *args, **kwargs)
        
        return cls.instance
        
s1 = Student()
s2 = Student()
s1.value = 20
print(s1, s2)
print(s1.value, s2.value)
print(s1 is s2)

############## 打印结果如下 ##############
<__main__.Student object at 0x1033a1c88> <__main__.Student object at 0x1033a1c88>
20 20
True

__init__

构造器,当一个实例被创建时调用的初始化方法

__del__

析构器,当一个实例被销毁时调用的方法

__call__

允许一个类的实例像函数一样被调用。举个栗子:

class Student(object):

    def __call__(self, value):
        print(value)
        
s = Student()
s(3)

运行之后,将会打印数字3!

__len__

调用len()将会触发,须返回一个数值,举个栗子:

class Student(object):

    def __len__(self):
        print("__len__")
        return 0
        
s = Student()
print(len(s))

############## 打印结果如下 ##############
__len__
0

__repr__

当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,用来告诉外界该对象具有的状态信息,也可以通过内置函数repr()函数来获取对象的信息。举个栗子:

class Student(object):
    pass
    
s = Student()
print(s)
############## 打印结果如下 ##############
<__main__.Student object at 0x1033d3390>


class Student(object)
    
    def __repr__(self):
        return "student"
        
s = Student()
print(s)
############## 打印结果如下 ##############
student

__str__

__repr____str__都是用于显示的,__str__是面向用户的,__repr__是面向程序员的。举个栗子,在交互环境中效果如下:

>>> class Student(object):
>>>
>>>     def __repr__(self):
>>>         return "__repr__"
>>>
>>>     def __str__(self):
>>>         return "__str__"
>>>
>>> s = Student()
>>> s
__repr__
>>> print(s)
__str__
>>> repr(s)
'__repr__'
>>> str(s)
'__str__'

打印操作首先会尝试__str__str内置函数,它通常会返回一个友好显示;__repr__用于其他所有的环境中,交互式环境中提示回应或调用repr()函数,将会返回__repr__信息,当使用print()str()函数,它通常会看有无__str__,如果无,才会显示__repr__的信息。

__bytes__

调用bytes()将会触发,须返回一个byte,举个栗子:

class Student(str):

    def __bytes__(self):
        print("__bytes__")
        return b"__bytes__"
        
s = Student()
bytes(s)

__hash__

__hash__在下面两种场景中触发使用:

  • 在使用内置函数hash()时
  • hash类型的集合对自身成员的hash操作:set、frozenset、dict

举个栗子:

class Student(object):
    
    def __init__(self, name):
        self.name = name
        
    def __hash__(self):
        print("{} __hash__".format(self.name))
        return hash(self.name)

s = Student("laozhang")
print(hash(s))

############## 打印结果如下 ##############
laozhang __hash__
-8362428577884394890

hash类型的集合:

class Student(object):
    
    def __init__(self, name):
        self.name = name
        
    def __hash__(self):
        print("{} __hash__".format(self.name))
        return hash(self.name)
        
s1 = Student("laozhang")
s2 = Student("laowang")
s3 = Student("laoli")
s4 = Student("laozhang")
data = {s1, s2, s3, s4}
print(data)

############## 打印结果如下 ##############
laozhang __hash__
laowang __hash__
laoli __hash__
laowang __hash__
{<__main__.Student object at 0x103bb2208>, <__main__.Student object at 0x103bb21d0>, <__main__.Student object at 0x103bb2198>, <__main__.Student object at 0x103bad748>}

我们知道,集合有去重效果,可是laozhang出现了两次,并未达到我们的效果。这是因为s1与s2虽然name相同,但是分属两个对象,而判断是否是同一个对象的方法是__eq__(),默认的__eq__()会比较对象之间的内存地址,以此来判断是否是同一个对象。s1、s2内存地址不同,所以是两个对象!我们只需重写一下__eq__()方法,使其强制变为同一个对象,如下:

class Student(object):
    
    def __init__(self, name):
        self.name = name
        
    def __hash__(self):
        print("{} __hash__".format(self.name))
        return hash(self.name)
        
    def __eq__(self, other):
        print("{} __eq__ {}".format(self.name, other.name))
        return self.name == other.name
        
s1 = Student("laozhang")
s2 = Student("laowang")
s3 = Student("laoli")
s4 = Student("laozhang")
data = {s1, s2, s3, s4}
print(data)
############## 打印结果如下 ##############
laozhang __hash__
laowang __hash__
laoli __hash__
laozhang __hash__
laozhang __eq__ laozhang
{<__main__.Student object at 0x103bad710>, <__main__.Student object at 0x103ba0c88>, <__main__.Student object at 0x103bb2160>}

值得注意的是,如果类重写了__eq__()方法,而没有重写__hash__()方法,那么将无法进行hash操作,将会报错。如下:

from collections.abc import Hashable


class Student(object):
    
    def __init__(self, name):
        self.name = name
        
class Teacher(object):
    
    def __init__(self, name):
        self.name = name
        
    def __eq__(self, other):
        print("__eq__")
        return self.name == other.name
        
s = Student("laozhang")
t = Teacher("laozhang")
print(isinstance(s, Hashable))
print(isinstance(t, Hashable))
print(hash(s))
print(hash(t))

############## 打印结果如下 ##############
True
False
271822193
Traceback (most recent call last):
  ...
TypeError: unhashable type: 'Teacher'

这是因为重写__eq__()方法后会默认把__hash__赋值为None。还是以上面类为例,如下:

s = Student("laozhang")
t = Teacher("laozhang")
print(s.__hash__)
print(t.__hash__)

############## 打印结果如下 ##############
<method-wrapper '__hash__' of Student object at 0x1033ad748>
None

用户定义的类默认都有__eq__()__hash__()方法,这是从object中继承的,如果你不重写任何一个,那么同一个类的实例x与y来说,x is yx == yhash(x) == hash(y)会同时成立或同时不成立!

__bool__

当调用bool()函数将会触发__bool__()方法,返回True或者False。其实bool()的本质,就是调用__bool__()的结果,如果实例不存在__bool__()方法,python则将会调用__len__()方法,如果为0,那么bool()将会得到False,否则返回True。举个栗子:

class A(object):
    pass
    
class B(object):
    
    def __bool__(self):
        print("__bool__")
        return True
        
class C(object):
    
    def __len__(self):
        print("__len__")
        return 0
        
a = A()
b = B()
c = C()
print(bool(a))
print(bool(b))
print(bool(c))

############## 打印结果如下 ##############
True
__bool__
True
__len__
False

默认情况下,我们自定义的类的实例都会被认为是真的,除非自定义的类中__bool__()__len__()方法有其他实现

__format__

format()被调用时触发。举个栗子:

class Student(object):
    
    def __init__(self, name):
        self.name = name
    
    def __format__(self, format_spec):
        print("__format__")
        if format_spec == "":
            return str(self)
            
        result = format_spec.replace("{}", self.name)
        return result
        
s = Student("laozhang")
print(format(s, "{}"))
print("{}".format(s))

############## 打印结果如下 ##############
__format__
laozhang
__format__
<__main__.Student object at 0x102bc0c88>

__getattr__

当类的实例读取一个不存在的属性(包括类属性与实例属性)时,将会触发,如果存在将不触发。举个栗子:

class Student(object):
    
    def __init__(self, name):
        self.name = name
        
    def __getattr__(self, item):
        return "__getattr__"
        
s = Student("laowang")
print(s.name)
print(s.age)
print(getattr(s, "addr"))

############## 打印结果如下 ##############
laowang
__getattr__
__getattr__

__getattribute__

当使用类的实例读取该类的属性(包括类属性与实例属性)时,,将会触发,无论该属性存在与否。举个栗子:

class Student(object):
    
    def __init__(self, name):
        self.name = name
    
    def __getattribute__(self, item):
        return "__getattribute__"
        
s = Student("laowang")
print(s.name)
print(s.age)
print(getattr(s, "addr"))

############## 打印结果如下 ##############
__getattribute__
__getattribute__
__getattribute__

另外,如果同时重写了__getattribute__()__getattr__()方法,并且有自定义的返回,那么正常情况下__getattr__()方法不会再触发,除非显示调用或引发AttributeError错误!如下:

class Student(object):
    
    def __init__(self, name):
        self.name = name
    
    def __getattr__(self, item):
        return "__getattr__"
    
    def __getattribute__(self, item):
        return "__getattribute__"
        
s = Student("laowang")
print(s.name)
print(s.age)
print(getattr(s, "addr"))

############## 打印结果如下 ##############
__getattribute__
__getattribute__
__getattribute__

值得注意的是,无论是在__getattr__()或者__getattribute__(),都切记不要在方法内部调用self.item,否则将会进入递归循环,__getattribute__()中调用其他属性也会使自身进入递归循环!!!为了避免无限循环,我们可以获取属性的方法指向一个更高的超类,如下:

def __getattribute__(self, item):
    return super(Student, self).__getattribute__(item)

__setattr__

当使用类的实例为属性进行赋值时,将会触发。举个栗子:

class Student(object):
    
    def __init__(self, name):
        self.name = name
        
    def __setattr__(self, key, value):
        print("__setattr__")
        super(Student, self).__setattr__(key, value)
        
s = Student("laowang")
s.age = 20
setattr(s, "addr", "深圳")

############## 打印结果如下 ##############
__setattr__
__setattr__
__setattr__

值得注意的是,切勿在__setattr__()方法中操作self.key = value,这样将会进入递归循环!

__delattr__

当类的实例中的一个属性被删除时,将会触发(无论属性存不存在)。举个栗子:

class Student(object):
    
    def __init__(self, name):
        self.name = name
        
    def __delattr__(self, item):
        print("__delattr__")
        
s = Student("laowang")
delattr(s, "name")
delattr(s, "addr")

############## 打印结果如下 ##############
__delattr__
__delattr__

__dir__

dir()函数被调用是,将会触发。举个栗子:

class Student(object):
    
    def __init__(self, name):
        self.name = name
        
    def __dir__(self):
        print("__dir__")
        return super(Student, self).__dir__()
        
s = Student("laowang")
print(dir(s))

############## 打印结果如下 ##############
__dir__
['__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__', 'name']

__get__

如果class定义了它,则这个class就可以称为descriptor。owner是所有者的类,instance是访问descriptor的实例,如果不是通过实例访问的,而是通过类访问的,那么instance则为None。举个栗子:

class Teacher(object):
    
    name = "张老师"
    
    def __get__(self, instance, owner):
        print("__get__", instance, owner)
        return self
        
class Student(object):

    t = Teacher()
    
s = Student()
t = Teacher()

print(s.t)
print(Student.t)
print(s.t.name)
print(Student.t.name)
print(t)
print(t.name)

############## 打印结果如下 ##############
__get__ <__main__.Teacher object at 0x1033d24e0> <__main__.Student object at 0x1033d2518> <class '__main__.Student'>
<__main__.Teacher object at 0x1033d24e0>
__get__ <__main__.Teacher object at 0x1033d24e0> None <class '__main__.Student'>
<__main__.Teacher object at 0x1033d24e0>
__get__ <__main__.Teacher object at 0x1033d24e0> <__main__.Student object at 0x1033d2518> <class '__main__.Student'>
张老师
__get__ <__main__.Teacher object at 0x1033d24e0> None <class '__main__.Student'>
张老师
<__main__.Teacher object at 0x1033d2550>
张老师

通过打印结果可以得知,descriptor的实例访问自己是不会触发__get__方法的!

将上面代码修改成为如下:

class Teacher(object):
    
    name = "张老师"
    
    def __get__(self, instance, owner):
        print("__get__", instance, owner)
        return self
        
class Student(object):

    def __init__(self):
        self.t = Teacher()
        
s = Student()
print(s.t)
print(s.t.name)

############## 打印结果如下 ##############
<__main__.Teacher object at 0x1033ad748>
张老师

从结果可以得知,如果descriptor为某个类的实例属性,那么调用他将不会触发__get__方法

__set__

__set__方法的触发条件与__get__类似,举个栗子:

class Teacher(object):

    name = "张老师"
    
    def __set__(self, instance, value):
        print("__set__", self, instance, value)
        
class Student(object):

    t = Teacher()
    

s = Student()
s.t = "laowang"
Student.t = "laozhang"

############## 打印结果如下 ##############
__set__ <__main__.Teacher object at 0x102bad748> <__main__.Student object at 0x102bb2198> laowang

从打印结果可知,如果访问descriptor的是类的实例,会触发;如果是访问descriptor的是类,那么将不会触发!同样,如果descriptor为某个类的实例属性,访问时,同样也不会触发!即下面这种情况时不会触发的:

class Teacher(object):

    name = "张老师"
    
    def __set__(self, instance, value):
        print("__set__", self, instance, value)
        
class Student(object):
    
    def __init__(self):
        self.t = Teacher()
        
s = Student()
s.t = "laowang"

__delete__

__delete__方法的触发条件与__set__,调用删除所有者类的实例的属性触发。举个栗子:

class Teacher(object):
    
    name = "张老师"
    
    def __delete__(self, instance):
        print("__delete__", self, instance)
        
class Student(object):
    
    t = Teacher()
    
# del Student.t     # 不触发,
s = Student()
del s.t

############## 打印结果如下 ##############
__delete__ <__main__.Teacher object at 0x1033a0c50> <__main__.Student object at 0x1033a0c88>

同样,如果descriptor为某个类的实例属性,删除时,同样也不会触发!即下面这种情况时不会触发的:

class Teacher(object):

    name = "张老师"
    
    def __set__(self, instance, value):
        print("__set__", self, instance, value)
        
class Student(object):
    
    def __init__(self):
        self.t = Teacher()
        
s = Student()
del s.t

__lt__

定义小于号的行为,将会触发实例的__lt__()方法。举个栗子:

class Student(object):
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __lt__(self, other):
        print("__lt__", self.age, other.age)
        return self.age < other.age
        
s1 = Student("laowang", 18)
s2 = Student("laozhang", 20)
print(s1 < s2)

############## 打印结果如下 ##############
__lt__ 18 20
True

__gt__

定义大于号的行为,将会触发实例的__gt__()方法。举个栗子:

class Student(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __gt__(self, other):
        print("__gt__", self.age, other.age)
        return self.age > other.age
        
s1 = Student("laowang", 18)
s2 = Student("laozhang", 20)
print(s1 > s2)

############## 打印结果如下 ##############
__lt__ 18 20
False

再看如下栗子:

class Student(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __gt__(self, other):
        print("__gt__", self.age, other.age)
        return self.age > other.age
        
s1 = Student("laowang", 18)
s2 = Student("laozhang", 20)
print(s1 < s2)

############## 打印结果如下 ##############
__gt__ 20 18
True


class Student(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __lt__(self, other):
        print("__lt__", self.age, other.age)
        return self.age < other.age
        
s1 = Student("laowang", 18)
s2 = Student("laozhang", 20)
print(s1 > s2)

############## 打印结果如下 ##############
__lt__ 20 18
False


class Student(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __gt__(self, other):
        print("__gt__", self.age, other.age)
        return self.age > other.age
        
    def __lt__(self, other):
        print("__lt__", self.age, other.age)
        return self.age < other.age

s1 = Student("laowang", 18)
s2 = Student("laozhang", 20)
print(s1 > s2)
print(s1 < s2)

############## 打印结果如下 ##############
__gt__ 18 20
False
__lt__ 18 20
True

从栗子中可以看出,如果调用x < y,那么首先将会寻找x的__lt__()方法,如果x未重写它,那么将会寻找y的__gt__()方法!同理,如果调用x > y,将会首先寻找x的__gt__()方法,如果x未重写它,那么将会寻找y的__lt__()方法!!!

__ge__

定义大于或等于号的行为,将会触发__ge__()方法。举个栗子:

class Student(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __ge__(self, other):
        print("__ge__", self.age, other.age)
        return self.age >= other.age
        
s1 = Student("laowang", 18)
s2 = Student("laozhang", 20)
print(s1 >= s2)

############## 打印结果如下 ##############
__ge__ 18 20
False

__le__

定义小于或等于号的行为,将会触发__le__()方法。举个栗子:

class Student(object):
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __le__(self, other):
        print("__le__", self.age, other.age)
        return self.age <= other.age
        
s1 = Student("laowang", 18)
s2 = Student("laozhang", 20)
print(s1 <= s2)

############## 打印结果如下 ##############
__le__ 18 20
True

__gt__()__lt__()方法一样,如果调用x <= y,首先会寻找x的__le__()方法,如果x未重写它,那么将会寻找y的__ge__()方法!同理,如果调用x >= y,将会首先寻找x的__ge__()方法,如果x未重写它,那么将会寻找y的__le__()方法!!!

__eq__

定义等于号的行为,即当使用==操作时,将会触发__eq__,举个栗子:

class Student(object):

    def __eq__(self, other):
        print("__eq__")
        return self.__dict__ == other.__dict__
        
s1 = Student()
s2 = Student()
print(s1 == s2)     # 触发__eq__
print(s1 is s2)     # 不触发

############## 打印结果如下 ##############
__eq__
True
False

__ne__

定义不等于号的行为,即当使用!=操作时,将会触发__ne__,举个栗子:

class Student(object):

    def __ne__(self, other):
        print("__ne__")
        return self.__dict__ == other.__dict__
        
s1 = Student()
s2 = Student()
print(s1 != s2)     # 触发__eq__
print(s1 is not s2) # 不触发

############## 打印结果如下 ##############
__ne__
True
True

未完待续~~~

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值