为什么我们需要三种不同的大熊猫操作方式?



为什么我们需要三种操作方式?

(我用乘法作为例子)

第一种方式:

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__之前执行一些检查。您应该了解两件事。

  1. 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所做的某些检查。

  1. 派生类运算符处理

当你尝试执行类似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作为变量或任何其他您想要使用的函数传递。这比对所有符号进行硬编码要容易得多。

最新更新