Code Monkey home page Code Monkey logo

one-python-craftsman's Issues

next() 函数提高性能的疑惑

Hi @piglei , 您的系列文章让人收益匪浅。

读到第四章“容器的门道”,对 next() 可以高效的实现 “从列表中查找第一个满足条件的成员”,我自己测了一下性能,似乎还不如普通方法。

In [2]: numbers = [3, 7, 8, 2, 21]

In [3]: %timeit [i for i in numbers if i % 2 == 0][0]
445 ns ± 5.35 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [4]: %timeit [i for i in numbers if i % 2 == 0][0]
449 ns ± 5.75 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [5]: %timeit next((i for i in numbers if i % 2 == 0))
501 ns ± 10.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [6]: %timeit next((i for i in numbers if i % 2 == 0))
500 ns ± 4.05 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

静态类型注释

静态类型注释是不是改变了变量名定义的方式和需求?

2.编写条件分支代码的技巧

if user.no_profile_exists:
    +++ profile_func = create_user_profile
    extra_args = {'points': 0, 'created': now()}
else:
    +++ profile_func = update_user_profile
    extra_args = {'updated': now()}

profile_func(
    username=user.username,
    email=user.email,
    age=user.age,
    address=user.address,
    **extra_args
)

你好,上面代码中我标记了 “+++” 的两行可以改成下面这样的吗?

create_user_profile = profile_func
update_user_profile = profile_func

催更

文章写的真好,获益匪浅。
感觉这个系列文章可以出一本书了~
另外问一下 写好面向对象代码的原则(下)什么时候更新
^ ^

感谢

非常好的总结和分享
感谢感谢

让class变得可调用?

函数自然是“可被调用”的对象。但除了函数外,我们也可以让任何一个类(class)变得“可被调用”(callable)。办法很简单,只要自定义类的 call 魔法方法即可。

类一直是可被调用的“callable”,这里是不是写错了,应该是object吧

书籍建议

建议代码清单8-7和8-8中,使用functools.update_wrapper替换update_wrapper
一个是因为文章中并没有说update_wrapper是functools中的,还有就是8-8的代码中有用到functools.partial,应当统一

关于「使用 try/while/for 中 else 分支」

while ... else 和 for ... else 相当的反直觉,引用 Effective Python 中的例子(加了一些注释):

# 看上去 else 是「之前」没有运行才会运行的,实际上之前 for loop 完成之后还是运行了 else
for i in range(3):
  print(i)
else:
  print("ELSE!")      # 可及


# 看上去 for loop 退出了,应该运行 else 了,实际上 else 会被跳过
for i in range(3):
  print(i)
  if i == 1:
    break       # 注意这里的 break
else:
  print("ELSE!")      # 不可及
  

# for loop 是空的时候 直接执行 else
for i in []:
  print(i)
else:
  print("ELSE!")       # 可及


# while 初始条件是 False 时直接执行 else
while False:
    print("ok")
else:
    print("ELSE")      # 可及


a = iter([None, 1, 2, 3, None, 5, 6])
while next(a):
    print('hi')
else:
    print("ELSE")     # 可及

这里也有一些讨论:https://mail.python.org/pipermail/python-ideas/2009-October/006155.html

错了几个字

def enter(self):
# 刚方法将在进入上下文时调用
return self

该方法将在进入上下文时调用 错了个字

还有前面几节举的例子, 列表的命名用 'l'(小写字母l), 看了半天, 觉得不应该啊..... 哈哈

设计模式

你好,下一篇能否介绍一下python中常用到的设计模式呢,因为要构建高效的python代码,和适用合理的设计模式也分不开吧。

第六篇-上下文管理器改善异常处理流程的疑问

class raise_api_error:
    """captures specified exception and raise ApiErrorCode instead

    :raises: AttributeError if code_name is not valid
    """
    def __init__(self, captures, code_name):
        self.captures = captures
        self.code = getattr(error_codes, code_name)

    def __enter__(self):
        # 该方法将在进入上下文时调用
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # 该方法将在退出上下文时调用
        # exc_type, exc_val, exc_tb 分别表示该上下文内抛出的
        # 异常类型、异常值、错误栈
        if exc_type is None:
            return False

        if exc_type == self.captures:
            raise self.code from exc_val
        return False

