这是来自
@pythonetc feed的Python技巧和编程的第三个集合。
先前的选择:
工厂方法
如果在
__init__
内创建新对象,则最好将它们作为参数传递给它们,然后使用factory方法创建该对象。 这会将业务逻辑与创建对象的技术实现分开。
在此示例中,
__init__
接受
host
和
port
作为创建数据库连接的参数:
class Query: def __init__(self, host, port): self._connection = Connection(host, port)
重构选项:
class Query: def __init__(self, connection): self._connection = connection @classmethod def create(cls, host, port): return cls(Connection(host, port))
此方法至少具有以下优点:
- 部署很容易。 在测试中,您可以执行
Query(FakeConnection())
。 - 一个类可以具有任意数量的工厂方法。 您不仅可以使用
host
和port
创建连接,还可以克隆另一个连接,读取配置文件,使用默认连接等。 - 可以将类似的工厂方法转换为异步函数,这对于
__init__
绝对是不可能的。
超级或下一个
super()
函数使您可以引用基类。 这在派生类要向方法的实现中添加某些内容而不是完全覆盖该方法的情况下非常有用。
class BaseTestCase(TestCase): def setUp(self): self._db = create_db() class UserTestCase(BaseTestCase): def setUp(self): super().setUp() self._user = create_user()
“超级”这个名称并不表示“超级”。 在这种情况下,它的意思是“更高层次”(例如,在“主管”一词中)。 同时,
super()
并不总是引用基类;它可以轻松地返回子类。 因此,使用名称
next()
会更正确,因为根据MRO返回了下一个类。
class Top: def foo(self): return 'top' class Left(Top): def foo(self): return super().foo() class Right(Top): def foo(self): return 'right' class Bottom(Left, Right): pass
请记住,取决于最初从何处调用方法,
super()
可以产生不同的结果。
>>> Bottom().foo() 'right' >>> Left().foo() 'top'
用于创建类的自定义名称空间
通过两个大步骤创建一个类。 首先,执行类的主体,就像函数的主体一样。 在第二步中,元类使用默认的结果名称空间(由
locals()
返回
locals()
(默认为
type
)创建类对象。
class Meta(type): def __new__(meta, name, bases, ns): print(ns) return super().__new__( meta, name, bases, ns ) class Foo(metaclass=Meta): B = 2
此代码显示
{'__module__': '__main__', '__qualname__':'Foo', 'B': 3}
。
显然,如果您引入类似
B = 2; B = 3
B = 2; B = 3
,则元类将仅看到
B = 3
,因为只有此值以
ns
。 此限制源于以下事实:元类仅在执行主体之后才开始工作。
但是,您可以通过滑动自己的名称空间来干预执行过程。 默认情况下使用简单的字典,但是如果您使用元类中的
__prepare__
方法,则可以提供自己的对象,类似于字典。
class CustomNamespace(dict): def __setitem__(self, key, value): print(f'{key} -> {value}') return super().__setitem__(key, value) class Meta(type): def __new__(meta, name, bases, ns): return super().__new__( meta, name, bases, ns ) @classmethod def __prepare__(metacls, cls, bases): return CustomNamespace() class Foo(metaclass=Meta): B = 2 B = 3
代码执行结果:
__module__ -> __main__ __qualname__ -> Foo B -> 2 B -> 3
这样
enum.Enum
防止
enum.Enum
重复 。
matplotlib
matplotlib
是一个复杂而灵活的Python图形库。 许多产品都支持它,包括Jupyter和Pycharm。 这是一个使用
matplotlib
渲染简单分形的示例:
https :
matplotlib
(请参见本出版物的标题图像)。
时区支持
Python提供了一个强大的
datetime
库,用于处理日期和时间。 奇怪的是,
datetime
对象具有支持时区的特殊接口(即
tzinfo
属性),但是此模块对上述接口的支持有限,因此,部分工作分配给了其他模块。
其中最受欢迎的是
pytz
。 但是事实是
pytz
并不完全符合
tzinfo
接口。 在
pytz
文档的开头就说明了这一点:“此库不同于已记录的tzinfo实现的Python API。”
您不能将
pytz
对象用作
tzinfo
。 如果尝试执行此操作,则可能会导致完全疯狂的结果:
In : paris = pytz.timezone('Europe/Paris') In : str(datetime(2017, 1, 1, tzinfo=paris)) Out: '2017-01-01 00:00:00+00:09'
注意偏移量+00:09。 像这样使用Pytz:
In : str(paris.localize(datetime(2017, 1, 1))) Out: '2017-01-01 00:00:00+01:00'
此外,在进行任何算术运算之后,您需要对
datetime
对象应用
normalize
,以避免更改偏移量(例如,在DST周期的边界)。
In : new_time = time + timedelta(days=2) In : str(new_time) Out: '2018-03-27 00:00:00+01:00' In : str(paris.normalize(new_time)) Out: '2018-03-27 01:00:00+02:00'
如果您具有Python 3.6,则文档建议使用
dateutil.tz
而不是
pytz
。 该库与
tzinfo
完全兼容,可以将其作为属性传递,而无需使用
normalize
。 是的,它的运行速度更慢。
如果您想知道
pytz
为什么不支持
datetime
API或想要查看更多示例,请阅读本文。
魔术StopIteration
每次调用
next(x)
,它都会从迭代器
x
返回一个新值,直到引发异常为止。 如果结果为
StopIteration
,则迭代器将耗尽,无法再提供值。 如果生成器是迭代的,则在主体结尾处,它将自动引发
StopIteration
:
>>> def one_two(): ... yield 1 ... yield 2 ... >>> i = one_two() >>> next(i) 1 >>> next(i) 2 >>> next(i) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
StopIteration
可以由调用
next
的工具自动处理:
>>> list(one_two()) [1, 2]
但是问题在于,生成器主体中出现的任何明显不期望的StopIteration都会被静默接受为生成器结束的标志,而不是像其他任何异常一样的错误:
def one_two(): yield 1 yield 2 def one_two_repeat(n): for _ in range(n): i = one_two() yield next(i) yield next(i) yield next(i) print(list(one_two_repeat(3)))
在这里,最后一个
yield
是一个错误:抛出
StopIteration
异常会停止
list(...)
的迭代。 我们得到结果
[1, 2]
。 但是,在Python 3.7中,此行为已更改。 外星人
StopIteration
替换为
RuntimeError
:
Traceback (most recent call last): File "test.py", line 10, in one_two_repeat yield next(i) StopIteration The above exception was the direct cause of the following exception: Traceback (most recent call last): File "test.py", line 12, in <module> print(list(one_two_repeat(3))) RuntimeError: generator raised StopIteration
您可以使用
__future__ import generator_stop
启用与Python 3.5相同的行为。