讀書筆記, Python - python-tricks-buffet-awesome-features


To be a Pythonista

1. assert

syntax: assert expression1 [",", expression2]
大致相當於
if __debug__:
    if not expression1:
        raise AssertionError(expression2)

例子1

def apply_discount(product, discount):
    price = int(product['price'] * (1.0 - discount))
    assert 0 <= price <= product['price']
    return price

例子2

if cond == 'x':
    do_x()
elif cond == 'y':
    do_y()
else:
    assert False, (
        'This should never happen, 如果出現Heisenbug, 可以用這樣的方式去debug
    )

注意事項1: 可以用python -O .py去關閉assert, 所以assert只是輔助debugging, 而不能發現run-time errors

# 如果關閉assert, 很危險
def delete_product(prod_id, user):
    assert user.is_admin(), 'Must be admin'
    assert store.has_product(prod_id), 'Unknown product'
    store.get_product(prod_id).delete()

應該改為

def delete_product(product_id, user):
    if not user.is_admin():
        raise AuthError('Must be admin to delete')
    if not store.has_product(product_id):
        raise ValueError('Unknown product id')   
    store.get_product(product_id).delete()

注意事項2: assert后面跟元祖, 永遠為true. 不過python3會有提示

def hello_assert():
    assert (1==2, 'This should fail')

# SyntaxWarning: assertion is always true, perhaps remove parentheses?

2. String literal concatenation

這樣就可以不用續行符了

my_str = ('This is a super long string constant '
    'spread out across multiple lines. '
    'And look, no backslash characters needed!')

雙刃劍. Python’s string literal concatenation feature can work to your benefit, or introduce hard-to-catch bugs.

另外, 最好在容器最后的元素加上逗號

names = [
    'Alice',
    'Bob',
    'Dilbert',
]

3. Context manager. 上下文管理器

有基於Class(實現__enter__, __exit__)或者contextlib這個庫和生成器

例子1. 基於Class

Class ManagedFile:
    def __init__(self, name):
        self.name = name
    
    def __enter__(self):
        self.file = open(self.name, 'w')
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()

    with ManagedFile('hello.txt') as f:
        f.write('hello, world\n')
        f.write('bye now\n')

例子2. 基於contextlib.contextmananger和generator-based factory function

from contextlib import contextmanager

@contextmanager
def managed_file(name):
    try:
        f = open(name, 'w')
        yield f
    finally:
        f.close()

with managed_file('hello.txt') as f:
    f.write('hello, world\n')
    f.write('bye now\n')

例子3

what if the “resource” we wanted to manage was text indentation levels in some kind of report generator program

class Indenter:
    def __init__(self):
        self.level = -1

    def __enter__(self):
        self.level += 1
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.level -= 1
    
    def print(self, text):
        print('  ' * self.level + text)

with Indenter() as indent:
    indent.print('h1!')
    with indent:
        indent.print('hello')
        with indent:
            indent.print('bonjour')
    indent.print('hey')

練習1. 計算程序需要耗費的時間(還可以基於修飾器)

個人理解如果Timer基於context manager的話,可以計算一個context里多個函數的耗費時間, 而修飾器只能計算被修飾函數的耗費時間.

# 基於Class
import time

class Timer:
    def __init__(self):
        self.start_time = 0

    def __enter__(self):
        self.start_time = time.time()

    def __exit__(self, exc_type, exc_val, exc_tb):
        end_time = time.time()
        print(end_time - self.start_time)

with Timer() as timer:
    print('******' * 30)
# 基於contextlib.contextmanager和生成器
import time
from contextlib import contextmanager


@contextmanager
def timer():
    try:
        start_time = time.time()
        yield 
    finally:
        end_time = time.time()
        print(end_time - start_time)

def do_something():
    time.sleep(2)

with timer():
    do_something()

4. Underscores, Dunders, and More

看PEP8

• Single Leading Underscore: var
• Single Trailing Underscore: var

• Double Leading Underscore: __var
• Double Leading and Trailing Underscore: __var__
• Single Underscore: _

