
这是来自@pythonetc feed的Python技巧和编程的第四个集合。
先前的选择:
覆盖和过载
有两个容易混淆的概念:覆盖和重载。
当子类定义父类已经提供的方法并由此替换它时,将发生覆盖。 在某些语言中,有必要显式标记覆盖方法(在C#中,使用override
修饰符),而在某些语言中,这是@Override
完成的(在Java中为@Override
注解)。 Python不需要使用特殊的修饰符,并且不提供此类方法的标准标记(出于可读性考虑,有人使用自定义@override
装饰器,该装饰器不执行任何操作)。
过载是另一个故事。 该术语指的是存在多个具有相同名称但具有不同签名的功能的情况。 在Java和C ++中可以重载;它通常用于提供默认参数:
class Foo { public static void main(String[] args) { System.out.println(Hello()); } public static String Hello() { return Hello("world"); } public static String Hello(String name) { return "Hello, " + name; } }
Python不支持仅通过名称通过签名搜索功能。 当然,您可以编写代码来显式分析参数的类型和数量,但这看起来很尴尬,最好避免这种做法:
def quadrilateral_area(*args): if len(args) == 4: quadrilateral = Quadrilateral(*args) elif len(args) == 1: quadrilateral = args[0] else: raise TypeError() return quadrilateral.area()
如果需要类型提示,请使用带有@overload
装饰器的typing
模块:
from typing import overload @overload def quadrilateral_area( q: Quadrilateral ) -> float: ... @overload def quadrilateral_area( p1: Point, p2: Point, p3: Point, p4: Point ) -> float: ...
自动虚拟化
collections.defaultdict
允许您创建一个字典,如果缺少请求的键(而不是抛出KeyError
),则该字典将返回默认值。 要创建defaultdict
您不仅需要提供默认值defaultdict
还需要提供此类值的工厂。
因此,您可以创建具有几乎无限数量的嵌套字典的字典,从而可以使用d[a][b][c]...[z]
。
>>> def infinite_dict(): ... return defaultdict(infinite_dict) ... >>> d = infinite_dict() >>> d[1][2][3][4] = 10 >>> dict(d[1][2][3][5]) {}
这种行为称为“自动复活”,该术语来自Perl。
实例化
对象的实例化涉及两个重要步骤。 首先,从类中调用__new__
方法,该方法创建并返回一个新对象。 然后Python从中调用__init__
方法,该方法设置此对象的初始状态。
但是,如果__new__
返回的对象不是原始类的实例,则不会调用__init__
。 在这种情况下,该对象可以由另一个类创建,这意味着已经在该对象上调用了__init__
:
class Foo: def __new__(cls, x): return dict(x=x) def __init__(self, x): print(x)
这也意味着您不应使用常规构造函数( Foo(...)
)在__new__
实例化同一类。 这可能导致重复执行__init__
,甚至导致无限递归。
无限递归:
class Foo: def __new__(cls, x): return Foo(-x)
双重执行__init__
:
class Foo: def __new__(cls, x): if x < 0: return Foo(-x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x
正确的方法:
class Foo: def __new__(cls, x): if x < 0: return cls.__new__(cls, -x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x
运算符[]和切片
在Python中,您可以通过定义__getitem__
魔术方法来覆盖[]
运算符。 因此,例如,您可以创建一个实际上包含无限数量的重复元素的对象:
class Cycle: def __init__(self, lst): self._lst = lst def __getitem__(self, index): return self._lst[ index % len(self._lst) ] print(Cycle(['a', 'b', 'c'])[100])
这里的不寻常之处在于[]
运算符支持唯一语法。 使用它,您不仅可以获得[2]
,而且可以获得[2:10]
, [2:10:2]
, [2::2]
甚至[:]
。 运算符的语义为:[开始:停止:步骤],但是您可以通过其他任何方式使用它来创建自定义对象。
但是,如果您使用这种语法调用__getitem__
,它将作为索引参数得到什么? 这正是切片对象存在的原因。
In : class Inspector: ...: def __getitem__(self, index): ...: print(index) ...: In : Inspector()[1] 1 In : Inspector()[1:2] slice(1, 2, None) In : Inspector()[1:2:3] slice(1, 2, 3) In : Inspector()[:] slice(None, None, None)
您甚至可以结合使用元组和切片的语法:
In : Inspector()[:, 0, :] (slice(None, None, None), 0, slice(None, None, None))
slice
不执行任何操作,仅存储属性start
, stop
和step
。
In : s = slice(1, 2, 3) In : s.start Out: 1 In : s.stop Out: 2 In : s.step Out: 3
中断异步协程
可以使用cancel()
方法中止任何可执行的协程asyncio
。 在这种情况下,一个CanceledError将被发送到协程,结果,这个和所有与其关联的协程将被中断,直到发现并抑制了该错误为止。
CancelledError
是Exception
的子类,这意味着可以使用try ... except Exception
组合(不try ... except Exception
意外捕获该Exception
,该try ... except Exception
旨在捕获“任何错误”。 为了安全地捕获协程的错误,您必须执行以下操作:
try: await action() except asyncio.CancelledError: raise except Exception: logging.exception('action failed')
执行计划
为了计划在特定时间执行某些代码, asyncio
通常创建一个任务,该任务执行await asyncio.sleep(x)
:
import asyncio async def do(n=0): print(n) await asyncio.sleep(1) loop.create_task(do(n + 1)) loop.create_task(do(n + 1)) loop = asyncio.get_event_loop() loop.create_task(do()) loop.run_forever()
但是创建新任务可能会很昂贵,并且如果您不打算执行异步操作(例如本例中的do
函数),则do
。 相反,可以使用功能loop.call_later
和loop.call_at
,它们可以安排异步回调调用:
import asyncio def do(n=0): print(n) loop = asyncio.get_event_loop() loop.call_later(1, do, n+1) loop.call_later(1, do, n+1) loop = asyncio.get_event_loop() do() loop.run_forever()