【无标题】 痛定思痛。 2024-05-24 13:04 40阅读 0赞 Python中对于一个类来说,有着非常多的魔法方法(以\_\_xxx\_\_方法是进行定义的),这些方法在Python解释器中会被特殊的事件所触发调用。比如比较对象大小,实例对象的创建等很多重要时刻,对应的魔法方法都会被解释器调用。但并不是当我们自己编写一个类的时候,这些魔法方法都需要被重写(object这个基类已经默认写好了这些魔法方法,通常情况下我们都不需要去重写)。不过了解这些魔法方法会加深你对Python解释器在特定时刻到底是如何执行的有很大帮助。 ### 1、创建和访问相关的魔法方法 ### #### 1.1、\_\_init\_\_(self,参数1, 参数2, ...) #### 在创建实例后,会立刻执行\_\_init\_\_方法,对实例进行初始化。这是我们创建自定义类时最常用的魔法方法,并且也是基本上都会重写的魔法方法。下面是一个最简单的例子: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name</code><code>=</code><code>"未命名"</code><code>):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p><code> </code> </p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(</code><code>12</code><code>)</code></p> <p><code>print</code><code>(f</code><code>"boy1的名字是:{boy1.name}, 年龄: {boy1.age}"</code><code>)</code></p> <p><code>print</code><code>(f</code><code>"boy2的名字是:{boy2.name}, 年龄: {boy2.age}"</code><code>)</code></p> </td> </tr> </tbody> </table> 执行结果是: ![0923aa15ff23ec0d79efb408734467fb.png][] 上例中我们在\_\_init\_\_方法中,设定了一个关键字参数age,一个带默认值的关键字参数name。在我们创建MyClass实例对象是就会进入到该方法中,例如第6行我们创建了一个姓名为"小明",年龄为14的对象。第7行我们并没有给出参数name的值,因此创建boy2时name为默认的“未命名”,而12对应的位置参数为age,因此该对象的age被赋值为12。 \_\_init\_\_方法相信大家在实际编程中都经常使用了,但我这里需要注意的是\_\_init\_\_是用于对象初始化时调用的,可以理解为我们已经创建了一个空白的对象,而\_\_init\_\_方法仅仅是给这个空白对象上增加了属于他独特的部分(创建属性值...),因此\_\_init\_\_方法不应该也不需要有任何返回值的。(如果你在该方法中return xxx,会得到系统报错,提示 TypeError) #### 1.2、\_\_new\_\_(cls, 参数1, 参数2, ...) #### 前文提到\_\_init\_\_其实只是对一个“空白”对象进行属性的初始化,那我们可以推测总归会有一个方法会在\_\_init\_\_之前调用,并返回一个"空白"对象吧。没错!这个魔法方法就是\_\_new\_\_。该魔法方法永远是静态的,并且不需要显式的装饰(通常类的静态方法都需要@staticmethod进行装饰,才能作为静态方法进行使用)。\_\_new\_\_的第一个参数也是最重要的一个参数就是创建实例所需得类(按照惯例是一般采用cls作为指代),而其余的参数则会原封不动的传递给\_\_init\_\_,最后会必须要返回一个创建好的对象,并且这个对象一定要是cls这个类的对象(因为\_\_new\_\_返回的对象会交给\_\_init\_\_进行初始化;如果返回的不是cls类的对象,那么就不会触发cls类的\_\_init\_\_方法)。 一般来说我们并不需要去自定义这个\_\_new\_\_方法,因为这个方法本质上只是创建一个空白的类对象(通过调用父类的\_\_new\_\_方法进行的)。如果我们非得要重写该方法,我们一般也需要调用父类的\_\_new\_\_方法来得到一个空白对象。例如下例子: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__new__(</code><code>cls</code><code>, age, name</code><code>=</code><code>"未命名1"</code><code>):</code></p> <p><code> </code><code>print</code><code>(</code><code>"进入了__new__方法"</code><code>)</code></p> <p><code> </code><code>instance </code><code>=</code> <code>super</code><code>().__new__(</code><code>cls</code><code>)</code></p> <p><code> </code><code>return</code> <code>instance</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name</code><code>=</code><code>"未命名2"</code><code>):</code></p> <p><code> </code><code>print</code><code>(</code><code>"进入了__init__"</code><code>)</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p><code> </code> </p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> </td> </tr> </tbody> </table> 输出结果是: ![7f9e6ae7d608b59e325e154397c1ef4c.png][] 从这里结果我们可以看到,当我们创建实例时,首先进入的就是\_\_new\_\_方法,第5行中调用了父类的\_\_new\_\_方法创建了一个实例(注意本例中的父类是object,其\_\_new\_\_方法只接收cls一个参数),得到的实例对象被直接return了。与此同时,age,name参数被原封不动的传递给了\_\_init\_\_,也就是将"小明"传递给了\_\_init\_\_中的name参数,14传递给了\_\_init\_\_中的age参数。这里我们需要注意的是在\_\_new\_\_中我们将name属性默认定义为"未命名1",而在\_\_init\_\_中name属性的默认定义为"未命名2",大家可以猜想下如果我们使用MyClass(12),进行创建对象,那该对象的name属性是多少?结果如下: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> </td> <td> <p><code>boy2 </code><code>=</code> <code>MyClass(</code><code>12</code><code>)</code></p> <p><code>print</code><code>(f</code><code>"boy2的名字是:{boy2.name}, 年龄: {boy2.age}"</code><code>)</code></p> </td> </tr> </tbody> </table> 结果如下: ![65862818740ddea7792a97f0928db7ee.png][] 可以看到,此时最终的结果boy2的name其实是按照\_\_init\_\_中所指定的。通过debug可以知道,其实执行完\_\_new\_\_后,会回到MyClass(12)该行,并且只会将12传入给\_\_init\_\_函数。因此实际上\_\_new\_\_的参数根本不会影响到\_\_init\_\_的收集参数,就算你在\_\_new\_\_中把name的数值改了,也没有任何影响。(从静态方法的角度来看会更清晰,因为\_\_new\_\_是一个静态方法,里面执行的任何操作都不会影响到对象)。 #### 1.3、\_\_getattr\_\_(self,attr\_name) #### 当我们试图访问一个对象不存在的属性时就会触发\_\_getattr\_\_()方法的调用。在创建实例对象后,我们可以通过 对象.属性名 的方式访问这个对象的对应属性。如果某个对象并不具有这个属性那么就会触发AttributeError异常,此时如果该类自定义了\_\_getattr\_\_()方法,那么编译器将不会报异常,并开始执行\_\_getattr\_\_()。\_\_getattr\_\_()方法有两个位置参数,第一个是self,第二个是属性名,也就是你想访问的属性的属性名,注意attr\_name的类型是字符串类型。 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>## 当访问对象属性时,如果出现异常(AttributeError)会执行该函数</code></p> <p><code> </code><code>def</code> <code>__getattr__(</code><code>self</code><code>, item):</code></p> <p><code> </code><code>print</code><code>(</code><code>type</code><code>(item))</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{item}属性访问异常, 不存在属性{item}"</code><code>)</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy1.day</code></p> </td> </tr> </tbody> </table> 我们在根据上文的代码稍作调整,我们创建了一个类,并重写了其中的\_\_getattr\_\_()方法,当我们对boy1对象的day属性进行访问时,由于boy1对象种并没有这个属性,因此会进入到\_\_getattr\_\_(),打印出两句话,运行结果如下: ![6758cc290245f1e0859054a85d0ab27a.png][] \_\_getattr\_\_方法捕获了我们想要访问但不存在的属性名,一般是当"常规方法无法访问属性时"会执行这个魔法函数。 #### 1.4、\_\_getattribute\_\_(self,attr\_name) #### 该魔法方法其实和\_\_getattr\_\_非常类似,\_\_getattr\_\_是在“常规方法”无法访问属性时才会被执行,而\_getattribute\_\_()方法是无条件的执行,只要程序中需要访问属性时都会被执行。 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> <p>16</p> <p>17</p> <p>18</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>## 在访问对象属性时会被无条件调用</code></p> <p><code> </code><code>def</code> <code>__getattribute__(</code><code>self</code><code>, __name: </code><code>str</code><code>):</code></p> <p><code> </code><code>attr_value </code><code>=</code> <code>object</code><code>.__getattribute__(</code><code>self</code><code>, __name) </code><code>## 注意此处必须执行采用执行基类的方法来得到属性值,不能使用self.name, 会产生死循环</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{__name}属性被访问, 其值为:{attr_value}"</code><code>)</code></p> <p><code> </code><code>return</code> <code>attr_value</code></p> <p></p> <p><code> </code><code>def</code> <code>__getattr__(</code><code>self</code><code>, item):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{item}属性访问异常, 不存在属性{item}"</code><code>)</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy1.age</code></p> <p><code>boy1.day</code></p> </td> </tr> </tbody> </table> 我们看上面这个例子,在我们自定义类中增加了\_\_getattribute\_\_()方法,当有属性被访问时,该方法打印一句话也就是第10行代码执行的内容。最终执行的结果如下: ![6ee7038e6375f2f1e64c082810d22f55.png][] 确实得到了我们想要的结果,这里我们需要注意两点。第一:\_\_getattribute\_\_()应该有返回值,而且是应该是对应的属性的值,否则我们永远也无法得到属性的值。第二点:我们在\_\_getattribute\_\_()函数体中一定不要出现使用常规的方法去访问属性,因为这会使得程序进入死循环。其实这就是上例中我们在第9行中做的操作。要记住只要我们使用 self.属性名 就是在访问属性,就会进入到\_\_getattribute\_\_()方法中,因此如果你将第9行的代码改为: attr\_value = self.\_\_name,那么上面的代码将会进入死循环直到栈溢出(大家可以自行尝试)。 为了解决上面的问题,我们就需要调用基类的\_\_getattribute\_\_方法,或者使用父类的\_\_getattribute\_\_方法去访问,分别是以下两种代码: * super().\_\_getattribute\_\_(\_\_name):调用父类的\_\_getattribute\_\_方法,并将被访问的属性名传入进去 * object.\_\_getattribute\_\_(self, \_\_name): 调用基类object的\_\_getattribute\_\_方法,不过需要传入self指定对象 一般情况下,我们并不需要重写\_\_getattribute\_\_(),因为该方法极容易出现死循环。再提一句,其实上面的例子中当我们试图访问boy1.day时,我们首先进入的还是\_\_getattribute\_\_()方法只不过,当执行到9行时,由于self中不存在day属性,因此在第9行会抛出AttributeError,此时编译器才会去执行\_\_getattr\_\_方法。换句话说我们再理解下\_\_getattr\_\_方法,其实就是当\_\_getattribute\_\_()方法中抛出了AttributeError,才会去执行\_\_getattr\_\_。基于此我们可以写出以下代码,那么我们将永远无法访问到类中的属性了。 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>def</code> <code>__getattribute__(</code><code>self</code><code>, __name: </code><code>str</code><code>):</code></p> <p><code> </code><code>raise</code> <code>AttributeError(</code><code>"所有属性均不存在"</code><code>)</code></p> <p></p> <p><code> </code><code>def</code> <code>__getattr__(</code><code>self</code><code>, item):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{item}属性访问异常, 不存在属性{item}"</code><code>)</code></p> </td> </tr> </tbody> </table> #### 1.5、\_\_setattr\_\_(self,attr\_name,attr\_value) #### 该魔法方法当你为一个属性/对象进行赋值操作(xxx = xxx)时会被调用。其中attr\_name会捕获我们想要对哪个属性进行赋值,attr\_value会捕获赋值内容,我们看下面这个例子。 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>## 当给对象属性进行赋值时,会进入到该函数</code></p> <p><code> </code><code>def</code> <code>__setattr__(</code><code>self</code><code>, name, value):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"属性{name}正在执行__setattr__, 值为{value}"</code><code>)</code></p> <p><code> </code><code>object</code><code>.__setattr__(</code><code>self</code><code>, name, value)</code></p> <p><code> </code> </p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy1.age </code><code>=</code> <code>18</code></p> </td> </tr> </tbody> </table> 这段代码的执行结果是: ![ed2eb9e3c63bd8c9afd12320b50d0796.png][] 有些读者可能会出现疑惑,为什么会打印第一句和第二句?我们不是只对age进行了赋值18的操作么?应该只打印最后一句才对。其实第1句和第2句的打印时在对象初始化时,也就是上面代码的第4和第5行。第4和第5行是对象实例化时进行的初始化操作,而这个初始化操作是赋值操作,因此也会进入到\_\_setattr\_\_中,这就是为什么前两句会被打印的原因。 下面我们继续讨论在上述类的定义下,我们是否可以给不存在的属性进行赋值操作呢?我们执行以下代码 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> </td> <td> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy1.day </code><code>=</code> <code>18</code></p> <p><code>print</code><code>(boy1.day)</code></p> </td> </tr> </tbody> </table> 我们在第2行对boy1的day属性进行了赋值,并且在第3行对boy1对象的day属性进行了访问,并打印其值。要知道boy1是没有day属性的,那第2行会不会报错呢?最终结果如下: ![115f9013845285e272a0eed1912a4148.png][] 我们可以看到在代码的第2行中对boy1不存在的属性day进行赋值时可行的,并且赋值之后就可以对这个属性进行访问了。其实对对象的不存在的属性进行赋值本身就是被允许的(即使我们不重写\_\_setattr\_\_方法,也是被允许的)。不过需要注意的是对于不存在的属性必须要先进行赋值才能被访问,如果直接就访问不存在的属性,将会直接爆出AttributeError异常。 其实\_\_setattr\_\_()与\_\_getattribute\_\_()一样,只要是赋值操作就会被调用,因此该方法也非常容易造成死循环(当你在\_\_setattr\_\_()中试图使用 self.attr = xxx 时就又会触发死循环),具体的解决死循环的方式与\_\_getattribute\_\_()一样,就是采用基类的方式对属性进行赋值,这也就是我在例子中第10行所采用的方法,使用 object.\_\_setattr\_\_(self, name, value) 对属性进行赋值。 ### 2、类型转换相关的魔法方法 ### #### 2.1、\_\_str\_\_(self) #### 这是也是一个最常重写的魔法方法,\_\_str\_\_方法会执行str(对象)时被调用,也就是将对象转化成字符串时,该魔法方法会被调用。该魔法方法必须有一个字符串类型的返回值。直接看例子吧。 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>def</code> <code>__str__(</code><code>self</code><code>):</code></p> <p><code> </code><code>return</code> <code>f</code><code>"我的名字是是{self.name}, 年龄是{self.age}"</code></p> <p><code> </code> </p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小华"</code><code>, age</code><code>=</code><code>15</code><code>)</code></p> <p><code>print</code><code>(boy1)</code></p> <p><code>print</code><code>(</code><code>str</code><code>(boy2))</code></p> </td> </tr> </tbody> </table> 第10行和第11行我们创建了两个对象,并且在第12行和第13行打印了这两个对象,由于print会自动调用str(),所以最后输出的结果是: ![7d1c7c3086e9eea797566060c8f8d6a4.png][] #### 2.2、\_\_bool\_\_(self) #### 这个魔法函数与\_\_str\_\_类似,会在执行bool(对象)时被调用,当然如果直接把对象放在 if 后面作为条件判断,也相当于是 if bool(对象) :, 我们可以看下面这个例子。 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> <p>16</p> <p>17</p> <p>18</p> <p>19</p> <p>20</p> <p>21</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>def</code> <code>__bool__(</code><code>self</code><code>):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{self.name}的__bool__被调用"</code><code>)</code></p> <p><code> </code><code>if</code> <code>self</code><code>.age ></code><code>=</code> <code>18</code><code>:</code></p> <p><code> </code><code>return</code> <code>True</code></p> <p><code> </code><code>else</code><code>:</code></p> <p><code> </code><code>return</code> <code>False</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小华"</code><code>, age</code><code>=</code><code>19</code><code>)</code></p> <p></p> <p><code>if</code> <code>boy1:</code></p> <p><code> </code><code>print</code><code>(</code><code>"进入了if"</code><code>)</code></p> <p><code>else</code><code>:</code></p> <p><code> </code><code>print</code><code>(</code><code>"没有进入if"</code><code>)</code></p> <p><code>print</code><code>(</code><code>bool</code><code>(boy2))</code></p> </td> </tr> </tbody> </table> 我们重写了\_\_bool\_\_方法,当对象的age属性大于等于18时返回True,否则返回False,最后的执行结果如下: ![ff7752521f4f53600dcb8a60eb8568e1.png][] 可以看到由于对象小明的age 时小于18的,因此if条件判断进入了else分支。同时我们还打印了对象小华执行bool()函数的结果,是True,这与我们想象的是一致的。 #### 2.3、\_\_int\_\_(self)、\_\_float\_\_(self) #### 这两个魔法方法与前面两个方法一样,都是会在指定的转换函数是触发调用。从名字中就可以看出\_\_int\_\_会在int(对象)时调用,\_\_float\_\_会在float(对象)时被调用。需要注意的是这两个魔法方法的返回值都一定要符合各自的要求,比如\_\_int\_\_的返回值必须是一个int类型。 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> <p>16</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__int__(</code><code>self</code><code>):</code></p> <p><code> </code><code>print</code><code>(</code><code>"__int__被调用"</code><code>)</code></p> <p><code> </code><code>return</code> <code>int</code><code>(</code><code>self</code><code>.age)</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小华"</code><code>, age</code><code>=</code><code>19</code><code>)</code></p> <p></p> <p><code>print</code><code>(</code><code>int</code><code>(boy1))</code></p> <p><code>print</code><code>(</code><code>int</code><code>(boy2))</code></p> </td> </tr> </tbody> </table> 运行结果是: ![6b3f715d3c95ccd9129af7e3a396d6cc.png][] ### 3、比较与操作符重载的魔法方法 ### #### 3.1、\_\_eq\_\_(self, other)、\_\_ne\_\_(self,other) #### 当使用==符号时,将会被调用对象的\_\_eq\_\_方法。需要注意的是,我们会率先调用==左边对象的\_\_eq\_\_方法,如果左边对象没有定义\_\_eq\_\_方法,才会调用右边对象的\_\_eq\_\_方法,我们看下面的例子. <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> <p>16</p> <p>17</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, name, age):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>def</code> <code>__eq__(</code><code>self</code><code>, other) </code><code>-</code><code>> </code><code>bool</code><code>:</code></p> <p><code> </code><code>print</code><code>(</code><code>self</code><code>.name, </code><code>"的__eq__被调用"</code><code>)</code></p> <p><code> </code><code>if</code> <code>isinstance</code><code>(other, MyClass) </code><code>and</code> <code>self</code><code>.age </code><code>=</code><code>=</code> <code>other.age:</code></p> <p><code> </code><code>return</code> <code>True</code></p> <p><code> </code><code>else</code><code>:</code></p> <p><code> </code><code>return</code> <code>False</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小华"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy3 </code><code>=</code> <code>object</code><code>()</code></p> <p><code>print</code><code>(boy1 </code><code>=</code><code>=</code> <code>boy2)</code></p> <p><code>print</code><code>(boy3 </code><code>=</code><code>=</code> <code>boy2)</code></p> </td> </tr> </tbody> </table> 我们在\_\_eq\_\_方法中判断两个对象的年龄是否相等,如果相等就返回Treu,否则返回False(如果判断的对象类型不是MyClass实例,则返回False)。结果如下: ![94574f2a0b0f7b6d60d17fdc127f5650.png][] 在16行时我们其实是在调用boy1.\_\_eq\_\_(boy2),而在第17行,由于boy3是空对象,没\_\_eq\_\_方法,那么我们将调用boy2.\_\_eq\_\_(boy3)。因此Python并没有要求我们必须要保证==的可交换性。 对于\_\_ne\_\_,其实就是在执行 != 时调用的,一般来说我们不需要单独定义这个魔法方法。因为如果我们定义了\_\_eq\_\_方法,那么Python会自动执行\_\_eq\_\_方法,并对得到的结果取反返回。 #### 3.2、\_\_lt\_\_(self, other)、\_\_le\_\_(self, other)、\_\_gt\_\_(self, other)、\_\_ge\_\_(self, other) #### 这几个魔法方法分别时在<,<=,>,>=,时被调用。通常来说我们不需要对上述所有魔法方法都进行定义,因为Python解释器会认为\_\_lt\_\_方法(<)是\_\_ge\_\_方法(>=)的取反,\_\_gt\_\_(>)是\_\_le\_\_(<=)方法的取反。而同样的Python解释器会认为\_\_le\_\_(<=)方法是由\_\_lt\_\_(<)和\_\_eq\_\_(==)分离得到的。这就意味着我们通常只需要定义\_\_eq\_\_、\_\_lt\_\_、\_\_gt\_\_就可以让上述的四种方法正常工作(除非你不想让他们具有这样的逻辑关系)。 其实定义了这相对比较的魔法方法之后,我们就可以实现自定义的排序了,比如我们想使用 列表.sort()进行排序,那么会自动调用列表元素中的这些比较方法,比如下面这个例子。 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> <p>16</p> <p>17</p> <p>18</p> <p>19</p> <p>20</p> <p>21</p> <p>22</p> <p>23</p> <p>24</p> <p>25</p> <p>26</p> <p>27</p> <p>28</p> <p>29</p> <p>30</p> <p>31</p> <p>32</p> <p>33</p> <p>34</p> <p>35</p> <p>36</p> <p>37</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, name, age):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>def</code> <code>__eq__(</code><code>self</code><code>, other) </code><code>-</code><code>> </code><code>bool</code><code>:</code></p> <p><code> </code><code>print</code><code>(</code><code>self</code><code>.name, </code><code>"的__eq__被调用"</code><code>)</code></p> <p><code> </code><code>if</code> <code>isinstance</code><code>(other, MyClass) </code><code>and</code> <code>self</code><code>.age </code><code>=</code><code>=</code> <code>other.age:</code></p> <p><code> </code><code>return</code> <code>True</code></p> <p><code> </code><code>else</code><code>:</code></p> <p><code> </code><code>return</code> <code>False</code></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__lt__(</code><code>self</code><code>, other):</code></p> <p><code> </code><code>if</code> <code>self</code><code>.age < other.age:</code></p> <p><code> </code><code>return</code> <code>True</code></p> <p><code> </code><code>else</code><code>:</code></p> <p><code> </code><code>return</code> <code>False</code></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__gt__(</code><code>self</code><code>, other):</code></p> <p><code> </code><code>if</code> <code>self</code><code>.age > other.age:</code></p> <p><code> </code><code>return</code> <code>True</code></p> <p><code> </code><code>else</code><code>:</code></p> <p><code> </code><code>return</code> <code>False</code></p> <p></p> <p><code> </code><code>def</code> <code>__repr__(</code><code>self</code><code>) </code><code>-</code><code>> </code><code>str</code><code>:</code></p> <p><code> </code><code>return</code> <code>f</code><code>"MyClass(name={self.name}, age={self.age})"</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小华"</code><code>, age</code><code>=</code><code>13</code><code>)</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy3 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小中"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy4 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小五"</code><code>, age</code><code>=</code><code>12</code><code>)</code></p> <p><code>boy5 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小星"</code><code>, age</code><code>=</code><code>10</code><code>)</code></p> <p><code>boy6 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小利"</code><code>, age</code><code>=</code><code>16</code><code>)</code></p> <p><code>boy7 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小国"</code><code>, age</code><code>=</code><code>21</code><code>)</code></p> <p><code>boys </code><code>=</code> <code>[boy1, boy2,boy3,boy4,boy5,boy6,boy7]</code></p> <p><code>boys.sort()</code></p> <p><code>print</code><code>(boys)</code></p> </td> </tr> </tbody> </table> 我们完成了\_\_eq\_\_、\_\_lt\_\_、\_\_gt\_\_的定义,并且创建了好几个对象,放入到列表中,最后使用列表自带的sort()方法进行排序,最后打印排序后的结果。(我们在类的定义中重写了\_\_repr\_\_方法,主要是用于展示排序后的结果的,后面会讲到它的作用)。结果如下: ![ae8852dc9e6d806e7bc48365d398f781.png][] 可以看到打印的结果真的是按照年龄来进行排序的。 #### 3.3、一元操作符 \_\_pos\_\_(self)、\_\_neg\_\_(self)、\_\_invert\_\_(self) #### 一元操作符的魔法方法分别对应的: +、-、~,其中+和-既是一元操作符,又是二元操作符,但Python解释器会根据表达式来自己判断执行。一元操作符的使用非常直接,比如+x就是在执行x.\_\_pos\_\_(),~x就是在执行x.\_\_invert\_\_()。比如我们考虑下面这个类,我们将返回字符串的逆序. <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> </td> <td> <p><code>class</code> <code>ReversibleString(</code><code>object</code><code>):</code></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, s):</code></p> <p><code> </code><code>self</code><code>.s </code><code>=</code> <code>s</code></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__invert__(</code><code>self</code><code>):</code></p> <p><code> </code><code>return</code> <code>self</code><code>.s[::</code><code>-</code><code>1</code><code>]</code></p> <p></p> <p><code>re </code><code>=</code> <code>ReversibleString(</code><code>"我爱中国"</code><code>)</code></p> <p><code>print</code><code>(~re)</code></p> </td> </tr> </tbody> </table> 我们在\_\_invert\_\_()方法中将字符串进行了反转,并返回了,最终执行结果式: ![bb816d651101f814513c06613b6f66a4.png][] 我们需要注意的式~re得到的返回值是个字符串,而字符串是没有定义~操作的,因此我们执行~~re将会报错。如何解决这个问题呢?只需要让\_\_invert\_\_()返回的是RversibleString对象即可,如下改进: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> </td> <td> <p><code>class</code> <code>ReversibleString(</code><code>object</code><code>):</code></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, s):</code></p> <p><code> </code><code>self</code><code>.s </code><code>=</code> <code>s</code></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__invert__(</code><code>self</code><code>):</code></p> <p><code> </code><code>return</code> <code>ReversibleString(</code><code>self</code><code>.s[::</code><code>-</code><code>1</code><code>])</code></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__str__(</code><code>self</code><code>) </code><code>-</code><code>> </code><code>str</code><code>:</code></p> <p><code> </code><code>return</code> <code>self</code><code>.s</code></p> <p></p> <p><code>re </code><code>=</code> <code>ReversibleString(</code><code>"我爱中国"</code><code>)</code></p> <p><code>print</code><code>(~re)</code></p> <p><code>print</code><code>(~~re)</code></p> </td> </tr> </tbody> </table> 最终的返回结果是: ![7f1550da26cb33645ec47597663780e7.png][] #### 3.4、二元操作符 \_\_add\_\_(self, other)等 #### Python允许我们重载二元操作符,例如: +,-,\*,/,|,%等等,理论上所有的python中的二元操作符都可以被重载。二元方法的调用顺序是从左至右,也就是如果x具有对应的二元方法则会被优先使用,比如x + y,实际上在执行 x.\_\_add\_\_(y)。如下面这个例子: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> <p>16</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>def</code> <code>__add__(</code><code>self</code><code>, other):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{self.name}的__add__被调用"</code><code>)</code></p> <p><code> </code><code>sum_age </code><code>=</code> <code>self</code><code>.age </code><code>+</code> <code>other.age</code></p> <p><code> </code><code>return</code> <code>sum_age</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小华"</code><code>, age</code><code>=</code><code>19</code><code>)</code></p> <p></p> <p><code>print</code><code>(boy1 </code><code>+</code> <code>boy2)</code></p> <p><code>print</code><code>(boy2 </code><code>+</code> <code>boy1)</code></p> </td> </tr> </tbody> </table> 我们重写了MyClass的\_\_add\_\_方法,由此我们可以使用 对象+对象 的方式(如果不重写该方法会爆出异常),我们在\_\_add\_\_中打印了到底是谁在执行\_\_add\_\_方法,并且架构两个对象的年龄求和的结果返回了,执行结果如下: ![bf53526f0ccecbcadf33e19edcdc924d.png][] 可以看到,其实+只执行了左边的\_\_add\_\_方法,并不会执行+右边的对象,因此我们在重写这个方法时候,需要注意是否要保证二元运算符的可交换性。 其实对于每一个二元操作符,Python都提供了3种魔术方法,第一种就是上面提到的,普通方法。第二种是即席方法,如果重写了即席方法,那么将会在执行 +=的时候被调用。+ 运算的即席方法叫做\_\_iadd\_\_(),例如: x += y 等价于在执行x.\_\_iadd\_\_(y)。一般情况下即席方法都是修改self,再返回self,但这并不是必须的。如果我们没有定义即席方法,只定义了\_\_add\_\_方法,那是否会报错呢?看如下例子: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> </td> <td> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小华"</code><code>, age</code><code>=</code><code>19</code><code>)</code></p> <p></p> <p><code>boy1 </code><code>+</code><code>=</code> <code>boy2</code></p> <p><code>print</code><code>(</code><code>type</code><code>(boy1))</code></p> </td> </tr> </tbody> </table> 运行的结果是: ![66797847ba7996d6d7c113dc3a749703.png][] 我们可以看到boy1的类型变成了int,如果打印boy1会得到33,这是为什么呢?其实就是当我们只定义了普通方法,没有定义即席方法,那么+=操作就等价于,x = x.\_\_add\_\_(y),即将x + y的结果返回给左边的对象。此处的boy1其实接收的就是boy1.\_\_add\_\_(boy2)的返回值。 第三种魔法函数是取反方法,当+左边的对象并没有提供\_\_add\_\_方法并且操作对象类型不同时,将会执行右边对象的\_\_radd\_\_()方法。换句话说 x + y 中如果x没有提供\_\_add\_\_()并且x与y不是同一类型对象,那么该条语句会执行y.\_\_radd\_\_(x)。我们看下面这个例子: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> <p>16</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>def</code> <code>__radd__(</code><code>self</code><code>, other):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{self.name}的__radd__被调用"</code><code>)</code></p> <p><code> </code><code>return</code> <code>self</code><code>.age</code></p> <p></p> <p><code>class</code> <code>B(</code><code>object</code><code>):</code></p> <p><code> </code><code>pass</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>B()</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>print</code><code>(boy1 </code><code>+</code> <code>boy2)</code></p> </td> </tr> </tbody> </table> 注意我们这里定义了两个类B类和MyClass类,这两个类都没有重写\_\_add\_\_方法。因此满足触发\_\_radd\_\_()调用的条件,boy1和boy2的类型是不同的,并且boy1没有\_\_add\_\_方法。最终的结果如下: ![1e7720df230802d2143c1839b11d6097.png][] 由此我们上面介绍了所有的关于+ 运算的三种魔法方法,其实其余的二元运算也是一样的,都具有普通方法,取反方法,即席方法。具体的名称如下图所示: ![823a7bce6f31a50fc435cdcb99b8c7f6.jpeg][] ### 4、类似集合的魔法方法 ### #### 4.1、\_\_getitem\_\_(self,index) #### 当对对象使用下标查询时(对象\[1\])会进入到该方法。该方法会收集index,至于如何处理就是自己定义了,上例子。 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p></p> <p><code> </code><code>## 可以使用对对象使用下标: 对象[1]</code></p> <p><code> </code><code>def</code> <code>__getitem__(</code><code>self</code><code>,index):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{self.name}的__getitem__被调用, index为{index}"</code><code>)</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>MyClass(</code><code>"小明"</code><code>)</code></p> <p><code>boy1[</code><code>1</code><code>]</code></p> <p><code>boy1[</code><code>20</code><code>]</code></p> <p><code>boy1[</code><code>200</code><code>]</code></p> </td> </tr> </tbody> </table> 结果如下: ![7e7f3c25f64d87b8c6cbbe0d5dc16620.png][] 可以看到当我们定义完成\_\_getitem\_\_方法后就可以使用中括号+数字来进行操作了,但是要注意,只定义\_\_getitem\_\_方法只能完成查询操作,并不能实现对下标进行赋值,即执行 对象\[1\] == xxx 依然会报错。 #### 4.2、\_\_setitem\_\_(self,key, value) #### 上一小节说明了如何使用下标访问,如果我们重写\_\_setitem\_\_方法,那么我们就可以实现使用中括号+下标进行赋值了,直接上例子。 <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.friends </code><code>=</code> <code>dict</code><code>()</code></p> <p></p> <p><code> </code><code>## 可以给指定对象下标进行赋值: 对象[1] = xxx</code></p> <p><code> </code><code>def</code> <code>__setitem__(</code><code>self</code><code>, key, value):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{self.name}的__setitem__被调用, key={key}, value={value}"</code><code>)</code></p> <p><code> </code><code>self</code><code>.friends[key] </code><code>=</code> <code>value</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>MyClass(</code><code>"小明"</code><code>)</code></p> <p><code>boy1[</code><code>1</code><code>] </code><code>=</code> <code>"李华"</code></p> <p><code>boy1[</code><code>"邻居"</code><code>] </code><code>=</code> <code>"小花"</code></p> <p><code>print</code><code>(boy1.friends)</code></p> </td> </tr> </tbody> </table> 我们这里看12和13行,我们并没有要求中括号中必须放入的是int类型的数值,理论上我们可以放任何东西,因为都会传入给\_\_setitem\_\_方法,并以key来接收中括号中的内容。最后的执行结果如下。 ![ea309f6f541f82804b14f3f8147482b9.png][] #### 4.3、\_\_contains\_\_(self,ele) #### 这一个方法会在使用 in 时被触发,比如 boy1 in boy2 ,相当于在执行boy2.\_\_contains\_\_(boy1),直观上在查询boy1是否在boy2中,我们可以通过自定义\_\_contains\_\_方法从而实现这个过程,上例子: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> <p>16</p> <p>17</p> <p>18</p> <p>19</p> <p>20</p> <p>21</p> <p>22</p> <p>23</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.friends </code><code>=</code> <code>dict</code><code>()</code></p> <p></p> <p><code> </code><code>def</code> <code>__setitem__(</code><code>self</code><code>, key, value):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{self.name}的__setitem__被调用, key={key}, value={value}"</code><code>)</code></p> <p><code> </code><code>self</code><code>.friends[key] </code><code>=</code> <code>value</code></p> <p></p> <p><code> </code><code>def</code> <code>__contains__(</code><code>self</code><code>, ele):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{self.name}的 __contains__被调用"</code><code>)</code></p> <p><code> </code><code>if</code> <code>ele </code><code>in</code> <code>self</code><code>.friends.values():</code></p> <p><code> </code><code>return</code> <code>True</code></p> <p><code> </code><code>else</code><code>:</code></p> <p><code> </code><code>return</code> <code>False</code></p> <p></p> <p><code>boy1 </code><code>=</code> <code>MyClass(</code><code>"小明"</code><code>)</code></p> <p><code>boy1[</code><code>"同学1"</code><code>] </code><code>=</code> <code>"李华"</code></p> <p><code>boy1[</code><code>"同学2"</code><code>] </code><code>=</code> <code>"山炮"</code></p> <p><code>boy1[</code><code>"邻居1"</code><code>] </code><code>=</code> <code>"小花"</code></p> <p><code>boy1[</code><code>"邻居2"</code><code>] </code><code>=</code> <code>"静香"</code></p> <p><code>print</code><code>(</code><code>"胖虎"</code> <code>in</code> <code>boy1)</code></p> <p><code>print</code><code>(</code><code>"山炮"</code> <code>in</code> <code>boy1)</code></p> </td> </tr> </tbody> </table> 这里我们自定义的\_\_contains\_\_方法实际上就是看ele是否在对象的friends属性(这是个字典)中,运行的结果是: ![e4980acf0c9946ad8f7203785858be5f.png][] 我相信这个例子很直观的说明了\_\_contains\_\_方法的使用场景吧。 ### 5、其他常用魔法方法 ### 下面介绍一些常见定义的魔法方法 #### 5.1、\_\_len\_\_(self) #### 当调用len(对象)时会执行这个魔法方法,返回的时对象的"长度"(返回值需要是>=0的数值)。这本来没有什么好说的,但是该函数又与bool()和IF条件判断有关。因为在没有定义\_\_bool\_\_函数时,如果使用 if 对象 或者 bool(对象)语句的时候,将也会进入该魔法方法(返回值如果时0则为False,否则为True),上例子: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p></p> <p><code> </code><code>def</code> <code>__len__(</code><code>self</code><code>):</code></p> <p><code> </code><code>return</code> <code>100</code></p> <p><code> </code> </p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>print</code><code>(</code><code>bool</code><code>(boy1))</code></p> <p><code>if</code> <code>boy1:</code></p> <p><code> </code><code>print</code><code>(</code><code>"进入了if"</code><code>)</code></p> <p><code>else</code><code>:</code></p> <p><code> </code><code>print</code><code>(</code><code>"没有进入if"</code><code>)</code></p> </td> </tr> </tbody> </table> 运行结果如下: ![f6e21b20f3ef8d3b2a3f8662f62012a3.png][] #### 5.2、\_\_repr\_\_(self) #### 这个魔法方法非常重要(但也经常被忽略),\_\_repr\_\_用于确定对象在终端的显示方式(注意在终端中显示一个对象不是直接调用str(对象)的)。一个对象在终端默认显示是类似于<\_\_main\_\_.Object at xxxx>这样的,用类型+地址表示一个对象,比如你把object放入到一个\[\]中,你打印列表,得到的就是\[<.....>, <.....>。。。 \]。但这种表示对于程序员来说也是没有多大价值的(除了显示出对象地址),程序员其实更想直到这个对象是如何建立的,因此一般\_\_repr\_\_都会直接返回初始化该对象的语句(这并不是必须的要求),例如我们在3.2小节中做的一样。直接看例子吧: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> <p>16</p> <p>17</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__repr__(</code><code>self</code><code>) </code><code>-</code><code>> </code><code>str</code><code>:</code></p> <p><code> </code><code>return</code> <code>f</code><code>"MyClass(name={self.name}, age={self.age})"</code></p> <p><code> </code> </p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小华"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy3 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小褚"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boys </code><code>=</code> <code>[]</code></p> <p><code>boys.append(boy1)</code></p> <p><code>boys.append(boy2)</code></p> <p><code>boys.append(boy3)</code></p> <p><code>print</code><code>(boys)</code></p> </td> </tr> </tbody> </table> 我们将三个对象放入到列表中,并打印这个列表,可以得到如下结果: ![b0b067f3a1450eb9e884ce2029a25ee8.png][] 大家可以试试如果删除\_\_repr\_\_打印的结果又会是什么呢?我们需要注意的是\_\_repr\_\_与\_\_str\_\_的差别,后者是当调用str(对象)时才会被执行,而\_\_repr\_\_则是显示在终端上的(你在DeBUG时对象显示的就是\_\_repr\_\_的内容),因此可以大致理解为\_\_repr\_\_显示的内容其实是给程序员看的,而\_\_str\_\_是给用户看的。 #### 5.3、\_\_hash\_\_(self) #### 这个魔法方法在对象传递给散列函数时被调用,也就是放入集合中,hash表中时会调用(例如将对象add进一个set时)。\_\_hash\_\_方法需要返回一个整型值,可以为负数,这个整型值通常时为了唯一的标识一个对象使用,不同对象返回理论上应该返回不同的整型值才对(因此Cpython中是根据对象的内存地址转化的整型值返回,当我们没有定义\_\_hash\_\_方法时就是调用的这个默认的方法,返回一大串数字)。然而如果我们自定义了\_\_eq\_\_方法(但是没有定义\_\_hash\_\_方法),那么默认的\_\_hash\_\_方法会被隐式的变为None(即无法调用)。 在python中只有可哈希化的对象才能放入set中或者称为字典的键,在上述两种情况下,哈希值(\_\_hash\_\_返回的值称为哈希值)用于确定一个对象是否是set对象成员以及将某个对象与字典的键进行比较从而进行查找。例如下面的例子: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> <p>13</p> <p>14</p> <p>15</p> <p>16</p> <p>17</p> <p>18</p> <p>19</p> <p>20</p> <p>21</p> <p>22</p> <p>23</p> <p>24</p> <p>25</p> <p>26</p> <p>27</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__hash__(</code><code>self</code><code>):</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{self.name}的__hash__被执行"</code><code>)</code></p> <p><code> </code><code>return</code> <code>0</code></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__repr__(</code><code>self</code><code>) </code><code>-</code><code>> </code><code>str</code><code>:</code></p> <p><code> </code><code>return</code> <code>f</code><code>"MyClass(name={self.name}, age={self.age})"</code></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__eq__(</code><code>self</code><code>, other) </code><code>-</code><code>> </code><code>bool</code><code>:</code></p> <p><code> </code><code>if</code> <code>self</code><code>.age </code><code>=</code><code>=</code> <code>other.age:</code></p> <p><code> </code><code>return</code> <code>True</code></p> <p><code> </code><code>else</code><code>:</code></p> <p><code> </code><code>return</code> <code>False</code></p> <p><code> </code> </p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy2 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小华"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boy3 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小褚"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>boys </code><code>=</code> <code>set</code><code>()</code></p> <p><code>boys.add(boy1)</code></p> <p><code>boys.add(boy2)</code></p> <p><code>boys.add(boy3)</code></p> <p><code>print</code><code>(boys)</code></p> </td> </tr> </tbody> </table> 我们重写了\_\_hash\_\_和\_\_eq\_\_方法,当年龄相等时,\_\_eq\_\_将返回True,而\_\_hash\_\_则一直返回0,在set或者字典的键判断两个对象是否相等,使用的逻辑是: obj1.\_\_hash\_\_() == obj.\_\_hash\_\_() and (obj1.id() == obj.id() or obj1.\_\_eq\_\_(obj2)),如果上述表达式返回的是True则认为两个对象相等,否则不相等(其中object.id()是得到对象的内存地址)。因此上述执行结果如下: ![7bcd5dfb46e526e201225bdcb6530395.png][] 其实这里里面有蛮多细节,关于散列表也就是set集合是如何储存一个对象的,这里仅仅简单说明一下(没有数据结构基础的不用了解)。 * 首先调用放入对象object2的\_\_hash\_\_()得到哈希值,并根据hash值得到该对象在hash表(可以认为是一维列表)中的下标位置。 * 如果hash表(可以认为是一维列表)中的下标位置处已经存在对象object1,则通过:obj1.\_\_hash\_\_() == obj2.\_\_hash\_\_() and (obj1.id() == obj2.id() or obj1.\_\_eq\_\_(obj2))的逻辑判断两个对象是否相等,如果相等则不放入该对象,如果不相等则将该对象"链接"到object2的后面。(其实这里的object2是一个链表的头节点,object1需要比较完整个链表的所有节点,才能被放入) #### 5.4、\_\_format\_\_(self,spec\_str) #### 我们在字符串输出时,常常会采用"".format()的方式进行格式化,而\_\_format\_\_魔法方法,则是将对象放入"".format(对象)中时会被执行的,而spec\_str参数则会保留下\{\}中:后的格式化要求(在字符串格式化中在冒号后面可以进行一些标准定义,比如\{:^10\}表示居中显示宽度为10),直接上例子: <table> <tbody> <tr> <td> <p>1</p> <p>2</p> <p>3</p> <p>4</p> <p>5</p> <p>6</p> <p>7</p> <p>8</p> <p>9</p> <p>10</p> <p>11</p> <p>12</p> </td> <td> <p><code>class</code> <code>MyClass(</code><code>object</code><code>):</code></p> <p></p> <p><code> </code><code>def</code> <code>__init__(</code><code>self</code><code>, age, name):</code></p> <p><code> </code><code>self</code><code>.name </code><code>=</code> <code>name</code></p> <p><code> </code><code>self</code><code>.age </code><code>=</code> <code>age</code></p> <p><code> </code> </p> <p><code> </code><code>def</code> <code>__format__(</code><code>self</code><code>, spec_str) </code><code>-</code><code>> </code><code>str</code><code>:</code></p> <p><code> </code><code>print</code><code>(f</code><code>"{self.name}的__format__被执行, spec_str={spec_str}"</code><code>)</code></p> <p><code> </code><code>return</code> <code>self</code><code>.name</code></p> <p><code> </code> </p> <p><code>boy1 </code><code>=</code> <code>MyClass(name</code><code>=</code><code>"小明"</code><code>, age</code><code>=</code><code>14</code><code>)</code></p> <p><code>print</code><code>(</code><code>"{} 测试, {:.s}, {:^d}"</code><code>.</code><code>format</code><code>(boy1, boy1, boy1))</code></p> </td> </tr> </tbody> </table> 这里我们直接将boy1传入到format中,而此处的:s,:^d都是我乱写的,只是为了展示\_\_format\_\_会接收到这些要求至于是否使用,全屏自定义,上面例子的运行结果是: ![961c3dbd546c286a413ff903f1228eca.png][] ### 6、总结 ### 本文总结有19种不同的魔法方法,但其实Python种定义的魔法方法有上百种,大多数魔法方法都是你不知道的地方发挥着作用。我们并不需要也不必须要去全部重写或者部分重写,这更像是Python给我们设定的语法糖一样,如果你的功能有魔法方法可以帮助你实现,那么可以使用这些魔法方法。在笔者看来了解这些魔法方法可以帮助编程者更加深入的了解Python在你不知道的地方到底执行了什么,有助于我们对python这一门动态语言的理解。 [0923aa15ff23ec0d79efb408734467fb.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/f8169be22769414bb65807e7f4157cae.png [7f9e6ae7d608b59e325e154397c1ef4c.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/eb9643971cf04c9baa0927de4a55dee1.png [65862818740ddea7792a97f0928db7ee.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/1c55fa9b00824212a37bca3a93239226.png [6758cc290245f1e0859054a85d0ab27a.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/6ecbda32f99a4b38b1f6c624326cb7b1.png [6ee7038e6375f2f1e64c082810d22f55.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/4035cfa98f2248fe920101bf24790762.png [ed2eb9e3c63bd8c9afd12320b50d0796.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/785b2c2537c8446dbb1d8ad03dd5e299.png [115f9013845285e272a0eed1912a4148.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/668fb5ce1d4146ceab77033453ce91e3.png [7d1c7c3086e9eea797566060c8f8d6a4.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/aa8bb3c92cbf4ecfac6995e661828d12.png [ff7752521f4f53600dcb8a60eb8568e1.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/af842026370e4083bc7e30e28247909d.png [6b3f715d3c95ccd9129af7e3a396d6cc.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/e1911c90c26e4fcc91a98a54f1ea2f0c.png [94574f2a0b0f7b6d60d17fdc127f5650.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/32efc96b556944069bbc7dc7e3b752dd.png [ae8852dc9e6d806e7bc48365d398f781.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/7b7de9bed60846e3ac11022a6336c38b.png [bb816d651101f814513c06613b6f66a4.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/181d27d3af6d4d3f9ead8721860c0349.png [7f1550da26cb33645ec47597663780e7.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/c1d33a62179b4af08f113b3b596827a2.png [bf53526f0ccecbcadf33e19edcdc924d.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/15a778ef0aeb4b56b4b9dc001444156d.png [66797847ba7996d6d7c113dc3a749703.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/e14c204e41af470aab6da33c73f28108.png [1e7720df230802d2143c1839b11d6097.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/94d373b43c0f42b6890b8e5b145e6699.png [823a7bce6f31a50fc435cdcb99b8c7f6.jpeg]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/d111a5eb99534e56ba3bcb083560513c.jpeg [7e7f3c25f64d87b8c6cbbe0d5dc16620.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/657cf4ad5807476dacd5412c8c24cfe0.png [ea309f6f541f82804b14f3f8147482b9.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/725c29fe3fdb44c2929a8090a69f6fd2.png [e4980acf0c9946ad8f7203785858be5f.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/4dd02cb0fc4142b08e968df04b5a1653.png [f6e21b20f3ef8d3b2a3f8662f62012a3.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/f067e41ed80f4595bb087e0f560b0905.png [b0b067f3a1450eb9e884ce2029a25ee8.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/3c1344d018124ee6bbd2e71eebebd74c.png [7bcd5dfb46e526e201225bdcb6530395.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/efb5cf565c424e5b9caa283d41503d74.png [961c3dbd546c286a413ff903f1228eca.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/24/987a77d5da254f449fea024c1d698632.png
相关 【无标题】 本文总结有19种不同的魔法方法,但其实Python种定义的魔法方法有上百种,大多数魔法方法都是你不知道的地方发挥着作用。我们并不需要也不必须要去全部重写或者部分重写,这更... 痛定思痛。/ 2024年05月24日 13:04/ 0 赞/ 41 阅读
相关 【无标题】 鸿蒙千帆起# 。#鸿蒙千帆起#服务用户量达3.8亿的中国电信App宣布拥抱鸿蒙生态,并将基于HarmonyOS NEXT,加速鸿蒙原生应用开发[哇]双方的合作进一步... 清疚/ 2024年05月06日 09:33/ 0 赞/ 18 阅读
相关 【无标题】 一、zip格式 zip可能是目前使用的最多的文档压缩格式。优点:可以在不同的操作系统平台上使用。缺点:支持的压缩率不是很高。而tar.gz和tar.bz2在压缩率 雨点打透心脏的1/2处/ 2024年04月07日 11:20/ 0 赞/ 65 阅读
相关 【无标题】 C 语言接口如何定义和继承? 在 C 语言中,接口通常通过定义结构体来实现,结构体中包含一些函数指针,这些函数指针定义了接口所需的所有函数。 下面是一个示例代码: 谁践踏了优雅/ 2024年03月26日 11:40/ 0 赞/ 63 阅读
相关 【无标题】 -------------------- Spring Boot Web开发通常采用MVC(Model-View-Controller)结构 ------------- 深碍√TFBOYSˉ_/ 2024年03月25日 20:04/ 0 赞/ 7 阅读
相关 【无标题】 -------------------- springboot web项目框架结构 -------------------- SpringBoot 是一个基于 Spr 女爷i/ 2024年03月25日 20:00/ 0 赞/ 13 阅读
相关 【无标题】 大家可以回想一下,当初我们最开始学习Java的时候,搭建一个Web所需要的步骤。。。 1、配置web.xml,加载spring和spring mvc 2、配置数据库连接、配 深藏阁楼爱情的钟/ 2024年03月23日 18:43/ 0 赞/ 7 阅读
相关 【无标题】 开启pg日志 pg安装请参考:https://kiwi.yginsight.com/kiwi/e/0d2734b2-2f9e-416f-857b-bcfee12aa700 妖狐艹你老母/ 2024年03月22日 20:34/ 0 赞/ 39 阅读
相关 【无标题】 注意事项 int 宽度是显示宽度,如果超过,可以自动增大宽度 int底层都是4个字节 时间的方式多样 '1256-12-23' "1256/12/23" "1256.12. 朴灿烈づ我的快乐病毒、/ 2023年09月27日 14:57/ 0 赞/ 101 阅读
相关 【无标题】 课程简介 课程导读:基于Shiro框架实现基于Role Based Access Control的权限管理 本课程从Shiro的单独应用,到整合SpringMVC以及Spr ゝ一世哀愁。/ 2023年09月27日 14:45/ 0 赞/ 110 阅读
还没有评论,来说两句吧...