读书笔记, 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!')

1310818-20190219125755752-528167842.png

双刃剑. 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')

1310818-20190219131116255-606643449.png

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

1310818-20190221015949369-924160865.png

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)

1310818-20190221020604987-694752893.png

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

1310818-20190221233520725-1194862896.png

保持被修饰函数的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())

1310818-20190221233827986-1686562591.png


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

1310818-20190222003742437-156102836.png


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

1310818-20190222125218845-717504762.png

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

1310818-20190223213533535-405788290.png


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

转载于:https://www.cnblogs.com/allen2333/p/10400379.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值