name mangling中要注意的點

_MangledGlobal__mangled = 23

class MangledGlobal:
    def test(self):
        return __mangled

MangledGlobal().test() # 23

5. A Shocking Truth About String Formatting

假設有兩個變量

>>> errno = 50159747054
>>> name = 'Bob'

字符串格式化1, old style, 使用%

“old style” formatting has been de-emphasized, it hasn’t been deprecated.

'Hello %s' % name

# 還可以加上format specifiers去控制輸出的字符串
'%x' % errno # 輸出16進制

# 注意"#"只接收一個參數,所以多參數要包在一個tuple里
>>> 'Hey %s, there is a 0x%x error!' % (name, errno)
'Hey Bob, there is a 0xbadc0ffee error!

支持mapping

>>> 'Hey %(name)s, there is a 0x%(errno)x error!' % {
... "name": name, "errno": errno }
'Hey Bob, there is a 0xbadc0ffee error!'

字符串格式化2, new style, format()

>>> 'Hello, {}'.format(name)
'Hello, Bob'

>>> 'Hey {name}, there is a 0x{errno:x} error!'.format(
... name=name, errno=errno)
'Hey Bob, there is a 0xbadc0ffee error!'

starting with Python 3.6 there’s an even better way to format your strings

字符串格式化3, f''

Python 3.6 adds yet another way to format strings, called Formatted String Literals

>>> f'Hello, {name}!'
'Hello, Bob!'

>>> a = 5
>>> b = 10
>>> f'Five plus ten is {a + b} and not {2 * (a + b)}.'
'Five plus ten is 15 and not 30.'

>>> def greet(name, question):
...         return f"Hello, {name}! How's it {question}?"
...
>>> greet('Bob', 'going')
"Hello, Bob! How's it going?"

# 差不多相等
>>> def greet(name, question):
...         return ("Hello, " + name + "! How's it " +
                    question + "?")
The real implementation is slightly faster than that because it uses the
BUILD_STRING opcode as an optimization.14 But functionally they’re
the same:
>>> import dis
>>> dis.dis(greet)
2 0 LOAD_CONST 1 ('Hello, ')
2 LOAD_FAST 0 (name)
4 FORMAT_VALUE 0
6 LOAD_CONST 2 ("! How's it ")
8 LOAD_FAST 1 (question)
10 FORMAT_VALUE 0
12 LOAD_CONST 3 ('?')
14 BUILD_STRING 5
16 RETURN_VALUE
# f''同樣支持format specifiers
>>> f"Hey {name}, there's a {errno:#x} error!"
"Hey Bob, there's a 0xbadc0ffee error!"

# Python’s new Formatted String Literals are similar to the JavaScript Template Literals added in ES2015.

字符串格式化4, Template Strings

It’s a simpler and less powerful mechanism.

>>> from string import Template
>>> t = Template('Hey, $name!')
>>> t.substitute(name=name)
'Hey, Bob!'
# Another difference is that template strings don’t allow format specifiers.
>>> templ_string = 'Hey $name, there is a $error error!'
>>> Template(templ_string).substitute(
... name=name, error=hex(errno))
'Hey Bob, there is a 0xbadc0ffee error!'

# In my opinion, the best use case for template strings is when you’re handling format strings generated by users of your program. Due to their reduced complexity, template strings are a safer choice
# 有安全隱患的
>>> SECRET = 'this-is-a-secret'
>>> class Error:
            def __init__(self):
                pass
>>> err = Error()
>>> user_input = '{error.__init__.__globals__[SECRET]}'
# Uh-oh...
>>> user_input.format(error=err)
'this-is-a-secret'

# Template String is much safer
>>> user_input = '${error.__init__.__globals__[SECRET]}'
>>> Template(user_input).substitute(error=err)
ValueError:
"Invalid placeholder in string: line 1, col 1"

Dan’s Python String Formatting Rule of Thumb:

If your format strings are user-supplied, use Template
Strings to avoid security issues. Otherwise, use Literal
String Interpolation if you’re on Python 3.6+, and “New
Style” String Formatting if you’re not.

6. “The Zen of Python” Easter Egg

Tim Peters’ Zen of Python

import this

Chapter 3 Effective Functions

7. Python’s Functions Are First-Class Citizens. 函數是一等公民

Python’s functions are first-class objects. You can assign them to variables, store them in data structures, pass them as arguments to other functions, and even return them as values from other functions.

假設

def yell(text):
    return text.upper() + '!'

>>> yell('hello")
'HELLO!'

Functions are objects

# It takes the function object referenced by yell and creates a second name, bark, that points to it
bark = yell

>>> bark('woof')
'WOOF!'

# Function objects and their names are two separate concerns.(A variable pointing to a function and the function itself are really two separate concerns)
>>> del yell
>>> yell('hello?')
NameError: "name 'yell' is not defined"
>>> bark('hey')
'HEY!'

# By the way, Python attaches a string identifier to every function at creation time for debugging purposes. You can access this internal
# identifier with the __name__ attribute
>>> bark.__name__
'yell'

Functions Can Be Stored in Data Structures. 函數可以存儲在數據結構中

>>> funcs = [bark, str.lower, str.capitalize]
>>> funcs
[<function yell at 0x10ff96510>,
<method 'lower' of 'str' objects>,
<method 'capitalize' of 'str' objects>]

>>> for f in funcs:
...        print(f, f('hey there'))
<function yell at 0x10ff96510> 'HEY THERE!'
<method 'lower' of 'str' objects> 'hey there'
<method 'capitalize' of 'str' objects> 'Hey there'

>>> funcs[0]('heyho')
'HEYHO!'

Functions Can Be Passed to Other Functions. 函數可以傳遞給另外一個函數

Functions that can accept other functions as arguments are also called higher-order functions. They are a necessity for the functional programming style.

def greet(func):
    greeting = func('Hi, I am a Python program')
    print(greeting)

>>> greet(bark)
'HI, I AM A PYTHON PROGRAM!'

def whisper(text):
    return text.lower() + '...'
>>> greet(whisper)
'hi, i am a python program...'
# 典型的high order函數:map
>>> list(map(bark, ['hello', 'hey', 'hi']))
['HELLO!', 'HEY!', 'HI!']

Functions Can Be Nested 函數可以定義在另外一個函數里

def speak(text):
    def whisper(t):
        return t.lower() + '...'
    return whisper(text)
>>> speak('Hello, World')
'hello, world...'

# 注意。Here’s the kicker though—whisper does not exist outside speak:
>>> whisper('Yo')
NameError:
"name 'whisper' is not defined"
>>> speak.whisper
AttributeError:
"'function' object has no attribute 'whisper'"
# 如果想訪問內部函數怎么辦?因為函數是一個對象,返回它就可以。
def get_speak_func(volume):

    def whisper(text):
        return text.lower() + '...'
    
    def yell(text):
        return rext.upper() + '!'

    if volume > 0.5:
        return yell
    else:
        return whisper

print(get_speak_func(0.3))
print(get_speak_func(0.7))

Functions Can Capture Local State

Not only can functions return other functions, these inner functions can also capture and carry some of the parent function’s state with them. 閉包, lexical closures (or just closures, for short)

閉包解釋

A closure remembers the values from its enclosing lexical scope even when the program flow is no longer in that scope.

閉包意義

In practical terms, this means not only can functions return behaviors but they can also pre-configure those behaviors

例子

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))

# In this example, make_adder serves as a factory to create and configure “adder” functions. Notice how the “adder” functions can still access the n argument of the make_adder function (the enclosing scope)

Objects Can Behave Like Functions

This is all powered by the call dunder method

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

    def __call__(self, x):
        return self.n + x

>>> plus_3 = Adder(3)
>>> plus_3(4)

8. Lambdas Are Single-Expression Functions

隱含return, 不用name綁定就可以用

