Python的魔法方法一

Python的魔法方法一

什么是魔法方法

魔法方法在python的官方文档里叫special method,而magic method这个词是没有出现过的,所谓的魔术方法,就是让用户课制化一个类的方式,顾名思义,定义在类里的一些方法,特点,就是这些方法的前后,都有两个下划线

new和init

这两个方法,是可以改从一个类创建一个对象的行为,也比较容易被搞混。
new是从一个class建立一个object的过程
init是有了这个object后,给这个object初始化的过程
举个例子

class A:
    def __new__(cls):
        print("new")
        return super().__new__(cls)

    def __init__(self):
        print("init")


o = A()

结果

new
init

上面这个类,就是new和init都用了,你可以粗暴的理解成o = A()的时候,先把这个class作为argument传给了new函数,返回一个object,然后这个object作为变量,去调用init函数。所以说
new是有返回值的,就是那个object
init是没有返回值的

如果我们在建立这个object的时候,传入了一些参数,那么这个参数,即会被存在new里,也会被存在init里

class A:
    def __new__(cls, x):
        print("new")
        return super().__new__(cls)

    def __init__(self, x):
        self.x = x
        print("init")


o = A(1)

在我们的实际应用中,这个new函数,是用的相对不多的,如果你不需要客制化建立这个object的过程,你只需要初始化这个object,那你只需要用到这个init,那什么时候需要用到new呢,比如说完哦要做一个singleton(单例) class,也就是说,在建立一个class的object之前,我首先要判断一下,我首先要判断一下有没有其他的object已经被建立了,如果有,那我就不创建新的了,这里我们就是在客制化建立这个object的过程,才会需要用到new,包括一些跟meataclass相关的内容,也会用到new。

init函数传入的这个self,就是你要初始化的对象,也就是那个object,init函数就该基本上折腾这个self就可以了

del

这个del,可以理解成一个析构函数,当然,他不是,当这个对象被释放的时候,你想干点什么

class A:
    def __new__(cls):
        print("new")
        return super().__new__(cls)

    def __init__(self):
        print("init")

    def __del__(self):
        print("del")

    def hello(self, name):
        print(f"hello{name}")

    def bug(self, name):
        print(f"bug{name}")


o = A()
o.hello("lisi")
o.bug("zhangsan")

结果

new
init
hellolisi
bugzhangsan
del

我们发现,当这个对象已经不在调用什么方法的时候,也就是引用数为0的时候,他就要被释放了,但是这个方法并不是那么的好用,因为在垃圾回收的时候,对象也可能会被释放,这个释放会发生在任何时候。
同时呢,python里还有一个del关键字,这两个完全不是一个东西哈,在python中,del o的时候,并不一定会触发del魔法函数。del o只是让这个对象少了一个引用

class A:

    def __del__(self):
        print("del")


o = A()
x = o
del o
print("finish")

结果

finish
del

我们faxing运行结果,是先打印的finish,而不是先打印del,也就是这个del o并没有触发del魔法函数,这是因为我们在做del o的时候,这个object身上还有一个x的引用,接下来这个例子更清晰一点。

class A:

    def __del__(self):
        print("del")

    def hello(self):
        print("hello")


o = A()
x = o
del o
print("finish")
x.hello()

结果

finish
hello
del

eq、ne

print([1, 2] == [1, 2])
print(2 > 5)
print('a' >= 'b')

结果

True
False
False

我们看上面的这些,用的都是内置的比较函数,但是如果我们要比较类呢

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day


x = Date(2024, 3, 27)
y = Date(2024, 3, 27)
print(x == y)

结果是False
因为在这里的 == ,其实比较的是x和y的地址,在python中当你没有去写一个类的比较逻辑的时候,它默认比较两个object是否相等的方法是is,本质上是打印了一个x is y,那我们可以通过eq函数来重新定义类的相等。

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    # self就是x,other就是y
    def __eq__(self, other):
        print("eq")
        return self.year == other.year and self.month == other.month and self.day == other.day


x = Date(2024, 3, 27)
y = Date(2024, 3, 27)
print(x == y)
print("------------")
print(x != y)

结果

eq
True
------------
eq
False

我们发现,这样不仅是 == 被eq改变了,连!=也被eq改变了,那是因为python会自动将eq的方法取反来获取!=的结果,所以!=也调用了eq方法.
看起来我们应该返回一个boolean,其实我们可以返回其他的东西,比如字符串,这是python灵活的地方。

