Python 神乎其技 Ch3


活用函式


目錄


這篇的內容是來自Python 神乎其技:精要剖析語法精髓,大幅提升程式功力!這本書的第三章,會介紹一些Python函式的特性與好用的功能

語彙閉包(closure)


def get_speak_func(text, volumn):
    def whisper():
        return text.lower() + '...'
    def yell():
        return text.upper() + '!'
    if volumn > 0.5:
        return yell
    else:
        return whisper
get_speak_func('Hello, World', 0.7)()
'HELLO, WORLD!'

雖然 whisperyell 都沒有 text 參數,但仍然能夠使用
能夠做到這點的函式,稱為「閉包」

可以用來預先設定行為的組態

def make_adder(n):
    def add(x):
        return x + n
    return add
plus_3 = make_adder(3)
plus_5 = make_adder(5)
print(plus_3(4))
print(plus_5(4))
7
9

make_adder 就好像一座「工廠」

函式就是物件,物件也可以是函式(可被呼叫)


class Adder:
    def __init__(self, n):
        self.n = n

    def __call__(self, x):
        return self.n + x
plus_3 = Adder(3)
plus_3(4)
7

__call__ 方法的物件,就是「可被呼叫的」(callable)

callable 檢查

print(callable(plus_3))
print(callable(make_adder))
print(callable('hello'))
True
True
False

lambda + closure


def make_adder(n):
    return lambda x: x + n
plus_3 = make_adder(3)
plus_5 = make_adder(5)
print(plus_3(4))
print(plus_5(4))
7
9

裝飾器(decorator)


吃一個函式進來,吐一個函式出去
i.e. 參數是一個函式,return 的也是一個函式

def uppercase(func):
    def wrapper():
        original_result = func()
        modified_result = original_result.upper()
        return modified_result
    return wrapper
def greet():
    return 'Hello'
print(greet)
print(greet())
<function greet at 0x112c36d08>
Hello
@uppercase
def greet():
    return 'Hello'
print(greet)
print(greet())
<function uppercase.<locals>.wrapper at 0x112c36d90>
HELLO

一次用多個裝飾器

def strong(func):
    def wrapper():
        return '<strong>' + func() + '</strong>'
    return wrapper

def emphasis(func):
    def wrapper():
        return '<em>' + func() + '</em>'
    return wrapper
@strong
@emphasis
def greet():
    return 'Hello!'
greet()
'<strong><em>Hello!</em></strong>'

裝飾器的效果是「由下而上
可以稱為「裝飾器堆疊(decorator stacking)」

不想用裝飾器語法的話

def greet():
    return 'Hello!'

decorated_greet = strong(emphasis(greet))

decorated_greet()
'<strong><em>Hello!</em></strong>'

裝飾有參數的函式

def trace(func):
    def wrapper(*args, **kargs):
        print(f'TRACE: calling {func.__name__}() '
              f'with {args}, {kargs}')

        original_result = func(*args, **kargs)

        print(f'TRACE: calling {func.__name__}() '
              f'returned {original_result!r}')

        return original_result
    return wrapper
@trace
def say(name, line):
    return f'{name}: {line}'
say('Jane', 'Hello, World!')
TRACE: calling say() with ('Jane', 'Hello, World!'), {}
TRACE: calling say() returned 'Jane: Hello, World!'





'Jane: Hello, World!'

「可除錯」的裝飾器

裝飾器會讓原始的函式被「隱藏」

def uppercase(func):
    def wrapper():
        return func().upper()
    return wrapper

@uppercase
def greet():
    """Return a friendly greet"""
    return 'Hello!'
print(greet.__name__)
print(greet.__doc__)
wrapper
None

自己寫的裝飾器應該用 functools.wraps 來保存原始的資料

import functools

def uppercase(func):
    @functools.wraps(func)
    def wrapper():
        return func().upper()
    return wrapper

@uppercase
def greet():
    """Return a friendly greet"""
    return 'Hello!'
print(greet.__name__)
print(greet.__doc__)
greet
Return a friendly greet

*args**kargs


def foo(required, *args, **kargs):
    print(required)
    if args:
        print(args)
    if kargs:
        print(kargs)
foo()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-42-c19b6d9633cf> in <module>
----> 1 foo()


TypeError: foo() missing 1 required positional argument: 'required'
foo('hello')
hello
foo('hello', 1, 2, 3)
hello
(1, 2, 3)
foo('hello', 1, 2, 3, key1='value', key2=999)
hello
(1, 2, 3)
{'key1': 'value', 'key2': 999}

轉傳 *args**kargs

  • 用在繼承的類別

    class Car:
    def __init__(self, color, mileage):
        self.color = color
        self.mileage = mileage
    
    class AlwaysBlueCar(Car):
    def __init__(self, *args, **kargs):
        print(args)
        print(kargs)
        super().__init__(*args, **kargs)
        self.color = 'blue'
    AlwaysBlueCar('green', 48392).color
    ('green', 48392)
    {}
    
    'blue'
    AlwaysBlueCar(mileage=48392, color='green').color
    ()
    {'mileage': 48392, 'color': 'green'}
    
    'blue'
  • 用在包裹的函式

    def trace(f):
    @functools.wraps(f)
    def decorated_function(*args, **kargs):
        print(f)
        print(args)
        print(kargs)
        result = f(*args, **kargs)
        print(result)
    return decorated_function
    
    @trace
    def greet(greeting, name):
    return '{}, {}!'.format(greeting, name)
    greet('Hello', 'Bob')
    <function greet at 0x111982048>
    ('Hello', 'Bob')
    {}
    Hello, Bob!

函式參數拆箱


假設有一個簡單的函數 print_vector

def print_vector(x, y, z):
    print('<%s, %s, %s>' % (x, y, z))
print_vector(0, 1, 0)
<0, 1, 0>

如果你有一個tuplelist要作為參數傳進去
一個一個傳進去是又繁瑣容易出錯的方式

tuple_vec = (1, 0, 1)

print_vector(tuple_vec[0],
             tuple_vec[1],
             tuple_vec[2])
<1, 0, 1>

如果直接用函式拆箱
則是簡單容易除錯

print_vector(*tuple_vec)
<1, 0, 1>

Do the same thing with less and clearer code

See also