这里的 raise self.code from exc_val 是啥意思?from exc_val 没看懂。

空对象模式 和 None

在第五章中,您讨论了函数返回值的问题。

在「谨慎使用 None 返回值」的讨论中提到我们应该思考「函数签名(名称与参数)与 None 返回值之间是否存在一种“意料之中”的暗示」。在类似 create_user_from_name() 的函数中,用户预期是能拿到一个结果的。

但在 「空对象模式」中,却手动创造了一个 NullAccount 类,来作为 Account.from_string() 的返回值。在我看来,这难道不是矛盾的建议么?

首先,从 from_string() 的名称与参数,用户应该预期会拿到一个 account,如果按照上文的建议,如果输入字符串非法,应该直接抛出异常。
其次,如果在这里由于某种原因我们选择返回一个 NullAccount 来代替把异常抛给用户,为什么我们不让 Account.from_string() 在处理异常时直接返回空的 usernamebalance

编写抽象类的目的

感谢您写的这些文章,读后获益良多!

在阅读写好面向对象代码的原则(上) 这篇文章里,有一个hint:“定义抽象类在 Python 的 OOP 中并不是必须的,你也可以不定义它” 。我想知道,既然不是必须的,那定义编写抽象类的目的是什么?它有什么作用?

使用namedtuple可以用解包赋值给多个变量

不过这样做也有坏处,因为代码对变更的兼容性虽然变好了,但是你不能继续用之前 x, y = f() 的方式一次解包定义多个变量了。取舍在于你自己。
这句话不妥,使用namedtuple可以这么做

improvement:优化阅读体验

建议在每一篇文章的最后添加两个跳转链接,分别是:

  • 【前一篇文章】
  • 【后一篇文章】

能有效提升连续阅读体验。

is 和 ==的使用

最佳实践的第二小节别在字符串上走太远,改进后的代码中使用了 !=None ,虽然在这里没有什么大问题,但是既然主题是匠心,还是忍不住说了,😂。另外,对于字符串的比较是用is还是==想听听大神的意见。PS,我明白is和==的区别,却不是很明白该怎么更精确的选择使用,就比如这里的!=None也没有什么问题,想听听关于字符串,甚至内置其他内置数据类型的比较是用is还是==的一个详细解释

对于with嵌套如何优化

感谢作者的分享,优美的文笔,恰当的例子,让我觉得我以前写的不是python。

对于with嵌套作者有没有好的优化思路?写代码的时候遇到要打开多个文件,嵌套好几层with获取 文件对象,感觉很丑。

縮進問題

在 做一个精通规则的玩家 這一節裏 的 利用集合的游戏规则這一小節 定義了VisitRecord 這個類,下面的__hash__ 和__eq__兩個方法爲了是不是該有縮進,來匹配前文的語法結構

“不必预计算字面量表达式“的疑问

Python 代码在执行时会被解释器编译成字节码,而真相就藏在字节码里。让我们用 dis 模块看看:

def f1(delta_seconds):
    if delta_seconds < 11 * 24 * 3600:
        return

import dis
dis.dis(f1)

# dis 执行结果
  5           0 LOAD_FAST                0 (delta_seconds)
              2 LOAD_CONST               1 (950400)
              4 COMPARE_OP               0 (<)
              6 POP_JUMP_IF_FALSE       12

  6           8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
        >>   12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

看见上面的 2 LOAD_CONST 1 (950400) 了吗?这表示 Python 解释器在将源码编译成成字节码时,会计算 11 * 24 * 3600 这段整型字面量,并用 950400 替换它。

所以,当我们的代码中需要出现复杂计算的字面量时,请保留整个算式吧。它对性能没有任何影响,而且会增加代码的可读性。