当然了我们也可以自己定义不等于方法

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    # self就是x,other就是y
    def __eq__(self, other):
        print("eq")
        return self.year == other.year and self.month == other.month and self.day == other.day

    def __ne__(self, other):
        print("ne")
        return self.year != other.year or self.month != other.month or self.day != other.day

x = Date(2024, 3, 27)
y = Date(2024, 3, 27)
print(x == y)
print("------------")
print(x != y)

结果

eq
True
------------
ne
False

gt、lt

上面的这些,x和y都是对称的,也就是可以用等于和不等于来判断的,那如果遇到了不对称的呢?需要用大于小于来比较的,我们知道等于和不等于是有默认实现的,但是大于小于可没有

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    # self就是x,other就是y
    def __eq__(self, other):
        print("eq")
        return self.year == other.year and self.month == other.month and self.day == other.day

    def __ne__(self, other):
        print("ne")
        return self.year != other.year or self.month != other.month or self.day != other.day


x = Date(2024, 3, 27)
y = Date(2024, 3, 27)
print(x > y)

会报错的

Traceback (most recent call last):
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 19, in <module>
    print(x > y)
TypeError: '>' not supported between instances of 'Date' and 'Date'

那我们就需要自己来实现这个功能

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __gt__(self, other):
        if self.year > other.year:
            return True
        if self.year == other.year:
            if self.month > other.month:
                return True
            if self.month == other.month:
                return self.day > other.day


x = Date(2024, 3, 28)
y = Date(2024, 3, 27)
print(x > y)
print(x < y)

结果

True
False

我们发现,只实现了大于呀,怎么小于的也有了呢,因为x和y是同一种object,x>y就等于y>x;python会自己找的。当然了,我们也可以自己写小于的方法

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __gt__(self, other):
        print("gt")
        if self.year > other.year:
            return True
        if self.year == other.year:
            if self.month > other.month:
                return True
            if self.month == other.month:
                return self.day > other.day

    def __lt__(self, other):
        print("lt")
        if self.year < other.year:
            return True
        if self.year == other.year:
            if self.month < other.month:
                return True
            if self.month == other.month:
                return self.day < other.day


x = Date(2024, 3, 28)
y = Date(2024, 3, 27)
print(x > y)
print(x < y)

结果

gt
True
lt
False

那我们考虑一个问题,是因为我们实现了小于方法,所以就调用了lt吗

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __gt__(self, other):
        print("gt")
        if self.year > other.year:
            return True
        if self.year == other.year:
            if self.month > other.month:
                return True
            if self.month == other.month:
                return self.day > other.day

    def __lt__(self, other):
        print("lt")
        if self.year < other.year:
            return True
        if self.year == other.year:
            if self.month < other.month:
                return True
            if self.month == other.month:
                return self.day < other.day


class NewDate(Date):
    pass


x = Date(2024, 3, 28)
y = NewDate(2024, 3, 27)
print(x < y)

结果

gt
False

NewDate继承自Date,什么都没做,当x<y的时候,其实执行判断的是y>x,而不是x<y

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __str__(self):
        # 通过str魔法函数,返回自身的信息
        return f"{self.year}/{self.month}/{self.day} "

    def __eq__(self, other):
        print("eq")
        # 在eq函数中按照顺序将self和other打印出来,在这里用到了上面的str函数
        print(self, other)
        return self.year == other.year and self.month == other.month and self.day == other.day

    def __gt__(self, other):
        print("gt")
        if self.year > other.year:
            return True
        if self.year == other.year:
            if self.month > other.month:
                return True
            if self.month == other.month:
                return self.day > other.day

    def __lt__(self, other):
        print("lt")
        if self.year < other.year:
            return True
        if self.year == other.year:
            if self.month < other.month:
                return True
            if self.month == other.month:
                return self.day < other.day


class NewDate(Date):
    pass


x = Date(2024, 3, 28)
y = NewDate(2024, 3, 27)
print(x == y)

结果

eq
2024/3/27  2024/3/28 
False

不对呀,为什么对比的顺序是 y==x呢,而不是x == y
而如果x和y都是一样的Object

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __str__(self):
        # 通过str魔法函数,返回自身的信息
        return f"{self.year}/{self.month}/{self.day} "

    def __eq__(self, other):
        print("eq")
        # 在eq函数中按照顺序将self和other打印出来
        print(self, other)
        return self.year == other.year and self.month == other.month and self.day == other.day

    def __gt__(self, other):
        print("gt")
        if self.year > other.year:
            return True
        if self.year == other.year:
            if self.month > other.month:
                return True
            if self.month == other.month:
                return self.day > other.day

    def __lt__(self, other):
        print("lt")
        if self.year < other.year:
            return True
        if self.year == other.year:
            if self.month < other.month:
                return True
            if self.month == other.month:
                return self.day < other.day


