最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

python with提前退出遇到的坑與解決方案

 更新時(shí)間:2018年01月05日 11:01:11   作者:豬了個(gè)去  
這篇文章主要介紹了python with提前退出遇到的坑與解決方法,需要的朋友參考下吧

問(wèn)題的起源

早些時(shí)候使用with實(shí)現(xiàn)了一版全局進(jìn)程鎖,希望實(shí)現(xiàn)以下效果:

with CacheLock("test_lock", 10):
  #如果搶占到鎖,那么就執(zhí)行這段代碼
  # 否則,讓with提前退出

全局進(jìn)程鎖本身不用多說(shuō),大部分都依靠外部的緩存來(lái)實(shí)現(xiàn)的,redis上用的是setnx,有時(shí)候根據(jù)需要加上緩存擊穿問(wèn)題、隨機(jī)延后以防止對(duì)緩存本身造成壓力

當(dāng)時(shí)同樣寫(xiě)了單元測(cè)試來(lái)測(cè)試這段代碼的有效性:

with CacheLock("test_lock", 10):
  value = cache.get("test_lock")
  self.assertEqual(value, 1)
  with CacheLock("test_lock", 10):
    # 不會(huì)進(jìn)到這里
    self.assertFalse(True)
value = cache.get("test_lock")
self.assertEqual(value, None)

看起來(lái)非常完美地通過(guò)了。

這樣的一個(gè)全局進(jìn)程鎖是通過(guò) __enter__ 方法拋出異常, __exit__ 方法中捕獲異常來(lái)實(shí)現(xiàn)的:

class CacheLock(object):
  def __init__(self, lock_key, lock_timeout):
    self.lock_key = lock_key
    self.lock_timeout = lock_timeout
    self.success = False
  def __enter__(self):
    self.success = cache.lock(self.lock_key, self.lock_timeout)
    if self.success:
      return self
    else:
      raise LockException("not have lock")
  def __exit__(self, exc_type, exc_value, traceback):
    #沒(méi)有搶到鎖的時(shí)候,啥都不做?
    if self.success:
      await cache.delete(self.lock_key)
    if isinstance(exc_value, LockException):
      return True
    if exc_type:
      raise exc_value

看起來(lái)還不錯(cuò),畢竟單元測(cè)試都過(guò)了。

但是,這樣的實(shí)現(xiàn)是有問(wèn)題的:

原因在于 __exit__ 的執(zhí)行不是包在 __enter__ 之外的,因此 __enter__ 拋出的異常,不會(huì)被 __exit__ 捕獲。

上面的單元測(cè)試恰好通過(guò),是因?yàn)槠渲杏袃蓚€(gè)with語(yǔ)句,外面的with 捕獲的其實(shí)是里面的 __enter__ 拋出的異常

使用改進(jìn)后的單元測(cè)試:

cache.set("test_lock",1)
with CacheLock("test_lock", 10):
  self.assertFalse(True)
value = cache.get("test_lock")
self.assertEqual(value, None)

就會(huì)發(fā)現(xiàn)單元測(cè)試過(guò)不去了。

這個(gè)問(wèn)題是我試圖使用with實(shí)現(xiàn)另一個(gè)邏輯:AB測(cè)試 時(shí)出現(xiàn)的,同樣是 __enter__ 拋出異常, __exit__ 試圖捕獲:

import operator
class EarlyExit(Exception):
  pass
class ABContext(object):
  """AB測(cè)試上下文
  >>> with ABContext(newVersion, consts.ABEnum.layer2):
  >>>   # dosomething
  """
  def __init__(self, version, ab_layer, relationship="eq"):
    self.version = version
    self.ab_layer = ab_layer
    # 如果不存在這種操作符,那就提前報(bào)錯(cuò)
    self.relationship = getattr(operator, relationship)
  def __enter__(self):
    # 如果不滿足條件,等于不執(zhí)行上下文中的內(nèi)容
    if not self.relationship(self.version, self.ab_layer.value):
      raise EarlyExit("not match")
    return self
  def __exit__(self, exc_type, exc_value, traceback):
    if exc_value is None:
      return True
    if isinstance(exc_value, EarlyExit):
      return True
    if exc_type:
      raise exc_value
    return True

調(diào)試沒(méi)有通過(guò)的單元測(cè)試的時(shí)候發(fā)現(xiàn),拋出異常后根本沒(méi)有執(zhí)行到 __enter__

第一種解決方案

既然想明白了with的執(zhí)行順序,那么第一種解決方案就呼之欲出了:既然__exit__捕獲的異常在__enter__執(zhí)行完成之后,那么我們提供一個(gè)函數(shù)確認(rèn)一下就可以了,把ABContext實(shí)現(xiàn)改成這樣:

def ensure(self):
    if not self.relationship(self.version, self.ab_layer.value):
      raise EarlyExit("not match")
  def __enter__(self):
    # 如果不滿足條件,等于不執(zhí)行上下文中的內(nèi)容
    return self

使用的時(shí)候:

with ABContext(newVersion, consts.ABEnum.layer2) as c:
  c.ensure()
  # 執(zhí)行其他的想要執(zhí)行的代碼

但這樣的解決方法并不優(yōu)雅,萬(wàn)一使用這個(gè)ABContext的時(shí)候忘記用ensure方法了,那么就等于完全沒(méi)用這個(gè)Context方法,太容易失誤了,而且代碼也失去了Pythonic的性質(zhì)

第二種解決方法

翻了一下contextlib的標(biāo)準(zhǔn)庫(kù)文檔,發(fā)現(xiàn)有一個(gè)已經(jīng)廢棄的函數(shù): contextlib.nested

from contextlib import nested
with nested(*managers):
  do_something()

可以執(zhí)行多個(gè)上下文.

from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
  do_something()
# is equivalent to this:
m1, m2, m3 = A(), B(), C()
with m1 as X:
  with m2 as Y:
    with m3 as Z:
      do_something()

這個(gè)廢棄的特性在Python2.7之后,可以直接由with關(guān)鍵字執(zhí)行,形如:

with context1,context2:
  #do something

這個(gè)特性還不錯(cuò),根據(jù) __enter__ 的執(zhí)行順序的話,那么我們可以實(shí)現(xiàn)一個(gè)由第一個(gè) context的__exit__來(lái)捕獲,第二個(gè)context的__enter__來(lái)拋出異常,

如同這樣:

class AlwaySuccessContext(object):
  def __enter__(self):
    return self
  def __exit__(self, exc_type, exc_value, traceback):
    if isinstance(exc_value, EarlyExit):
      return True
    if exc_type:
      raise exc_value
    return True

結(jié)合前面我們實(shí)現(xiàn)的ABContext的使用是這樣的:

def test_context_noteq(self):
    obj = MagicMock(return_value=True)
    with AlwaySuccessContext(), ABContext(2, const.ABTestEnum.control):
      self.assertFalse(obj())
    obj.assert_not_called()

good,單元測(cè)試就這樣過(guò)了

能不能再給力點(diǎn)?

確實(shí),在with里要寫(xiě)倆context有點(diǎn)蛋疼,并不是特別優(yōu)雅,能不能還是回到最初的那種用法:我們只用寫(xiě)一條context,這一個(gè)context做到了兩個(gè)context的事情?

要是nested那個(gè)函數(shù)還在就好了。。要的其實(shí)就是它的功能。

Python3.1之后contextlib提供了一個(gè)ExitStack的功能來(lái)提供一個(gè)模擬的功能,但試了一下發(fā)現(xiàn),實(shí)際上只調(diào)用了__enter__方法,但沒(méi)有做對(duì)應(yīng)的異常捕獲

第三種解決方案

哈哈哈哈把自己繞到圈子里去了,想了一下,同樣是一個(gè)縮進(jìn)的代碼塊,為什么不能用if來(lái)解決呢!不就是個(gè):

def test_context_noteq(self):
    # 不等的時(shí)候,不會(huì)執(zhí)行with里的內(nèi)容
    obj = MagicMock(return_value=True)
    context = ABContext(2, const.ABTestEnum.control)
    # print(type(context))
    if ABContext(2, const.ABTestEnum.control):
      self.assertFalse(obj())
    obj.assert_not_called()

TIL

總之學(xué)到了contextlib里的一些有用的函數(shù)和裝飾器,也第一次發(fā)現(xiàn)with可以放個(gè)context

雖然放多個(gè)context的動(dòng)態(tài)構(gòu)造還有待研究,with 后面的代碼塊也不能填一個(gè)元組或者列表。。惆悵。。

相關(guān)文章

  • python實(shí)現(xiàn)三次樣條插值

    python實(shí)現(xiàn)三次樣條插值

    這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)三次樣條插值,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • CentOS 7 安裝python3.7.1的方法及注意事項(xiàng)

    CentOS 7 安裝python3.7.1的方法及注意事項(xiàng)

    這篇文章主要介紹了CentOS 7 安裝python3.7.1的方法,文中給大家提到了注意事項(xiàng),需要的朋友可以參考下
    2018-11-11
  • Python之?dāng)?shù)據(jù)序列化(json、pickle、shelve)詳解

    Python之?dāng)?shù)據(jù)序列化(json、pickle、shelve)詳解

    這篇文章主要介紹了Python之?dāng)?shù)據(jù)序列化(json、pickle、shelve)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • Flask使用Pyecharts在單個(gè)頁(yè)面展示多個(gè)圖表的方法

    Flask使用Pyecharts在單個(gè)頁(yè)面展示多個(gè)圖表的方法

    這篇文章主要介紹了Flask使用Pyecharts在單個(gè)頁(yè)面展示多個(gè)圖表的方法,在Flask頁(yè)面展示echarts,主要有兩種方法,文中給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2019-08-08
  • Python查找算法之折半查找算法的實(shí)現(xiàn)

    Python查找算法之折半查找算法的實(shí)現(xiàn)

    這篇文章主要介紹了Python查找算法之折半查找算法的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Windows64x下VScode下載過(guò)程

    Windows64x下VScode下載過(guò)程

    這篇文章主要介紹了Windows64x下VScode下載,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • 詳解Python中的斷點(diǎn)類(lèi)型

    詳解Python中的斷點(diǎn)類(lèi)型

    在?Python?中,斷點(diǎn)是一種在代碼中設(shè)置的標(biāo)記,用于在程序執(zhí)行過(guò)程中停止或中斷程序的執(zhí)行,本文將詳細(xì)介紹?Python?中的斷點(diǎn)類(lèi)型,感興趣的可以了解下
    2024-02-02
  • Python實(shí)現(xiàn)關(guān)鍵路徑和七格圖計(jì)算詳解

    Python實(shí)現(xiàn)關(guān)鍵路徑和七格圖計(jì)算詳解

    這篇文章主要為大家詳細(xì)介紹了如何利用Python實(shí)現(xiàn)關(guān)鍵路徑和七格圖計(jì)算,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下
    2023-03-03
  • Python算法模塊之hashlib模塊詳解

    Python算法模塊之hashlib模塊詳解

    這篇文章主要介紹了Python算法模塊之hashlib模塊詳解,hash是一種算法,不同的hash算法只是復(fù)雜度不一樣,該算法接受傳入的內(nèi)容,經(jīng)過(guò)運(yùn)算得到一串hash值,本文提供了部分實(shí)例代碼方便理解,需要的朋友可以參考下
    2023-08-08
  • Python天氣語(yǔ)音播報(bào)小助手

    Python天氣語(yǔ)音播報(bào)小助手

    馬上就要迎來(lái)國(guó)慶小長(zhǎng)假了,激不激動(dòng),興不興奮!那今年國(guó)慶:天氣怎么樣?能不能出門(mén)逛街?能不能出去旅游?旅游出門(mén)就要挑個(gè)好的天氣!下雨天哪兒哪兒都不舒服。今天小編帶大家寫(xiě)一款Python天氣語(yǔ)音播報(bào)小助手
    2021-09-09

最新評(píng)論

云阳县| 许昌市| 东安县| 洪雅县| 渑池县| 黄平县| 翁源县| 高唐县| 蓝田县| 泰顺县| 陇西县| 颍上县| 平利县| 高安市| 耿马| 苍溪县| 铜山县| 涡阳县| 巫山县| 瓮安县| 蓝田县| 龙里县| 田东县| 从江县| 华池县| 格尔木市| 桦南县| 湖口县| 浮梁县| 五大连池市| 霍州市| 阿尔山市| 秭归县| 金湖县| 延长县| 亚东县| 汉源县| 保康县| 二手房| 凤庆县| 嘉禾县|