就算字节码会用计算好的结果替换“11 * 24 * 3600”,后面反复执行代码会用优化过的字节码;
但第一次源码编译成字节码时还是会计算“11 * 24 * 3600”的,这一次的速度还是会比直接写“950400”慢呀~

关于EP13的一点小小的建议

例子的代码这么写是不是会更舒服?因为感觉上判断修改应该是类内部的逻辑。

class User(Model):
    """普通用户模型类
    """
    def __init__(self, username: str):
        self.username = username

    def allow_deactivate(self) -> bool:
        """是否允许被停用
        """
        return True

    def _deactivate(self):
        """将当前用户停用
        """
        self.is_active = True
        self.save()

    def deactivate(self) -> bool:
        """将当前用户停用
        """
        if self.allow_deactivate():
            self._deactivate()
            return True
        else:
            return False

class Admin(User):
    """管理员用户类
    """
    def allow_deactivate(self) -> bool:
        # 管理员用户不允许被停用
        return False

def deactivate_users(users: Iterable[User]):
    """批量停用多个用户
    """
    for user in users:
        is_deactivated = user.deactivate()
        if not is_deactivated:
            logger.info(f'user {user.username} does not allow deactivating, skip.')

另外,停用之后is_active变成了True,是不是反了?

勘误

https://github.com/piglei/one-python-craftsman/blob/master/zh_CN/2-if-else-block-secrets.md

`上面的代码里,判断 UserCollection 是否有内容时用到了 users._users 的长度。其实,通过为 UserCollection 添加 len 魔法方法,上面的分支可以变得更简单:

class UserCollection:

def __init__(self, users):
    self._users = users

def __len__(self):
    return len(self._users)

users = UserCollection([piglei, raymond])

定义了 len 方法后,UserCollection 对象本身就可以被用于布尔判断了

if users:
print("There's some users in collection!")
`

应该是 if len(users):

Python suffixed of some code block is missing

您好,很棒的一系列文章!阅读过程发现个小问题,该文章的此处:

https://github.com/piglei/one-python-craftsman/blob/master/zh_CN/3-tips-on-numbers-and-strings.md

但是这样写会破坏整段代码的缩进视觉效果,显得非常突兀。要改善它有很多种办法,比如我们可以把这段多行字符串作为变量提取到模块的最外层。不过,如果在你的代码逻辑里更适合用字面量的话,你也可以用标准库 textwrap 来解决这个问题:

from textwrap import dedent

def main():
    if user.is_active:
        # dedent 将会缩进掉整段文字最左边的空字符串
        message = dedent("""\
            Welcome, today's movie list:
            - Jaw (1975)
            - The Shining (1980)
            - Saw (2004)""")

Code block 上少了 python suffixed:

from textwrap import dedent

def main():
    if user.is_active:
        # dedent 将会缩进掉整段文字最左边的空字符串
        message = dedent("""\
            Welcome, today's movie list:
            - Jaw (1975)
            - The Shining (1980)
            - Saw (2004)""")

如果不介意,我可以发个PR协助修正

链接文本错误

/zh_CN/4-mastering-container-types.md
底部的:
<<<上一篇【3.编写条件分支代码的技巧】
指向正确,名字错误,应该是:
<<<上一篇【3.使用数字与字符串的技巧】

推荐添加用在装饰器上的自省函数 inspect.signature 的使用说明

inspect.signature 可以用来查看内层(被装饰的)函数的参数,提前检查传入外层函数传入的参数是否正确,还可以方便地访问特定参数。

import sys
import inspect
import functools
import traceback


def decorator(func):
    sig = inspect.signature(func)

    @functools.wraps(func)
    def wrap(*args, **kwargs):
        bound_arguments = sig.bind(*args, **kwargs)
        bound_arguments.apply_defaults()
        print(bound_arguments.arguments)
        return func(*args, **kwargs)

    return wrap


@decorator
def boo(a, b, c):
    pass

boo(1, b=2, c=3)

try:
    boo(1, b=2, c=3, d=4)
