隨手扎
關於Python Lambda那些可能不知道的三兩事
lambda本質和function無異
def f():
pass
type(lambda : None) # => <type 'function'>
type(f) # => <type 'function'>
lambda :None # => <function <lambda> at 0x7ffa6d343650>
f # => <function f at 0x7ffa6d349dd0>
lambda和function的型態都是function
,並沒有區分開來。
一個是語句、一個是表達式
差別在於def
是關鍵字形成的語句(statement),lambda
是表達式(expression)。他們差別在於,能出現在程式碼的不同位置。
※ Note: 表達是(expression)也是一種語句(statement)。為了簡單來看,接下來都會區分開來。
內建有多個函式可以接受函式參數
因為lambda和function本質差無異,所以不會有函式只可以接受lambda,不可以接受function。
filter(lambda parameter: expression, iterable)
filter(function, iterable) # more correct
比起使用lambda描述filter,用function更正確。reduce等也是這樣。
lambda多行的寫法
通常看到lambda
都只有一行,正常情況下也都是這樣。不過先來看看文件怎麼描述lambda的語法:
lambda_expr ::= "lambda" [parameter_list] ":" expression
lambda_expr_nocond ::= "lambda" [parameter_list] ":" expression_nocond
相當於:
def <lambda>(parameters):
return expression
def
、和function
幾乎等價的行為看了。把注意力放到expression
和expression_nocond
,這表示可以接受表達式(expression),所以其實這樣寫也是可以的:
# Output: Hello, World
# Return: (None, None)
# In Python 2, print is a statement, so you can't write like this.
(lambda: (print("Hello, ", end=""),
print("World")))()
def puts(s:str, *args, **kwargs):
print(s, *args, **kwargs)
return s
# Output: Hello, World
# Return: ("Hello, ", "World")
# In Python 2, print is a statement, so you can't write like this.
(lambda: (puts("Hello, ", end=""),
puts("World")))()
其時還有個寫法,賣個關子寫在後面。先來看看Python怎麼定義換行?
2.1. Line structure
A Python program is divided into a number of logical lines.
2.1.1. Logical lines
The end of a logical line is represented by the token NEWLINE. Statements cannot cross logical line boundaries except where NEWLINE is allowed by the syntax (e.g., between statements in compound statements). A logical line is constructed from one or more physical lines by following the explicit or implicit line joining rules.
2.1.2. Physical lines
A physical line is a sequence of characters terminated by an end-of-line sequence. In source files and strings, any of the standard platform line termination sequences can be used - the Unix form using ASCII LF (linefeed), the Windows form using the ASCII sequence CR LF (return followed by linefeed), or the old Macintosh form using the ASCII CR (return) character. All of these forms can be used equally, regardless of platform. The end of input also serves as an implicit terminator for the final physical line.
When embedding Python, source code strings should be passed to Python APIs using the standard C conventions for newline characters (the \n character, representing ASCII LF, is the line terminator).
簡單說,有 物理換行 和 邏輯換行。所以有可能隱藏著連接行的記號,你也可以明確寫下反斜線(\
)
(lambda: (puts("Hello, ", end=""),
puts("World")))()
# same like
(lambda: (puts("Hello, ", end=""), \
puts("World")))()
你可能沒看過得Python
上面作法限制還蠻多的,在看到最後寫法之前,先來實現幾個東西–GlobalVar(name, val)
、Environment Class
、Void(Any)
1:
def GlobalVar(name: str, val = None):
globals()[name] = val
return val
def Void(*expr, **kexpr):
return None
def Progn(*expr):
if expr: return expr[-1]
先不管Environment
,來看看GlobalVar()
和Void
怎麼用:
GlobalVar("name", "World") # => "World"
name # => "World"
hello = lambda name: print("Hello, " + str(name))
hello(name) # => Output: "Hello, World"
Void(name) # => None
Void(hello(name)) # => None. Output: "Hello, World"
這裡Void()
的設計是我改過後的第二版,你甚至可以這樣寫:
Void(
puts("Hello, ", end=""),
puts("World.")
) # => None. Output: Hello, World.
# or
Void(
step_1 = puts("Hello, ", end=""),
step_2 = puts("World")
) # => None. Output: Hello, World.
另外,受到Lisp的progn
啟發,我而外寫了Progn()
,與Void()
的差別只在於其會返回最後一個表達式的結果。
result = Progn(
puts("Hello, ", end=""),
puts("World."),
) # Output: Hello, World.
result # => World.
配合上GlobalVar
:
Void(
GlobalVar("name", "Daniel"),
hello(name),
) # Output: "Hello, Daniel"
或是Python 3.8後你可以這樣寫:
Void(
name := "Daniel",
hello(name)
) # Output: "Hello, Daniel"
Environment Class
但是GlobalVar
直接宣告設定全域變數,這不太好,但是Python又不太能存取寫入closure、nonlocal的變數。所以寫了一個Environment
類別,想法有些從Haskell的I/O來的,不過實現的不是很好看…😭
class Environment:
def __getitem__(self, key:str):
return self.__getattr__(key)
def __setitem__(self, key:str, value):
setattr(self, key, value)
return CallableWrapper.new(value)
def __getattr__(self, key: str, default = None):
if key in self.__dict__: return CallableWrapper.new(self.__dict__[key])
else: return CallableWrapper.new(default)
def get(self, key, default = None):
return self.__getattr__(key, default)
def set(self, key, value):
setattr(self, key, value)
return CallableWrapper.new(value)
def set_(self, value, key:str):
return self.set(key, value)
def __call__(env, f, *args, **kwargs):
f(env, *args, **kwargs)
return env
@staticmethod
def WrapFun(f, *args, **kwargs):
def _f(env,*args, **kwargs):
return f(*args, **kwargs)
return _f
Environment
用到之前寫的CallableWrapper
,主要是為了function chaining。儘管當前實現仍有不盡意之處,但是Environment
基本遵守幾個原則。
- 呼叫
Environment
時,和Void
、Progn
很像,寫法不同。此外總是回傳環境本身。(所以可以在同地多次呼叫) - 只有
get()
、set()
方法,不過同樣允許註冊為變數名稱,所以更安全的使用是Environment.get(env, key: str[, default])
和Environment.get(env, key: str[, val])
(直接從Class呼叫)。 - 從
Environment.get
取得值,總是用CallableWrapper.new
包起來,這有助於更靈活的使用。同樣的理由Environment.set
也會回傳值。
Env = Environment # alias
env = Env()
env(puts,"Hello", end="") \
(puts, "World.") # Output: Hello, World
###########
env.get("a", 100) # => 100
env.set("a", [1,2,3,4,5,6]) # => [1,2,3,4,5,6]
env.a # => [1,2,3,4,5,6]
env.get("a")(sum) # => 21
env["a"](sum) # => 21
###########
env.set("get", 100) # 100
env.get # 100
#env.get("a", 100) # can't
來改改看上面的Hello World程式:
env = Env()
env(Env.set, "name", "World").get("name")(puts) # Output: World
hello(env.name) # => Hello, World
多行的lambda(2)
Python的邏輯運算是惰性的。
env = Env()
result = (lambda: Void(env.set("name", "Wrold")) or
Void(hello(env.name)) or
env.name)() # Output: Hello, World. Return env.name
result # => "World"
env.name # => "World"
Python 3.8後還可更靈活一些:
result = (lambda: Void(name:="Daniel") or
Void(hello(name)) or
name)() # Output: Hello, Eaniel. Return local name
name # => "World"
result # => "Daniel"
受到js裡void operator的啟發,我寫了
Void()
函式,可以接受任何表達式,但都會返回None
。 ↩︎