標籤:

在 Python 中,為什麼 pow 這樣的函數可以直接調用,而 floor 這樣的函數得先導入模塊?

python新手,望大神們多指教


本來寫在 @bhuztez 大大的回答的評論里的。算了還是發到頂層好了。

誒這裡 @藍色 大大的回答真的誤解一些了, @bhuztez 大大的回答才是完全正解。

這個問題,要看最精闢的答案的請看 @flow memory 大大的,要深入到Python的內部機制的請看 @bhuztez 大大的,要看點具體代碼的請看 @藍色 大大修正過的答案。

Python的__builtin___模塊完全是個運行時的東西, @藍色 大大引用的代碼其實是在VM初始化的時候把初始的__builtin___模塊中的名字與函數指針的對應關係註冊好;然而Python的(源碼到位元組碼)編譯器是不關心這個的。

Python的pow()跟像GCC的__builtin_powi()不可以相提並論;前者的行為可以在運行時改變,而編譯器完全不把它當作特殊的東西看待;後者則是編譯器直接支持的intrinsic function。

剛初始化好的時候,__builtin__模塊里的"pow"映射到的是builtin_pow()函數,後者進一步調用PyNumber_Power()函數來實現功能;cpython/bltinmodule.c at 2.7 · python/cpython · GitHub

Python的源碼編譯器會把 ** 運算符編譯為BINARY_POWER位元組碼指令,而Python的位元組碼解釋器為BINARY_POWER的實現則是直接調用PyNumber_Power()函數(不通過符號解析__builtin__模塊里的"pow"的當前綁定)。cpython/ceval.c at 2.7 · python/cpython · GitHub

由於在Python代碼里調用pow()實際上要先經過一次符號解析(LOAD_NAME)找到目標然後再調用過去,而模塊的綁定又是可變的,所以可以做到下面這種事情:

$ python
Python 2.7.5 (default, Mar 9 2014, 22:15:05)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
&>&>&> 2 ** 5
32
&>&>&> pow(2, 5)
32
&>&>&> __builtins__
&
&>&>&> dir(__builtins__)
["ArithmeticError", "AssertionError", "AttributeError", "BaseException", "BufferError", "BytesWarning", "DeprecationWarning", "EOFError", "Ellipsis", "EnvironmentError", "Exception", "False", "FloatingPointError", "FutureWarning", "GeneratorExit", "IOError", "ImportError", "ImportWarning", "IndentationError", "IndexError", "KeyError", "KeyboardInterrupt", "LookupError", "MemoryError", "NameError", "None", "NotImplemented", "NotImplementedError", "OSError", "OverflowError", "PendingDeprecationWarning", "ReferenceError", "RuntimeError", "RuntimeWarning", "StandardError", "StopIteration", "SyntaxError", "SyntaxWarning", "SystemError", "SystemExit", "TabError", "True", "TypeError", "UnboundLocalError", "UnicodeDecodeError", "UnicodeEncodeError", "UnicodeError", "UnicodeTranslateError", "UnicodeWarning", "UserWarning", "ValueError", "Warning", "ZeroDivisionError", "_", "__debug__", "__doc__", "__import__", "__name__", "__package__", "abs", "all", "any", "apply", "basestring", "bin", "bool", "buffer", "bytearray", "bytes", "callable", "chr", "classmethod", "cmp", "coerce", "compile", "complex", "copyright", "credits", "delattr", "dict", "dir", "divmod", "enumerate", "eval", "execfile", "exit", "file", "filter", "float", "format", "frozenset", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", "input", "int", "intern", "isinstance", "issubclass", "iter", "len", "license", "list", "locals", "long", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "print", "property", "quit", "range", "raw_input", "reduce", "reload", "repr", "reversed", "round", "set", "setattr", "slice", "sorted", "staticmethod", "str", "sum", "super", "tuple", "type", "unichr", "unicode", "vars", "xrange", "zip"]
&>&>&> pow
&
&>&>&> mypow = pow
&>&>&> mypow
&
&>&>&> __builtins__.pow = lambda x, y: mypow(x, y) - 1
&>&>&> pow(2, 5)
31
&>&>&> 2 ** 5
32

可見通過運行時修改__builtin__模塊里的綁定,pow()的行為就在運行時發生了變化;而 ** 運算符的行為是寫死的改不了。


很多人扯一大堆道理,然而對於本問題並沒什麼卵用。

真正原因在於,這是Python設計者的喜好罷了,因為設計者完全可以把floor也做成可以直接調用的嘛。


看到藍色來冒充Python專家了,特來批判一番。這裡首先是黑魔法,不然有些問題就解釋不清楚啦。

&>&>&> __builtins__
&
&>&>&> pow(2,2)
4
&>&>&> __builtins__ = None
&>&>&> pow(2,2)
Traceback (most recent call last):
File "&", line 1, in &
NameError: name "pow" is not defined
&>&>&> __builtins__ = {"pow":1}
&>&>&> pow
1
&>&>&>

只是模塊載入默認填充的__builtins__剛好是那個__builtin__模塊。所以,你想不手動導入floor也不是不可行的。

--------------------------

非要說編譯器會把pow替換掉。來來來,我dump出來給你看

Python代碼

print pow(2,2)

編譯結果

000000 03 f3 0d 0a MAGIC
000004 b1 22 7d 55 2015-06-14 06:44:01
000008 63 CODE
000009 00 00 00 00 argcount: 0
00000d 00 00 00 00 nlocals: 0
000011 03 00 00 00 stacksize: 3
000015 40 00 00 00 flags: NOFREE
000019 code:
000019 73 12 00 00 00 STRING&:
00001e 65 00 00 LOAD_NAME 0x0000
000021 64 00 00 LOAD_CONST 0x0000
000024 64 00 00 LOAD_CONST 0x0000
000027 83 02 00 CALL_FUNCTION 0x0002
00002a 47 PRINT_ITEM
00002b 48 PRINT_NEWLINE
00002c 64 01 00 LOAD_CONST 0x0001
00002f 53 RETURN_VALUE
000030 consts:
000030 28 02 00 00 00 TUPLE&:
000035 69 02 00 00 00 INT 2
00003a 4e NONE
00003b names:
00003b 28 01 00 00 00 TUPLE&:
000040 74 03 00 00 00 INTERNED&:
000045 70 6f 77 pow
000048 varnames:
000048 28 00 00 00 00 TUPLE&:
00004d freevars:
00004d 28 00 00 00 00 TUPLE&:
000052 cellvars:
000052 28 00 00 00 00 TUPLE&:
000057 filename:
000057 73 0a 00 00 00 STRING&:
00005c 65 78 61 6d 70 6c 65 2e example.
000064 70 79 py
000066 name:
000066 74 08 00 00 00 INTERNED&:
00006b 3c 6d 6f 64 75 6c 65 3e &
000073 03 00 00 00 firstlineno: 0x3
000077 lnotab:
000077 73 00 00 00 00 STRING&:
00007c

就是這個 LOAD_NAME有黑魔法

------------------------

手動eval

&>&>&> eval("pow")
&
&>&>&> eval("pow", {"__builtins__":{"pow":2}})
2
&>&>&> __builtins__ = {"eval":eval,"pow":1}
&>&>&> pow
1
&>&>&> eval("pow")
1
&>&>&> eval("pow", {"__builtins__":{"pow":2}})
2
&>&>&> eval("lambda: pow", {"__builtins__":{"pow":2}})()
2
&>&>&> eval("floor", {"__builtins__":{"floor":3}})
3

這下總該明白了吧 ... 這個__builtins__就是你在eval時,傳進去的globals里的__builtins__決定的。你想有floor就能有floor

編譯器才不管你是不是builtin的函數呢。編譯器事實上也管不著,只要允許有 import * 存在,鬼知道這個函數是不是builtin


pow()是built-in function,所以不需要導入.

floor()是math module的function,需要import math(from math import floor)後才能用.

不過要注意的是math module中也有個pow(),和built-in的pow()有些不同.

可以看看官方文檔:

2. Built-in Functions

以及

9.2. math — Mathematical functions


-------------------------------Update------------------

可以看 @RednaxelaFX 的解釋,我的理解有所偏差,還是放在前面吧,讓更多人看到

上面說的很清楚,pow是Builtin函數,而Builtin函數是編譯器直接支持的,可以參考這個鏈接了解Built in函數與普通函數的不同:Intrinsic function

下面我將順便展開說說Python中是如何實現Builtin Pow的。首先在Python中,Built in函數定義在了Bltinmodules.c這個文件中,具體的代碼在:

static PyMethodDef builtin_methods[]

這個數組中,pow的定義是:

{"pow", builtin_pow, METH_VARARGS, pow_doc},

所以,我們可以發現pow,其實方法在編譯器中是builtin_pow,其定義為:

static PyObject *
builtin_pow(PyObject *self, PyObject *args)
{
PyObject *v, *w, *z = Py_None;

if (!PyArg_UnpackTuple(args, "pow", 2, 3, v, w, z))
return NULL;
return PyNumber_Power(v, w, z);
}

所以,我們可以發現其實builtin_pow是調用了PyNumber_Power,而PyNumber_Power則是

PyObject *
PyNumber_Power(PyObject *v, PyObject *w, PyObject *z)
{
return ternary_op(v, w, z, NB_SLOT(nb_power), "** or pow()");
}

於是其本質是調用了power的SLOT,那麼有了這個SLOT後,就可以針對不同的類型,如float,long等。那麼,如果是long類型,那麼就在longobject.c中有long_pow的方法:

static PyObject *
long_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)

