@Pythonetc 2018年9月


这是来自@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) # Never called print(Foo(0)) 

这也意味着您不应使用常规构造函数( Foo(...) )在__new__实例化同一类。 这可能导致重复执行__init__ ,甚至导致无限递归。


无限递归:


 class Foo: def __new__(cls, x): return Foo(-x) # Recursion 

双重执行__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]) # 'b' 

这里的不寻常之处在于[]运算符支持唯一语法。 使用它,您不仅可以获得[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不执行任何操作,仅存储属性startstopstep


 In : s = slice(1, 2, 3) In : s.start Out: 1 In : s.stop Out: 2 In : s.step Out: 3 

中断异步协程


可以使用cancel()方法中止任何可执行的协程asyncio 。 在这种情况下,一个CanceledError将被发送到协程,结果,这个和所有与其关联的协程将被中断,直到发现并抑制了该错误为止。


CancelledErrorException的子类,这意味着可以使用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_laterloop.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() 

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


All Articles