分享

Python-魔法方法详解(上)

 风声之家 2019-07-10

作者 | 奔跑的豆子_
来源 | CSDN博客

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__

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 y、x == y和hash(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

原文:
https://blog.csdn.net/y472360651/article/details/94580032

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多