class NewDate(Date):
    pass


x = Date(2024, 3, 28)
y = Date(2024, 3, 27)
print(x == y)

结果

eq
2024/3/28  2024/3/27 
False

综合上面两个例子来看,如果x和y不是一个object,你就可能会遇到一些麻烦,这里的规则是,如果y是x的衍生类,那就优先使用y的比较方法,否则就优先使用x的比较方法,大部分情况下,优先使用左面的那个类,如果左边的没有,再去找右边的那个类的函数,除非右边的类是左边的类的子类。

同时呢,我们需要注意一下,x<y or x=y并不等于x<=y,也就是说python并不会在你有gt和eq的时候,并不会自动推断出x<=y可以拆解为gt和eq两个函数。

hash

我们有可能想去求某一个数据结构的hash值,一个自定义的数据结构,是有它默认的hash算法的。

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day


x = Date(2024, 3, 28)
print(hash(x))

结果 272861618
我们最常见的hash的用法就是将一个数据结构的对象做为key,放到一些hashtable里,比如说字典比如说set

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __repr__(self):
        return f"{self.year}/{self.month}/{self.day}"


x = Date(2024, 3, 28)
y = Date(2024, 3, 28)

# 记录我每天赚了多少钱
income = {}
income[x] = 1000
income[y] = 1000
print(income)

结果

{2024/3/28: 1000, 2024/3/28: 1000}

我们发现啊,这个x和y虽然是一样的,但是在dict里,还是展示成了两个,其实根源是他们就是两个对象,那可能有的人想,简单啊,把上面的eq函数拿过来不就行了

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __eq__(self, other):
        return self.year == other.year and self.month == other.month and self.day == other.day

    def __repr__(self):
        return f"{self.year}/{self.month}/{self.day}"


x = Date(2024, 3, 28)
y = Date(2024, 3, 28)

# 记录我每天赚了多少钱
income = {}
income[x] = 1000
income[y] = 1000
print(income)

结果

Traceback (most recent call last):
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 19, in <module>
    income[x] = 1000
TypeError: unhashable type: 'Date'

报错了,说我们这个Data的类型是unhashable,也就是不可hash的,因为python虽然对每一个数据结构都默认了eq方法,以及一个hash函数,但是如果你对这个自定义类定义了自己的eq函数,这个默认的hash函数就会被删除掉,如果你还想对其使用hash方法,就必须自定义自己的hash函数。
hash的基本定义就是如果两个对象相等,他们的hash一定相等,当你改变了eq函数,他们的hash函数显然就不对了,所以如果我们还想要把这个类放在dict里的话,就必须在定义了新的eq的同时,也定义新的hash

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __eq__(self, other):
        return self.year == other.year and self.month == other.month and self.day == other.day

    def __repr__(self):
        return f"{self.year}/{self.month}/{self.day}"

    def __hash__(self):
        # 第一,必须返回一个整数
        # 第二,对于两个相等的对象,必须有同样的一个hash值
        # 第三,python推荐你使用官方提供的hash方法,通过把对象的核心属性组成一个tuple放到这个方法里,来返回你的对象的hash值
        return hash((self.year, self.month, self.day))


x = Date(2024, 3, 28)
y = Date(2024, 3, 28)

# 记录我每天赚了多少钱
income = {}
income[x] = 1000
income[y] = 1000
print(income)

这样就是

{2024/3/28: 1000}

还有一个需要注意的是,如果你的对象是一个mutable(可变)的,就不应该作为key放在dict里,当然对于所有python object来说,都是mutable的,这里指的是这个类你创建了之后是不是还会变更。

bool

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day


y = Date(2024, 3, 28)
# 当你把一个对象放在if statement里的时候,得到的都会是True
if y:
    print("hello")

那怎么改变呢,就是说我怎么自己定义一个对象是True还是False呢

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __repr__(self):
        return f"{self.year}/{self.month}/{self.day}"

    def __bool__(self):
        print("bool")
        # 一定要返回一个boolean值
        return False


y = Date(2024, 3, 28)
print(bool(y))
if y:
    print("hello")

结果

bool
False
bool
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值