except TypeError:
    traceback.print_exc(file=sys.stdout)

执行结果

{'a': 1, 'b': 2, 'c': 3}
Traceback (most recent call last):
  File "<ipython-input-1-b43513683a33>", line 27, in <module>
    boo(1, b=2, c=3, d=4)
  File "<ipython-input-1-b43513683a33>", line 12, in wrap
    bound_arguments = sig.bind(*args, **kwargs)
  File "/usr/local/Cellar/[email protected]/3.9.1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/inspect.py", line 3062, in bind
    return self._bind(args, kwargs)
  File "/usr/local/Cellar/[email protected]/3.9.1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/inspect.py", line 3051, in _bind
    raise TypeError(
TypeError: got an unexpected keyword argument 'd'

+=和join效率的问题

在处理长度为4201612的字符串时
+=用了91.966637s
join用了10.327656s
虽然上面的情况比较极端,我觉得还是尽量用join更好一些

环境是OS X 10.14.5 Python3.7

写的很棒

写得太棒了,看了以后感觉以前写的代码太烂了,出了实体书一定支持。

OOP 原则

不是 issue, 只是 comment.

里式替换的另一个比较隐蔽的错误会发生在多层抽象的耦合里.

比如有两个抽象 A 和 B, 类型声明是 A 以 B 作为入参 A(B), 但是如果实际的实现里出现了类型特定化, 那么说明这里的抽象失败了.
具体来讲, 抽象 A 有两个实现 A1, A2; 抽象 B 也有两个实现 B1, B2; 一个符合里式替换的实现应该允许 A1(B1), A1(B2), A2(B1), A2(B2) 这些调用组合, 但是稍微缺少经验的工程师可能会实现为特定类型的入参, 只能运行 A1(B1), A2(B2), 传入不正确的类型就会报错.
这类问题在使用抽象工厂的时候特别常见, 因为抽象工厂里就有多个抽象的多个实现, 一旦业务把多个抽象耦合起来, 就容易违反里式替换.
抽象工厂的 UML 就有 A1 A2 B1 B2:
image


依赖倒置其实和开放封闭是有关的, 应该说依赖倒置就是为了开放封闭.

拿你的例子来说:
image
如果未来需要实现另一种 HNWebPage, 比如叫做 LocalHNWebPage, 没有依赖倒置的话, 业务逻辑代码里 (SiteSourceGrouper) 就要去修改 import 模块, 这就违反了开放封闭, 因为并没有修改业务逻辑, 却要改业务代码; 而使用依赖倒置就完美地开放封闭了.
实际上 Django ORM 就是这样的, 想象一下 view 层里的 import model, 这个 model 其实只是一个抽象, 这样将来就算更换下层数据库的实现 (从 MySQL 换成 PostgreSQL), view 层代码(理论上)不需要做相应更改, 这就是依赖倒置这层抽象带来的好处.

代码没有高亮

Python 工匠:写好面向对象代码的原则(中)
中的一段代码没有高亮 忘写python了,这算Bug吗?还是feature.

原文:

现在,假设我需要写一个函数,来获取和用户有关的所有帖子标题:

def list_user_post_titles(user: User) -> Iterable[str]:
    """获取与用户有关的所有帖子标题
    """
    for post_id in user.list_related_posts():
        yield session.query(Post).get(post_id).title

err_msg未定义?

class CreateItemError(Exception):
"""创建 Item 失败时抛出的异常"""

def create_item(name):
"""创建一个新的 Item

:raises: 当无法创建时抛出 CreateItemError
"""
if len(name) > MAX_LENGTH_OF_NAME:
    raise CreateItemError('name of item is too long')
if len(CURRENT_ITEMS) > MAX_ITEMS_QUOTA:
    raise CreateItemError('items is full')
return Item(name=name)

def create_for_input():
name = input()
try:
item = create_item(name)
except CreateItemError as e:
print(f'create item failed: {err_msg}') #这句是不是有bug,err_msg 无定义
else:
print(f'item<{name}> created')

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.