中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python 3.8中怎么實現一個functools.cached_property功能

發布時間:2021-07-24 15:24:24 來源:億速云 閱讀:257 作者:Leah 欄目:編程語言

這篇文章將為大家詳細講解有關Python 3.8中怎么實現一個functools.cached_property功能,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

  bottle.cached_property

  Bottle是我最早接觸的Web框架,也是我第一次閱讀的開源項目源碼。最早知道 cached_property 就是通過這個項目,如果你是一個Web開發,我不建議你用這個框架,但是源碼量少,值得一讀。

  werkzeug.utils.cached_property

  Werkzeug是Flask的依賴,是應用 cached_property 最成功的一個項目。

  pip._vendor.distlib.util.cached_property

  PIP是Python官方包管理工具。

  kombu.utils.objects.cached_property

  Kombu是Celery的依賴。

  django.utils.functional.cached_property

  Django是知名Web框架,你肯定聽過。

  甚至有專門的一個包: pydanny/cached-property。

  如果你犯過他們的代碼其實大同小異,在我的觀點里面這種輪子是完全沒有必要的。Python 3.8給 functools 模塊添加了 cached_property 類,這樣就有了官方的實現了。

  PS: 其實這個Issue 2014年就建立了,5年才被Merge!

  Python 3.8的cached_property

  借著這個小章節我們了解下怎么使用以及它的作用(其實看名字你可能已經猜出來):

  ./python.exe

  Python 3.8.0a4+ (heads/master:9ee2c264c3, May 28 2019, 17:44:24)

  [Clang 10.0.0 (clang-1000.11.45.5)] on darwin

  Type "help", "copyright", "credits" or "license" for more information.

  >>> from functools import cached_property

  >>> class Foo:

  ... @cached_property

  ... def bar(self):

  ... print('calculate somethings')

  ... return 42

  ...

  >>> f = Foo()

  >>> f.bar

  calculate somethings

  42

  >>> f.bar

  42

  上面的例子中首先獲得了Foo的實例f,第一次獲得 f.bar 時可以看到執行了bar方法的邏輯(因為執行了print語句),之后再獲得 f.bar 的值并不會在執行bar方法,而是用了緩存的屬性的值。

  標準庫中的版本還有一種的特點,就是加了線程鎖,防止多個線程一起修改緩存。通過對比Werkzeug里的實現幫助大家理解一下:

  import time

  from threading import Thread

  from werkzeug.utils import cached_property

  class Foo:

  def __init__(self):

  self.count = 0

  @cached_property

  def bar(self):

  time.sleep(1) # 模仿耗時的邏輯,讓多線程啟動后能執行一會而不是直接結束

  self.count += 1

  return self.count

  threads = []

  f = Foo()

  for x in range(10):

  t = Thread(target=lambda: f.bar)

  t.start()

  threads.append(t)

  for t in threads:

  t.join()

  這個例子中,bar方法對 self.count 做了自增1的操作,然后返回。但是注意f.bar的訪問是在10個線程下進行的,里面大家猜現在 f.bar 的值是多少?

  ipython -i threaded_cached_property.py

  Python 3.7.1 (default, Dec 13 2018, 22:28:16)

  Type 'copyright', 'credits' or 'license' for more information

  IPython 7.5.0 -- An enhanced Interactive Python. Type '?' for help.

  In [1]: f.bar

  Out[1]: 10

  結果是10。也就是10個線程同時訪問 f.bar ,每個線程中訪問時由于都還沒有緩存,就會給 f.count 做自增1操作。第三方庫對于這個問題可以不關注,只要你確保在項目中不出現多線程并發訪問場景即可。但是對于標準庫來說,需要考慮的更周全。我們把 cached_property 改成從標準庫導入,感受下:

  ./python.exe

  Python 3.8.0a4+ (heads/master:8cd5165ba0, May 27 2019, 22:28:15)

  [Clang 10.0.0 (clang-1000.11.45.5)] on darwin

  Type "help", "copyright", "credits" or "license" for more information.

  >>> import time

  >>> from threading import Thread

  >>> from functools import cached_property

  >>>

  >>>

  >>> class Foo:

  ... def __init__(self):

  ... self.count = 0

  ... @cached_property

  ... def bar(self):

  ... time.sleep(1)

  ... self.count += 1

  ... return self.count

  ...

  >>>

  >>> threads = []

  >>> f = Foo()

  >>>

  >>> for x in range(10):

  ... t = Thread(target=lambda: f.bar)

  ... t.start()

  ... threads.append(t)

  ...

  >>> for t in threads:

  ... t.join()

  ...

  >>> f.bar

  可以看到,由于加了線程鎖, f.bar 的結果是正確的1。

  cached_property不支持異步

  除了 pydanny/cached-property 這個包以外,其他的包都不支持異步函數:

  ./python.exe -m asyncio

  asyncio REPL 3.8.0a4+ (heads/master:8cd5165ba0, May 27 2019, 22:28:15)

  [Clang 10.0.0 (clang-1000.11.45.5)] on darwin

  Use "await" directly instead of "asyncio.run()".

  Type "help", "copyright", "credits" or "license" for more information.

  >>> import asyncio

  >>> from functools import cached_property

  >>>

  >>>

  >>> class Foo:

  ... def __init__(self):

  ... self.count = 0

  ... @cached_property

  ... async def bar(self):

  ... await asyncio.sleep(1)

  ... self.count += 1

  ... return self.count

  ...

  >>> f = Foo()

  >>> await f.bar

  1

  >>> await f.bar

  Traceback (most recent call last):

  File "/Users/dongwm/cpython/Lib/concurrent/futures/_base.py", line 439, in result

  return self.__get_result()

  File "/Users/dongwm/cpython/Lib/concurrent/futures/_base.py", line 388, in __get_result

  raise self._exception

  File "", line 1, in

  RuntimeError: cannot reuse already awaited coroutine

  pydanny/cached-property的異步支持實現的很巧妙,我把這部分邏輯抽出來:

  try:

  import asyncio

  except (ImportError, SyntaxError):

  asyncio = None

  class cached_property:

  def __get__(self, obj, cls):

  ...

  if asyncio and asyncio.iscoroutinefunction(self.func):

  return self._wrap_in_coroutine(obj)

  ...

  def _wrap_in_coroutine(self, obj):

  @asyncio.coroutine

  def wrapper():

  future = asyncio.ensure_future(self.func(obj))

  obj.__dict__[self.func.__name__] = future

  return future

  return wrapper()

  我解析一下這段代碼:

  對 import asyncio 的異常處理主要為了處理Python 2和Python3.4之前沒有asyncio的問題

  __get__ 里面會判斷方法是不是協程函數,如果是會 return self._wrap_in_coroutine(obj)

  _wrap_in_coroutine 里面首先會把方法封裝成一個Task,并把Task對象緩存在 obj.__dict__ 里,wrapper通過裝飾器 asyncio.coroutine 包裝最后返回。

  為了方便理解,在IPython運行一下:

  In : f = Foo()

  In : f.bar # 由于用了`asyncio.coroutine`裝飾器,這是一個生成器對象

  Out: .wrapper at 0x10a26f0c0>

  In : await f.bar # 第一次獲得f.bar的值,會sleep 1秒然后返回結果

  Out: 1

  In : f.__dict__['bar'] # 這樣就把Task對象緩存到了f.__dict__里面了,Task狀態是finished

  Out: :4> result=1>

  In : f.bar # f.bar已經是一個task了

  Out: :4> result=1>

  In : await f.bar # 相當于 await task

  Out: 1可以看到多次await都可以獲得正常結果。如果一個Task對象已經是finished狀態,直接返回結果而不會重復執行了。

關于Python 3.8中怎么實現一個functools.cached_property功能就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

迁安市| 屯门区| 南陵县| 丰镇市| 木里| 深水埗区| 卢氏县| 紫云| 泸水县| 瑞丽市| 剑河县| 邵东县| 玉山县| 临澧县| 信丰县| 闻喜县| 将乐县| 墨竹工卡县| 通辽市| 昂仁县| 舒城县| 丹寨县| 且末县| 大田县| 布拖县| 丹棱县| 金平| 宣化县| 辉县市| 会理县| 农安县| 徐州市| 阿拉善左旗| 桃园县| 沙雅县| 遵义县| 嘉峪关市| 乌恰县| 石阡县| 德江县| 桓台县|