piglei / one-python-craftsman Goto Github PK
View Code? Open in Web Editor NEW来自一位 Pythonista 的编程经验分享,内容涵盖编码技巧、最佳实践与思维模式等方面。
Home Page: https://www.piglei.com
License: Apache License 2.0
来自一位 Pythonista 的编程经验分享,内容涵盖编码技巧、最佳实践与思维模式等方面。
Home Page: https://www.piglei.com
License: Apache License 2.0
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)
静态类型注释是不是改变了变量名定义的方式和需求?
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
文章写的真好,获益匪浅。
感觉这个系列文章可以出一本书了~
另外问一下 写好面向对象代码的原则(下)什么时候更新
^ ^
Hi
I am finding that what you have described are in line with software craftsman's practice. I am not sure if you would consider adding the software craftsmanship manifesto to your articles, so that your readers would be aware of the software craftsmanship movement.
https://manifesto.softwarecraftsmanship.org/#/zh-cn
thanks
非常好的总结和分享
感谢感谢
函数自然是“可被调用”的对象。但除了函数外,我们也可以让任何一个类(class)变得“可被调用”(callable)。办法很简单,只要自定义类的 call 魔法方法即可。
类一直是可被调用的“callable”,这里是不是写错了,应该是object吧
建议代码清单8-7和8-8中,使用functools.update_wrapper
替换update_wrapper
一个是因为文章中并没有说update_wrapper是functools中的,还有就是8-8的代码中有用到functools.partial
,应当统一
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), 看了半天, 觉得不应该啊..... 哈哈
f-string的用法是3.6推出的,在文中多次提到3.5版本,对于新手可能不太友好,望采纳😄
你好,下一篇能否介绍一下python中常用到的设计模式呢,因为要构建高效的python代码,和适用合理的设计模式也分不开吧。
请问朱雷老师对Monkey Patch是怎么看的?
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
没看懂。
min(numbers)>10
在第五章中,您讨论了函数返回值的问题。
在「谨慎使用 None 返回值」的讨论中提到我们应该思考「函数签名(名称与参数)与 None 返回值之间是否存在一种“意料之中”的暗示」。在类似 create_user_from_name()
的函数中,用户预期是能拿到一个结果的。
但在 「空对象模式」中,却手动创造了一个 NullAccount
类,来作为 Account.from_string()
的返回值。在我看来,这难道不是矛盾的建议么?
首先,从 from_string()
的名称与参数,用户应该预期会拿到一个 account
,如果按照上文的建议,如果输入字符串非法,应该直接抛出异常。
其次,如果在这里由于某种原因我们选择返回一个 NullAccount
来代替把异常抛给用户,为什么我们不让 Account.from_string()
在处理异常时直接返回空的 username
和 balance
?
感谢您写的这些文章,读后获益良多!
在阅读写好面向对象代码的原则(上)
这篇文章里,有一个hint:“定义抽象类在 Python 的 OOP 中并不是必须的,你也可以不定义它” 。我想知道,既然不是必须的,那定义编写抽象类的目的是什么?它有什么作用?
62行 class TripSource(IntEum): 应该是class TripSource(IntEnum): 。
这样方便阅读一些^_^
不过这样做也有坏处,因为代码对变更的兼容性虽然变好了,但是你不能继续用之前 x, y = f() 的方式一次解包定义多个变量了。取舍在于你自己。
这句话不妥,使用namedtuple可以这么做
第六篇文章 “Write Cleaner Python: Use Exceptions” 链接已失效。
最佳实践的第二小节别在字符串上走太远,改进后的代码中使用了 !=None ,虽然在这里没有什么大问题,但是既然主题是匠心,还是忍不住说了,😂。另外,对于字符串的比较是用is还是==想听听大神的意见。PS,我明白is和==的区别,却不是很明白该怎么更精确的选择使用,就比如这里的!=None也没有什么问题,想听听关于字符串,甚至内置其他内置数据类型的比较是用is还是==的一个详细解释
感谢作者的分享,优美的文笔,恰当的例子,让我觉得我以前写的不是python。
对于with嵌套作者有没有好的优化思路?写代码的时候遇到要打开多个文件,嵌套好几层with获取 文件对象,感觉很丑。
首先感谢您分享高质量的文章。
刚刚去看了您分享的 “Beyond PEP8 - Best practices for beautiful intelligible code”,也是受益匪浅。
不知道哪里可以找到您分享推荐的一些高质量的文章、视频等资料呢。
在 做一个精通规则的玩家 這一節裏 的 利用集合的游戏规则這一小節 定義了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”慢呀~
例子的代码这么写是不是会更舒服?因为感觉上判断修改应该是类内部的逻辑。
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])
应该是 if len(users):
您好,很棒的一系列文章!阅读过程发现个小问题,该文章的此处:
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协助修正
你好,关于这个问题,我想请教一下,value在这里局部变量吗?
https://github.com/piglei/one-python-craftsman/blob/master/zh_CN/2-if-else-block-secrets.md
第二篇里面的第四节,使用 try/while/for 中 else 分支,第一段函数里面,抛出异常的话应该就直接return了,怎么样都不会运行到do_the_second_thing()了吧。
/zh_CN/4-mastering-container-types.md
底部的:
<<<上一篇【3.编写条件分支代码的技巧】
指向正确,名字错误,应该是:
<<<上一篇【3.使用数字与字符串的技巧】
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'
在处理长度为4201612的字符串时
+=用了91.966637s
join用了10.327656s
虽然上面的情况比较极端,我觉得还是尽量用join更好一些
环境是OS X 10.14.5 Python3.7
更好的使用变量 - 2. 尽量不要用 globals()/locals()
这里的代码示例
def render(request, user_id, trip_id):
pass
return render(request, 'trip.html', locals())
上下两个render应该是对应不同的方法吧?使用相同的方法名是否有点不妥?
写得太棒了,看了以后感觉以前写的代码太烂了,出了实体书一定支持。
《Python 工匠:使用装饰器的技巧》中介绍了 wrapt 这个第三方装饰器库,github 上还有个decorator库,和wrapt比较哪个作为官方装饰器语法的替代品更好呢?
我在python3.7.2上面测试join比+=快200倍
不是 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:
依赖倒置其实和开放封闭是有关的, 应该说依赖倒置就是为了开放封闭.
拿你的例子来说:
如果未来需要实现另一种 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
https://www.zlovezl.cn/这个博客挂了。。。
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')
在枚举处的代码,是不是应该继承IntEnum
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.