add = lambda x, y: x + y
add(5, 3)

(lambda x, y: x + y)(5, 3)

Lambdas You Can Use, 排序的時候

>>> tuples = [(1, 'd'), (2, 'b'), (4, 'a'), (3, 'c')]
>>> sorted(tuples, key=lambda x: x[1])
[(4, 'a'), (2, 'b'), (3, 'c'), (1, 'd')]

>>> sorted(range(-5, 6), key=lambda x: x * x)
[0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5]

Just like regular nested functions, lambdas also work as lexical closures. Lambda可以是閉包,也即是一個沙盒。

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))

應該少用lambda

因為可讀性和維護性差。Always ask yourself: Would using a regular (named) function or a list comprehension offer more clarity?

# Harmful:
>>> class Car:
...         rev = lambda self: print('Wroom!')
...         crash = lambda self: print('Boom!')
>>> my_car = Car()
>>> my_car.crash()
'Boom!'
# Harmful:
>>> list(filter(lambda x: x % 2 == 0, range(16)))
[0, 2, 4, 6, 8, 10, 12, 14]
# Better:
>>> [x for x in range(16) if x % 2 == 0]
[0, 2, 4, 6, 8, 10, 12, 14]

9. The Power of Decorators

可以用在以下情景

logging 日志
enforcing access control and authentication 訪問與授權控制
計算耗費的時間
限流
緩存等等

基本

def uppercase(func):
    def wrapper():
        original_result = func()
        modified_result = original_result.upper()
        return modified_result
    return wrapper

@uppercase
def greet():
    return 'Hello!'
>>> greet()
'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>'

# 相當於 decorated_greet = strong(emphasis(greet))

修飾帶參數的函數

def trace(func):
    def wrapper(*args, **kwargs):
        print(f'TRACE: calling {func.__name__}() '
            f'with {args}, {kwargs}')
        original_result = func(*args, **kwargs)
        print(f'TRACE: {func.__name__}() '
                f'returned {original_result!r}')
        return original_result
    return wrapper

@trace
def say(name, line):
    return f'{name}: {line}'

print(say('Jane', 'Hello World'))

保持被修飾函數的metadata

import functools

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


@uppercase
def greet():
    """Return a friendly greeting"""
    return 'hello!'

print(greet.__name__)
print(greet.__doc__)
print(greet())


10. 關於*args, **kwargs

They allow a
function to accept optional arguments, so you can create flexible APIs in your modules and classes

def foo(required, *args, **kwargs):
    print(required)
    if args:
        print(args)
    if kwargs:
        print(kwargs)

>>> foo()
TypeError:
"foo() missing 1 required positional arg: '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}

Forwarding Optional or Keyword Arguments(Function Argument Unpacking)

用*或**去unpack參數,傳遞給另外一個函數

def foo(x, *args, **kwargs):
    kwargs['name'] = 'Alice'
    new_args = args + ('extra', )
    bar(x, *new_args, **kwargs)

This technique can be useful for subclassing and writing wrapper functions.

class Car:
    def __init__(self, color, mileage):
        self.color = color
        self.mileage = mileage

class AlwaysBlueCar(Car):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.color = 'blue'

>>> AlwaysBlueCar('green', 48392).color
'blue'

缺點就是接口不明確,傳啥參數要看父類。所以一般不在自己寫的類繼承結構中(自己清楚接口的類)用。用在繼承外部類(一些你不能控制、不清楚的類)中使用。但是在修飾器中,非常有用(維護性好)。

import functools
def trace(f):
    @functools.wraps(f)
    def decorated_function(*args, **kwargs):
        print(f, args, kwargs)
        result = f(*args, **kwargs)
        print(result)
    return decorated_function

@trace
def greet(greeting, name):
    return '{}, {}!'.format(greeting, name)

>>> greet('Hello', 'Bob')
<function greet at 0x1031c9158> ('Hello', 'Bob') {}
'Hello, Bob!'
With techniques like this one, it’s sometimes difficult to balance the
idea of making your code explicit enough and yet adhere to the Don’t
Repeat Yourself (DRY) principle. This will always be a tough choice to
make. If you can get a second opinion from a colleague, I’d encourage
you to ask for one.

