为什么我们需要三种操作方式?
(我用乘法作为例子)
第一种方式:
df['a'] * 5
第二种方式:
df['a'].mul(5)
第三种方式:
df['a'].__mul__(5)
两个还不够吗,不需要mul
,我想知道它会像正常方式一样,像整数一样
吗第一种方式:
3 * 5
第二种方式:
(3).__mul__(5)
但是在 inetger 的常规基础上:
(3).mul(5)
会坏。
我只是好奇,为什么我们在熊猫中需要这么多东西,加法、减法和除法也是如此。
*
和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]]))
"magic method"__mul__
和运算符*
在底层 python 中都是相同的(*
只是调用__mul__
),正如你所指出的,这是 python stadarized 处理事情的方式。另一种方法mul
是可用于映射(使用map
)和避免使用lambda x, y: x*mul
的方法。 是的,您仍然可以使用__mul__
但通常这些方法(__x__
)的目的不是用作普通函数,一个简单的mul
使代码更清晰。
所以,你并不真正"需要"它,但它很好拥有和使用。
首先,第三种方式(df['a'].__mul__(5)
)永远不应该使用,因为它是由Python类调用的内部方法。通常,用户不会触摸任何"dunder"方法。
关于另外两种方式,第一种方式是显而易见的;你只是把事情乘以。这是标准的数学。
第二种方式变得更有趣。我如何使用该方法的一个例子是,当您要应用的函数是变量时。
例如:
def pandas_math(series, func, val):
return getattr(series, func)(val)
pandas_math(df['a'], 'mul', 5)
将给出与df['a'].mul(5)
相同的结果,但现在您可以将mul
作为变量或任何其他您想要使用的函数传递。这比对所有符号进行硬编码要容易得多。