@Pythonetc 2018年8月



这是来自@pythonetc feed的Python技巧和编程的第三个集合。

先前的选择:



工厂方法


如果在__init__内创建新对象,则最好将它们作为参数传递给它们,然后使用factory方法创建该对象。 这会将业务逻辑与创建对象的技术实现分开。

在此示例中, __init__接受hostport作为创建数据库连接的参数:

 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())
  • 一个类可以具有任意数量的工厂方法。 您不仅可以使用hostport创建连接,还可以克隆另一个连接,读取配置文件,使用默认连接等。
  • 可以将类似的工厂方法转换为异步函数,这对于__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 # prints 'right' print(Bottom().foo()) 

请记住,取决于最初从何处调用方法, 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相同的行为。

Source: https://habr.com/ru/post/zh-CN422789/


All Articles