例子 * unpack iterable, ** unpack dict

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

tuple_vec = (1, 0, 1)
print_vector(*tuple_vec)

>>> genexpr = (x * x for x in range(3))
>>> print_vector(*genexpr)

dict_vec = {'y': 0, 'z': 1, 'x': 1}
>>> print_vector(**dict_vec)
<1, 0, 1>

11.關於函數的return. Python是隱式return None

默認return None

def foo1(value):
    if value:
        return value
    else:
        return None

def foo2(value):
    """Bare return statement implies `return None`"""
    if value:
        return value
    else:
        return

def foo3(value):
    """Missing return statement implies `return None`"""
    if value:
        return value

type(foo3(0))
<class 'NoneType'>

# 作者建議如果一個函數不用return, 就不寫return None. 這是一個Python core feature.

Chapter 4: Classes & OOP

12. 對象比較"is" vs "=="

is 比較引用是否指向同一個對象
== 比值

13. repr和str

class Car:
    def __init__(self, color, mileage):
        self.color = color
        self.mileage = mileage

    def __repr__(self):
        return '__repr__ for Car'

    def __str(self):
        return '__str__ for Car'

>>> my_car = Car('red', 37281)
>>> print(my_car)
__str__ for Car
>>> '{}'.format(my_car)
'__str__ for Car'
>>> my_car
__repr__ for Car
Interestingly, containers like lists and dicts always use the result of
__repr__ to represent the objects they contain. Even if you call str
on the container itself:

str([my_car])
'[__repr__ for Car]'
By the way, some people refer to Python’s “dunder” methods as
“magic methods.” But these methods are not supposed to be magical
in any way. The fact that these methods start and end in double
underscores is simply a naming convention to flag them as core
Python features.

差異

import datetime
today = datetime.date.today()
>>> str(today)
'2017-02-02'

>>> repr(today)
'datetime.date(2017, 2, 2)'

# __repr__ is for developers,  __str__ is for user. --- StackOverflow 

應該如何用

If you don’t add a __str__ method, Python falls back on the result
of __repr__ when looking for __str__. Therefore, I recommend that
you always add at least a __repr__ method to your classes.

def __repr__(self):
    return (f'{self.__class__.__name__}('
                f'{self.color!r}, {self.mileage!r})')

>>> repr(my_car)
'Car(red, 37281)'
class Car:
    def __init__(self, color, mileage):
        self.color = color
        self.mileage = mileage

    def __repr__(self):
        return (f'{self.__class__.__name__}({self.color!r}, {self.mileage!r})')

    def __str__(self):
        return f'a {self.color} car'

car = Car('Red', 44444)
print(car)
print(repr(car))


14. Defining Your Own Exception Classes 自定義異常

Why

# 令stack trace更加清晰, 對debug更加友好
# 更好維護
# 例如比單純地raise ValueError更好
class NameTooShortError(ValueError):
    pass

def validate(name):
    if len(name) < 10:
        raise NameTooShortError(name)

print(validate('allen'))

class BaseValidationError(ValueError):
    pass

class NameTooShortError(BaseValidationError):
    pass

class NameTooLongError(BaseValidationError):
    pass

try:
    validate(name)
except BaseValidationError as err:
    handle_validation_error(err)

15. Cloning Objects. 復制(拷貝、克隆)對象

對於mutable objects(可變對象), 有時需要克隆整個對象。意義在於修改克隆出來的對象,而不修改原來的對象。

對於列表,字典,集合這些Python's built-in collections可以通過它們的工廠函數去克隆它們

# 注意,這是shallow copies.
new_list = list(original_list)
new_dict = dict(original_dict)
new_set = set(original_set)

淺復制,深復制

A shallow copy means constructing a new collection object and then
populating it with references to the child objects found in the original.
In essence, a shallow copy is only one level deep. The copying process
does not recurse and therefore won’t create copies of the child objects
themselves.

