为什么我们需要三种操作方式?
(我以乘法为例)
第一种方式:
df['a'] * 5
第二种方式:
df['a'].mul(5)
第三种方式:
df['a'].__mul__(5)
不仅仅是两个,就不需要mul
,我想知道它是否可以像普通方式一样,像整数
第一种方式:
3 * 5
第二种方式:
(3).__mul__(5)
但是在一个整数的基础上:
(3).mul(5)
会休息。
我很好奇,为什么我们在熊猫中需要这么多东西,加,减和除法也是一样。
答案 0 :(得分:3)
*
和mul
做相同的事情,但是__mul__
不同。
*
和mul
在委派给__mul__
之前执行一些检查。您应该了解两件事。
NotImplemented
在无法处理其他操作数的情况下,类的NotImplemented
返回一个特殊的单例值__mul__
。然后,这告诉Python尝试__rmul__
。如果同样失败,则引发通用TypeError
。如果直接使用__mul__
,则不会得到此逻辑。观察:
class TestClass:
def __mul__(self, other):
return NotImplemented
TestClass() * 1
输出:
TypeError: unsupported operand type(s) for *: 'TestClass' and 'int'
对此进行比较:
TestClass().__mul__(1)
输出:
NotImplemented
这就是为什么通常应该避免直接调用dunder(魔术)方法的原因:您绕开了Python所做的某些检查。
如果您尝试执行类似Base() * Derived()
的操作,其中Derived
继承自Base
,那么您会期望首先调用Base.__mul__(Derived())
。这可能会带来问题,因为Derived.__mul__
更可能知道如何处理此类情况。
因此,当您使用*
时,Python将检查右操作数的类型是否比左操作数的类型派生,如果是,则直接调用右操作数的__rmul__
方法。
观察:
class Base:
def __mul__(self, other):
print('base mul')
class Derived(Base):
def __rmul__(self, other):
print('derived rmul')
Base() * Derived()
输出:
derived rmul
请注意,即使Base.__mul__
不会返回NotImplemented
并可以清楚地处理类型为Derived
的对象,Python甚至不会先看 ;它会立即委派给Derived.__rmul__
。
为完整起见,在*
的上下文中,mul
和pandas
之间有一个区别:mul
是一个函数,并且因此可以在变量中传递并独立使用。例如:
import pandas as pd
pandas_mul = pd.DataFrame.mul
pandas_mul(pd.DataFrame([[1]]), pd.DataFrame([[2]]))
另一方面,这将失败:
*(pd.DataFrame([[1]]), pd.DataFrame([[2]]))
答案 1 :(得分:1)
在底层python中,“魔术方法” __mul__
和运算符*
都是相同的(*
只是调用__mul__
),正如您所指出的那样是Python稳定处理事物的方式。另一种方法mul
是一种可用于映射的方法(使用map
),例如,避免使用lambda x, y: x*mul
。
是的,您仍然可以使用__mul__
,但是通常这些方法(__x__
的目的不是用作常规功能,而简单的mul
可以使代码更清晰。>
因此,您并不是真的“需要”它,但是拥有和使用它很好。
答案 2 :(得分:1)
首先,永远不要使用第三种方式(df['a'].__mul__(5)
),因为它是Python类调用的内部方法。通常,用户不会触摸任何“笨拙”的方法。
关于其他两种方式,第一种是显而易见的。你只是乘以东西。这是标准数学。
第二种方法变得更加有趣。我如何使用该方法的一个示例是当您要应用的函数是变量时。
例如:
def pandas_math(series, func, val):
return getattr(series, func)(val)
pandas_math(df['a'], 'mul', 5)
将得到与df['a'].mul(5)
相同的结果,但是现在您可以将mul
作为变量或要使用的任何其他函数传递。比对所有符号进行硬编码要容易得多。