去掉一些"初始化設置"和"噪音」後,其最核心的代碼其實為這裡:

if (Py_SIZE(b) &<= FIVEARY_CUTOFF) { /* Left-to-right binary exponentiation (HAC Algorithm 14.79) */ /* http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf */ for (i = Py_SIZE(b) - 1; i &>= 0; --i) {
digit bi = b-&>ob_digit[i];

for (j = (digit)1 &<&< (PyLong_SHIFT-1); j != 0; j &>&>= 1) {
MULT(z, z, z);
if (bi j)
MULT(z, a, z);
}
}
}
else {
/* Left-to-right 5-ary exponentiation (HAC Algorithm 14.82) */
Py_INCREF(z); /* still holds 1L */
table[0] = z;
for (i = 1; i &< 32; ++i) MULT(table[i-1], a, table[i]); for (i = Py_SIZE(b) - 1; i &>= 0; --i) {
const digit bi = b-&>ob_digit[i];

for (j = PyLong_SHIFT - 5; j &>= 0; j -= 5) {
const int index = (bi &>&> j) 0x1f;
for (k = 0; k &< 5; ++k) MULT(z, z, z); if (index) MULT(z, table[index], z); } } }

也可以看到Python實現的時候參考的是什麼演算法,並且有對應的PDF資料。

而不僅對應pow函數,其實Python內置的 ** 操作符,也是這麼走過來的,所以在PyNumber_Power方法可以最後一個const char*的參數值是"** or pow()"。


pow 貌似比 floor 更常用 更適合作為 build-in 存在


這個問題的實質就是:為什麼有的函數在__builtins__,而有的不在?


除了scope原因之外,造成這樣scope的原因,還有一個可能是Pow 是int, floor可能是float。雖然python裡面並沒有什麼type。


推薦閱讀:

如何開始在github上學習東西?
gitbash中只調用python沒反應但如果後面添加了具體.py文件可以執行,是怎麼回事?
在matlab2015a中如何調用python?
最好的 Python 網站開發方面的學習教程有哪些?
python把一個unicode字元串寫入文件為什麼會報錯?

TAG:Python | 編程 |