淺復制例子

>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys = list(xs) # Make a shallow copy

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

>>> xs.append(['new sublist'])
>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9], ['new sublist']]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

>>> xs[1][0] = 'X'
>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9], ['new sublist']]
>>> ys
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]]

深復制例子

>>> import copy
>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> zs = copy.deepcopy(xs)

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> zs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

>>> xs[1][0] = 'X'
>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]]
>>> zs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

復制(淺復制,深復制)任意對象,包括自定義類

淺復制對象

# 在這個例子中,因為用primitive types(基本類型)int去定義坐標,所以這個例子中淺復制和深復制沒區別
import copy

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point({self.x!r}, {self.y!r})'

# 用copy.copy淺復制
a = Point(23, 42)
b = copy.copy(a)

print(a is b)    #False
print(id(a), id(b)) 

深復制對象之前,再談淺復制

# 在這個例子中,Rectangle用Point去作為坐標,這樣就需要深復制
import copy

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point({self.x!r}, {self.y!r})'

class Rectangle:
    def __init__(self, topleft, bottomright):
        self.topleft = topleft
        self.bottomright = bottomright

    def __repr__(self):
        return (f'Rectangle({self.topleft!r}, {self.bottomright!r})')

rect = Rectangle(Point(0, 1), Point(5, 6))
# 淺復制
shallow_rect = copy.copy(rect)

print(rect is shallow_rect) # False
print(rect.topleft is shallow_rect.topleft) # True

>>> rect.topleft.x = 999
>>> rect
Rectangle(Point(999, 1), Point(5, 6))
>>> srect
Rectangle(Point(999, 1), Point(5, 6))

深復制

import copy

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point({self.x!r}, {self.y!r})'

class Rectangle:
    def __init__(self, topleft, bottomright):
        self.topleft = topleft
        self.bottomright = bottomright

    def __repr__(self):
        return (f'Rectangle({self.topleft!r}, {self.bottomright!r})')

rect = Rectangle(Point(0, 1), Point(5, 6))
shallow_rect = copy.deepcopy(rect)

print(rect is shallow_rect) # False
print(rect.topleft is shallow_rect.topleft) # False

更深入復制

看copy模塊
For example, objects can control
how they’re copied by defining the special methods __copy__() and
__deepcopy__() on them. 

總結

淺復制復制第一層,第一層獨立。
深復制復制所有層,完全和原來的對象獨立。

16. Abstract Base Classes 抽象類

Why使用抽象類。如果沒有抽象類,如何實現相關的接口。

這個例子的壞處就是當調用的時候才得到error

class Base:
    def foo(self):
        raise NotImplementedError()

    def bar(self):
        raise NotImplementedError()


class Concrete(Base):
    def foo(self):
        return 'foo called'

c = Concrete()
c.foo()
c.bar() # NotImplementedError

更安全的做法是使用abc模塊。令類繼承結構更加可維護。

from abc import ABCMeta, abstractclassmethod

class Base(metaclass=ABCMeta):
    @abstractclassmethod
    def foo(self):
        pass

    @abstractclassmethod
    def bar(self):
        pass

class Concrete(Base):
    def foo(self):
        pass

c = Concrete()


17. Namedtuples. 命名元組

namedtuple是tuple的拓展。

namedtuple解決tuple只能通過index訪問、元祖內元素是隨機的問題(例如很難去確定兩個tuple擁有相同的元素和個數)。可提高可讀性。

from collections import namedtuple


Car1 = namedtuple('Car1', 'color mileage')
Car2 = namedtuple('Car2', [
    'Color',
    'mileage'
])


my_car = Car1('red', 44444)

# 特點1. 可通過元素名訪問
print(my_car.color)
print(my_car.mileage)

>>> color, mileage = my_car1
>>> print(color, mileage)
red 44444
>>> print(*my_car)
red 44444

