「python」-6:関数とモジュールの使用

8982 ワード

すべてのダンスをしたことがない日、すべて生命に対する裏切りです!
関数とモジュールの使用
この章の内容を説明する前に、数学の問題を研究して、次の方程式がどれだけの正の整数解を持っているかを話してください.
x1 + x2 + x3 + x4 = 8
実際、上記の問題は、8つのリンゴを4つのグループに分けて、少なくとも1つのリンゴにどれだけの案があるかということに等しい.この点を考えると答えが出てくる.
これは数学の中の関数で、実はすべてのプログラミング言語に関数があって、もちろん数学の意味の上の関数と違います.

"""
  M N  C(M,N)
"""

m = int(input('m = '))
n = int(input('n = '))
fm = 1
for num in range(1, m + 1):
    fm *= num
fn = 1
for num in range(1, n + 1):
    fn *= num
fmn = 1
for num in range(1, m - n + 1):
    fmn *= num
print(fm // fn // fmn)

関数の役割
皆さんは気づいているかどうか分かりませんが、上のコードでは、3回も階乗を求めましたが、このようなコードは実際には重複コードです.プログラミングの巨匠Martin Fowlerさんはかつて「コードには多くの悪い味があり、繰り返しは最悪のものだ」と話していました.高品質のコードを書くには、まずコードを繰り返す問題を解決しなければならない.上記のコードでは、乗算機能を「関数」と呼ばれる機能モジュールにカプセル化することができます.乗算を計算する必要がある場所では、「呼び出し」という「関数」だけでいいです.
関数の定義
Pythonではdefキーワードを使用して関数を定義できます.変数と同様に各関数にも大きな名前があり、ネーミングルールは変数のネーミングルールと一致しています.関数名の後ろの括弧には、関数に渡されるパラメータを置くことができます.これは数学的な関数とよく似ています.プログラム内の関数のパラメータは、数学的に言った関数の引数に相当します.関数の実行が完了したら、returnキーワードで値を返すことができます.これは、数学的に言った関数の引数に相当します.
関数の定義方法を理解した後、上記のコードを再構築することができます.再構築とは、コードの実行結果に影響を与えない前提でコードの構造を調整することです.再構築後のコードは以下のようになります.
def factorial(num):
    """
       
    
    :param num:     
    :return: num   
    """
    result = 1
    for n in range(1, num + 1):
        result *= n
    return result


m = int(input('m = '))
n = int(input('n = '))
#                                  
print(factorial(m) // factorial(n) // factorial(m - n))
:Pythonのmathモジュールにはすでにfactorial関数があり、実際に計算する階乗は自分で定義することなく、この既存の関数を直接使用することができます.次の例のいくつかの関数は実はPythonにも内蔵されています.ここでは関数の定義と使用を説明するためにもう一度実現しましたが、実際の開発ではこのような低レベルの重複性の仕事はお勧めしません.
関数のパラメータ
関数はほとんどのプログラミング言語でサポートされているコードの「構築ブロック」ですが、Pythonの関数は他の言語の関数とは異なる点が多く、その顕著な違いの一つはPythonの関数パラメータの処理です.Pythonでは、関数のパラメータにデフォルト値があり、可変パラメータの使用もサポートされているので、Pythonは他の言語のように関数のリロードをサポートする必要はありません.1つの関数を定義するときに、いくつかの異なる使用方法を使用することができます.以下は2つの小さな例です.
from random import randint


def roll_dice(n=2):
    """
       
    
    :param n:      
    :return: n       
    """
    total = 0
    for _ in range(n):
        total += randint(1, 6)
    return total


def add(a=0, b=0, c=0):
    return a + b + c


#                     
print(roll_dice())
#      
print(roll_dice(3))
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
#                    
print(add(c=50, a=100, b=200))


上記の2つの関数のパラメータにデフォルト値を設定します.これは、関数を呼び出すときに対応するパラメータの値が入力されない場合にこのパラメータのデフォルト値が使用されることを意味します.したがって、上記のコードではadd関数を様々な方法で呼び出すことができます.これは、他の多くの言語での関数の再ロードの効果と一致します.
実は上のadd関数はもっと良い実現案があります.私たちは0つ以上のパラメータを加算する可能性がありますが、具体的にどのくらいのパラメータが呼び出し者によって決定されるか、私たちは関数の設計者としてこの点について何も知らないので、パラメータの個数を確定しないときは、可変パラメータを使用することができます.コードは以下に示します.
#        *  args       
#     add       0      
def add(*args):
    total = 0
    for val in args:
        total += val
    return total


print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 3, 5, 7, 9))

モジュールで関数を管理する
いずれのプログラミング言語においても、変数、関数のような識別子に名前を付けるのは、 という気まずい状況に遭遇するため、頭を悩ませる問題です.最も簡単なシーンは、同じ.py で2つの同名関数が定義されていることです.Pythonには関数のリロードの概念がないため、後の定義は前の定義を上書きします.つまり、2つの関数の同名関数が実際に1つしか存在しないことを意味します.
def foo():
    print('hello, world!')


def foo():
    print('goodbye, world!')


#            ?
foo()

もちろん、上記の状況は簡単に回避できますが、プロジェクトが複数の人が協力してチーム開発を行う場合、チームの中にfooという関数を定義しているプログラマーが複数いる可能性があります.では、この名前の衝突をどのように解決しますか?答えは実は簡単で、Pythonの中の各ファイルは1つのモジュール(module)を代表して、私达は异なるモジュールの中で同名の関数があって、関数を使う时私达はimportキーワードを通じて指定したモジュールをインポートして、いったいどのモジュールの中のfoo関数を使うのかを区别することができて、コードは以下のように示します.module1.py
def foo():
    print('hello, world!')
module2.py
def foo():
    print('goodbye, world!')
test.py
from module1 import foo

#   hello, world!
foo()

from module2 import foo

#   goodbye, world!
foo()

どのfoo関数を使用するかは、以下に示すように区別することもできます.test.py
import module1 as m1
import module2 as m2

m1.foo()
m2.foo()

しかし、コードを次のように書くと、プログラムで呼び出されたのは最後にインポートされたfooであり、後にインポートされたfooが前にインポートされたfooを上書きしているためである.test.py
from module1 import foo
from module2 import foo

#   goodbye, world!
foo()
test.py
from module2 import foo
from module1 import foo

#   hello, world!
foo()

なお、我々がインポートしたモジュールに定義関数のほかに実行可能なコードがある場合、Python解釈器はこのモジュールをインポートする際にこれらのコードを実行するが、実際にはそうは望んでいない可能性があるため、モジュールに実行コードを記述した場合、これらの実行コードを以下のような条件に入れることが望ましい.これにより、モジュールを直接実行しない限り、ifの条件下でのこれらのコードは実行されません.なぜなら、直接実行されるモジュールの名前だけが“__main__”であるからです.module3.py
def foo():
    pass


def bar():
    pass


# __name__ Python                 
#    Python               __main__
if __name__ == '__main__':
    print('call foo()')
    foo()
    print('call bar()')
    bar()
test.py
import module3

#   module3         if                 module3   __main__

練習する
練習1:最大公約数と最小公倍数を求める関数を計算する.
def gcd(x, y):
    (x, y) = (y, x) if x > y else (x, y)
    for factor in range(x, 0, -1):
        if x % factor == 0 and y % factor == 0:
            return factor


def lcm(x, y):
    return x * y // gcd(x, y)
    

練習2:1つの数が文数であるか否かを判断する関数を実現する.
def is_palindrome(num):
    temp = num
    total = 0
    while temp > 0:
        total = total * 10 + temp % 10
        temp //= 10
    return total == num

練習3:1つの数が素数であるか否かを判断する関数を実現する.
def is_prime(num):
    for factor in range(2, num):
        if num % factor == 0:
            return False
    return True if num != 1 else False

練習4:入力した正の整数が回文素数であるかどうかを判断するプログラムを書く.
if __name__ == '__main__':
    num = int(input('      : '))
    if is_palindrome(num) and is_prime(num):
        print('%d     ' % num)

上記のプログラムから,コードに繰り返される相対的に独立した機能を関数に抽出すると,これらの関数を組み合わせてより複雑な問題を解決することができ,これも関数を定義して使用する上で非常に重要な原因であることがわかる.
最後に,Pythonにおける変数の役割ドメインに関する問題について議論する.
def foo():
    b = 'hello'

    def bar():  # Python             
        c = True
        print(a)
        print(b)
        print(c)

    bar()
    # print(c)  # NameError: name 'c' is not defined


if __name__ == '__main__':
    a = 100
    # print(b)  # NameError: name 'b' is not defined
    foo()


上記のコードは順調に実行され、100および“hello”が印刷されるが、bar の内部にはaおよびbの2つの変数が定義されていないことに気づき、aおよびbはどこから来たのか.上記のコードのifブランチでは、いずれの関数にも定義されていないため、aに属する (global variable)の変数 を定義した.上記のfoo では、関数に定義されたbであり、局所的な役割ドメインに属し、 (local variable)の外部ではアクセスできない変数foo を定義した.しかし、foo 内部のbar では、変数b に属し、bar でアクセスできます.bar の変数c に属し、bar関数以外ではアクセスできません.実際、Pythonは変数を検索する際に“ ”、“ ”、“ ” “ ”の順序で検索します.上位3つは上記のコードで見ましたが、“ ”とはPythonに内蔵されている隠れた識別子min、lenなどが内蔵されている役割ドメインです).
次のコードを見てみると、関数呼び出しでグローバル変数aの値を変更したいのですが、実際には次のコードはできません.
def foo():
    a = 200
    print(a)  # 200


if __name__ == '__main__':
    a = 100
    foo()
    print(a)  # 100
foo を呼び出すと、aの値は依然として100であることが分かった.これは、 fooa = 200と書くと、aという名前のローカル変数が再定義され、グローバル役割ドメインのaと同じ変数ではないからである.ローカル役割ドメインには独自の変数aがあるため、foo関数はグローバル役割ドメインのaを検索しない.foo関数でグローバル役割ドメインのaを変更したい場合は、コードを以下に示します.globalキーワードを使用してfoo関数の変数a から来たことを示すことができ、グローバル役割ドメインにaがない場合、次の行のコードは変数aを定義し、グローバル役割ドメインに配置する.同様に、関数内部の関数がネストされた役割ドメインの変数を変更することを望む場合は、nonlocalキーワードを使用して変数がネストされた役割ドメインから来ていることを示すことができます.
実際の開発では、グローバル変数の役割ドメインと影響が広すぎるため、予想外の変更と使用が発生する可能性があります.それ以外に、グローバル変数はローカル変数よりも長いライフサイクルを有し、オブジェクトが消費するメモリが長時間ゴミに回収されない可能性があります.実際,グローバル変数の使用を減らすことは,コード間の結合度を低減する重要な措置であり,ディミット法則の実践でもある.グローバル変数の使用を減らすことは、できるだけ変数の役割ドメインを関数の内部に配置することを意味しますが、ローカル変数のライフサイクルを延長し、関数呼び出しが終了してもアクセスできるようにするには、閉パッケージを使用する必要があります.これについては、後述します.
説明:多くの人が「閉パッケージ」と「匿名関数」を混同することが多いが、実際には異なる概念であり、事前にこの概念を理解したい場合は、ウィキペディアやこの概念についての議論をお勧めする.
そんなことを言って、実は結論は簡単で、今からPythonコードを次のフォーマットで書くことができます.このわずかな改善は、関数と役割ドメインを理解した上で大きな一歩です.
def main():
    # Todo: Add your code here
    pass


if __name__ == '__main__':
    main()

exercise python 100 days from https://github.com/jackfrued/Python-100-Days