ホームページ >バックエンド開発 >Python チュートリアル >Python デコレータを深く理解できるサンプル コードの共有

Python デコレータを深く理解できるサンプル コードの共有

黄舟
黄舟オリジナル
2017-06-18 11:33:371418ブラウズ

Python のデコレーターは、Python に入るにはハードルがあり、それを越えるか越えないかに関係なく存在します。 Python のデコレーターの概念はよく人々を混乱させるので、今日は Python のデコレーターを分析しましょう

1 スコープ

Python には、グローバル スコープとローカル スコープの 2 種類のスコープがあります。

グローバル スコープは、ファイル レベルで定義された変数と関数の名前です。ローカル スコープは 定義関数 内にあります。

スコープに関しては、次の 2 つの点を理解する必要があります: a. ローカルに定義された変数はグローバルにアクセスできませんが、グローバルに定義された変数は変更できません (もちろん、変更する方法はあります)。

次の例を見てみましょう:


x = 1
def funx():
  x = 10
  print(x) # 打印出10

funx()
print(x) # 打印出1

定義された変数 x がローカルに存在しない場合、関数は x を内側から外側に検索します。それが見つからない場合は、エラーが報告されます。


x = 1
def funx():
  print(x) # 打印出1

funx()
print(x) # 打印出1

x = 1
def funx():
  def func1():
    print(x) # 打印出1
  func1()

funx()
print(x) # 打印出1

スコープの問題に関しては、次の 2 つの点だけを覚えておく必要があります。グローバル変数はファイル内のどこでも参照できますが、変更は必要な変数がローカルに見つからない場合にのみグローバルに実行できます。見つからない場合は、外部で検索され、エラーが報告されます。

2. 高度な関数

関数名は実際にはメモリ空間のアドレスを指していることがわかっているので、この機能を使用できます。

a 関数名を値として使用できます


def delete(ps):
  import os
  filename = ps[-1]
  delelemetns = ps[1]
  with open(filename, encoding='utf-8') as f_read,\
    open('tmp.txt', 'w', encoding='utf-8') as f_write:
    for line in iter(f_read.readline, ''):
      if line != '\n': # 处理非空行
        if delelemetns in line:
          line = line.replace(delelemetns,'')
        f_write.write(line)
  os.remove(filename)
  os.rename('tmp.txt',filename)

def add(ps):
  filename = ps[-1]
  addelemetns = ps[1]
  with open(filename, 'a', encoding='utf-8') as fp:
    fp.write("\n", addelemetns)

def modify(ps):
  import os
  filename = ps[-1]
  modify_elemetns = ps[1]
  with open(filename, encoding='utf-8') as f_read, \
      open('tmp.txt', 'w', encoding='utf-8') as f_write:
    for line in iter(f_read.readline, ''):
      if line != '\n': # 处理非空行
        if modify_elemetns in line:
          line = line.replace(modify_elemetns, '')
        f_write.write(line)
  os.remove(filename)
  os.rename('tmp.txt', filename)


def search(cmd):
  filename = cmd[-1]
  pattern = cmd[1]
  with open(filename, 'r', encoding="utf-8") as f:
    for line in f:
      if pattern in line:
        print(line, end="")
    else:
      print("没有找到")

dic_func ={'delete': delete, 'add': add, 'modify': modify, 'search': search}

while True:
  inp = input("请输入您要进行的操作:").strip()
  if not inp:
    continue
  cmd_1 = inp.split()
  cmd = cmd_1[0]
  if cmd in dic_func:
    dic_func[cmd](cmd_1)
  else:
    print("Error")

b 関数名を戻り値として使用できます


def outer():
  def inner():
    pass
  return inner

s = outer()
print(s)

######输出结果为#######
<function outer.<locals>.inner at 0x000000D22D8AB8C8>

c 関数名をパラメータとして使用できます


def index():
  print("index func")

def outer(index):
  s = index
  s()
  
outer(index)

######输出结果#########

index func

以上の 2 つの条件が満たされています。 どれでも高レベル関数と呼ぶことができます。

3. クロージャ関数

クロージャ関数は、次の 2 つの条件を満たす必要があります。 1. 関数内で定義された関数 2.グローバル スコープではなく外部スコープが含まれます。 以下は、いくつかの例を通じてクロージャ関数への参照です:

例 1: 以下は関数内で関数を定義するだけですが、これはクロージャ関数ではありません

def outer():
  def inner():
    print("inner func excuted")
  inner() # 调用执行inner()函数
  print("outer func excuted")
outer() # 调用执行outer函数

####输出结果为##########
inner func excuted
outer func excuted

例2: 以下は関数 A 関数内で定義されており、外部変数も参照しています。 2 番目の項目が満たされていないことがわかったはずです。はい、ここでの変数 x はグローバル変数であり、外部に作用する変数ではありません。ドメイン。次の例を見てみましょう:

x = 1
def outer():
  def inner():
    print("x=%s" %x) # 引用了一个非inner函数内部的变量
    print("inner func excuted")
  inner() # 执行inner函数
  print("outer func excuted")

outer()
#####输出结果########
x=1
inner func excuted
outer func excuted

明らかに、上記の例はクロージャー関数の条件を満たしています。さて、クロージャ関数としては、上記の 2 つの条件を満たさなければならず、どちらも削除できないことを知っておく必要があります。ただし、通常の状況では、クロージャ関数に値を返します。その理由についてはここでは説明しません。次のコンテンツで、この戻り値の使用方法を説明します。

def outer():
  x = 1
  def inner():
    print("x=%s" %x)
    print("inner func excuted")
  inner()
  print("outer func excuted")

outer()

#####输出结果#########
x=1
inner func excuted
outer func excuted

次に、クロージャを定義します。関数。これは、関数とそれに関連する参照環境で構成されるエンティティです。深い制約を実装する場合、参照環境を明示的に表すものを作成し、それを関連するサブルーチンとバンドルして、バンドルがクロージャになるようにする必要があります。上記の例では、クロージャ関数が実際にクロージャ関数と呼ばれるには、それ自体の関数と外部変数が含まれている必要があることがわかります。外部変数がバインドされていない場合、関数はクロージャー関数とみなされません。

それでは、クロージャー関数が持つ外部参照変数の数をどうやって知るのでしょうか? 以下のコードを見てください。


def outer():
  x = 1
  def inner():
    print("x=%s" %x)
    print("inner func excuted")
  print("outer func excuted")
  return inner # 返回内部函数名
  
outer()

結果は、内部で 2 つの外部ローカル変数が参照されていることを示しています。非ローカル変数が参照されている場合、ここでの出力は None になります。

クロージャ関数の特徴:

1. 独自のスコープが付いています 2. 遅延計算


それでは、クロージャ関数が何をするのかは明確にわかっています。 , クロージャー関数を定義する場合は、外部環境にバインドする必要があります。この全体をクロージャー関数とみなすことができ、このバインディング機能を使用して特定の特殊な関数を完成させることができます。

例3:受信したURLに従ってページのソースコードをダウンロードする

def outer():
  x = 1
  y = 2

  def inner():
    print("x= %s" %x)
    print("y= %s" %y)

  print(inner.closure)
  return inner

outer()

######输出结果#######
(<cell at 0x000000DF9EA965B8: int object at 0x000000006FC2B440>, <cell at 0x000000DF9EA965E8: int object at 0x000000006FC2B460>)

これはクロージャ関数の条件を満たしていないという人もいるかもしれませんが、非グローバル外部変数を参照していません。実際、前に述べたように、関数内の変数が関数に属している限り、これは当てはまりません。次に、index(url) に移動します。この URL も関数内に属しますが、手順が 1 つ省略されているため、上記の関数はクロージャ関数でもあります。

4. デコレータ


上記の基礎により、デコレータを簡単に理解できます。

デコレータ: 外部関数は装飾された関数の名前を渡し、内部関数は装飾された関数の名前を返します。

特徴: 1. 装飾された関数の呼び出しメソッドを変更しません 2. 装飾された関数のソースコードを変更しません a. パラメーターのないデコレーター

以下の例では、コードの実行時間を計算する必要があります。


import time, random

def index():
  time.sleep(random.randrange(1, 5))
  print("welcome to index page")

  根据装饰器的特点,我们不能对index()进行任何修改,而且调用方式也不能变。这时候,我们就可以使用装饰器来完成如上功能.


import time, random

def outer(func): # 将index的地址传递给func
  def inner():
    start_time = time.time()
    func()  # fun = index 即func保存了外部index函数的地址
    end_time = time.time()
    print("运行时间为%s"%(end_time - start_time))
  return inner # 返回inner的地址

def index():
  time.sleep(random.randrange(1, 5))
  print("welcome to index page")

index = outer(index) # 这里返回的是inner的地址,并重新赋值给index

index()

  但是,有些情况,被装饰的函数需要传递参数进去,有些函数又不需要参数,那么如何来处理这种变参数函数呢?下面来看看有参数装饰器的使用情况.

  b.有参装饰器


def outer(func): # 将index的地址传递给func
  def inner(*args, **kwargs):
    start_time = time.time()
    func(*args, **kwargs)  # fun = index 即func保存了外部index函数的地址
    end_time = time.time()
    print("运行时间为%s"%(end_time - start_time))
  return inner # 返回inner的地址

  下面来说说一些其他情况的实例。

   如果被装饰的函数有返回值


def timmer(func):
  def wrapper(*args,**kwargs):
    start_time = time.time()
    res=func(*args,**kwargs) #res来接收home函数的返回值
    stop_time=time.time()
    print(&#39;run time is %s&#39; %(stop_time-start_time))
    return res 
  return wrapper

def home(name):
  time.sleep(random.randrange(1,3))
  print(&#39;welecome to %s HOME page&#39; %name)
  return 123123123123123123123123123123123123123123

  这里补充一点,加入我们要执行被装饰后的函数,那么应该是如下调用方式:

  home = timmer(home)  # 等式右边返回的是wrapper的内存地址,再将其赋值给home,这里的home不在是原来的的那个函数,而是被装饰以后的函数了。像home = timmer(home)这样的写法,python给我们提供了一个便捷的方式------语法糖@.以后我们再要在被装饰的函数之前写上@timmer,它的效果就和home = timmer(home)是一样的。

  如果一个函数被多个装饰器装饰,那么执行顺序是怎样的。


import time
import random

def timmer(func):
  def wrapper():
    start_time = time.time()
    func()
    stop_time=time.time()
    print(&#39;run time is %s&#39; %(stop_time-start_time))
  return wrapper
def auth(func):
  def deco():
    name=input(&#39;name: &#39;)
    password=input(&#39;password: &#39;)
    if name == &#39;egon&#39; and password == &#39;123&#39;:
      print(&#39;login successful&#39;)
      func() #wrapper()
    else:
      print(&#39;login err&#39;)
  return deco

@auth  # index = auth(timmer(index))         
@timmer # index = timmer(index)
def index():
 
  time.sleep(3)
  print(&#39;welecome to index page&#39;)

index()

  实验结果表明,多个装饰器装饰一个函数,其执行顺序是从下往上。

  关于装饰器,还有一些高级用法,有兴趣的可以自己研究研究。

以上がPython デコレータを深く理解できるサンプル コードの共有の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。