# 特點2. 和元組一樣。是不可變的。
>>> my_car.color = 'blue'
AttributeError: "can't set attribute"

namedtuples are a memoryefficient shortcut to defining an immutable class in Python manually.

即namedtuple是基於class的。所以可以繼承它。但這個例子little clunky

Car = namedtuple('Car', 'color mileage')

class MyCarWithMethods(Car):
    def hexcolor(self):
        if self.color == 'red':
            return '#ff0000'
        else:
            return '#000000'

>>> c = MyCarWithMethods('red', 1234)
>>> c.hexcolor()
'#ff0000'

#  It might be worth doing if you want a class with immutable properties, but it’s also easy to shoot yourself in the foot here

The easiest way to create hierarchies of namedtuples is to use the base tuple’s _fields property:

Car = namedtuple('Car', 'color mileage)
ElectricCar = namedtuple('ElectricCar', Car._fields + ('charge',))

>>> ElectricCar('red', 1234, 45.0)
ElectricCar(color='red', mileage=1234, charge=45.0)

built-in helper methods。 內置的工具方法

namedtuple的built-in helper methods是以_開頭。眾所周知,_開頭的方法是非公開接口。但是這里為了不和自定義的變量名沖突而在前面加_, 所以放心使用_開頭的方法。它們是namedtuple的公共接口。

>>> my_car._asdict()
OrderedDict([('color', 'red'), ('mileage', 3812.4)])

>>> json.dumps(my_car._asdict())
'{"color": "red", "mileage": 3812.4}'

>>> my_car._replace(color='blue')
Car(color='blue', mileage=3812.4)

>>> Car._make(['red', 999])
Car(color='red', mileage=999)

什么時候用

Using namedtuples over unstructured tuples and dicts can also make
my coworkers’ lives easier because they make the data being passed
around “self-documenting” (to a degree).

On the other hand, I try not to use namedtuples for their own sake if
they don’t help me write “cleaner” and more maintainable code. Like
many other techniques shown in this book, sometimes there can be
too much of a good thing.

However, if you use them with care, namedtuples can undoubtedly
make your Python code better and more expressive.

18. Class vs Instance Variable Pitfalls 類變量和實例變量的缺陷

# Class variables are for data shared by all instances of a class.

class Dog:
    num_legs = 4 # <- Class variable

    def __init__(self, name):
        self.name = name # <- Instance variable

>>> jack = Dog('Jack')
>>> jill = Dog('Jill')
>>> jack.name, jill.name
('Jack', 'Jill')

缺陷1

實例中的__class__中的類變量不同步,因為這種行為會創造一個與類變量相同名字的變量(淺復制)

>>> jack.num_legs, jack.__class__.num_legs
(6, 4)

缺陷2

# Good
class CountedObject:
    num_instances = 0

    def __init__(self):
        self.__class__.num_instances += 1

>>> CountedObject.num_instances
0
>>> CountedObject().num_instances
1
>>> CountedObject().num_instances
2
>>> CountedObject().num_instances
3
>>> CountedObject.num_instances
3
# 這里有bug, 注意construtor
class BuggyCountedObject:
    num_instances = 0

    def __init__(self):
        self.num_instances += 1 # !!!

>>> BuggyCountedObject.num_instances
0
>>> BuggyCountedObject().num_instances
1
>>> BuggyCountedObject().num_instances
1
>>> BuggyCountedObject().num_instances
1
>>> BuggyCountedObject.num_instances
0

原因: “shadowed” the
num_instance class variable by creating an instance variable of the
same name in the constructor.

19. Instance, Class, and Static Methods Demystified 實例方法、類方法、靜態方法

class MyClass:
    # self訪問實例本身
    def method(self):
        return 'instance method called', self

    # cls訪問類本身
    @classmethod
    def classmethod(cls):
        return 'class method called', cls

    @staticmethod
    def staticmethod():
        return 'static method called'

m = MyClass()
print(m.method())
print(m.classmethod())
print(m.staticmethod())

Chapter 5 Common Data Structures in Python


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM