効率的な Python コードの書き方

巴扎黑
リリース: 2017-09-09 11:38:38
オリジナル
1375 人が閲覧しました

この記事は主に、効率的でエレガントな Python コードの書き方を紹介し、共有します。必要な方は参考にしてください。

この記事の一部は書籍「Effective Python」および「Python3 Cookbook」から抜粋しています。変更および追加された内容は、作成者自身の理解とアプリケーションにおけるベスト プラクティスです。

全文は約 9956 ワードで、読むのに 24 分かかる場合があります。

Python のリスト切断

list[start:end:step]list[start:end:step]

如果从列表开头开始切割,那么忽略 start 位的 0,例如list[:4]

如果一直切到列表尾部,则忽略 end 位的 0,例如list[3:]

切割列表时,即便 start 或者 end 索引跨界也不会有问题

列表切片不会改变原列表。索引都留空时,会生成一份原列表的拷贝

列表推导式

使用列表推导式来取代mapfilter

不要使用含有两个以上表达式的列表推导式

数据多时,列表推导式可能会消耗大量内存,此时建议使用生成器表达式

迭代

需要获取 index 时使用enumerate

enumerate可以接受第二个参数,作为迭代时加在index上的数值

zip同时遍历两个迭代器

zip遍历时返回一个元组

关于forwhile循环后的else

循环正常结束之后会调用else内的代码

循环里通过break跳出循环,则不会执行else

要遍历的序列为空时,立即执行else

反向迭代

对于普通的序列(列表),我们可以通过内置的reversed()函数进行反向迭代:

除此以外,还可以通过实现类里的__reversed__方法,将类进行反向迭代:

try/except/else/finally

如果try内没有发生异常,则调用else内的代码

else会在finally之前运行

最终一定会执行finally,可以在其中进行清理工作

函数使用装饰器

装饰器用于在不改变原函数代码的情况下修改已存在的函数。常见场景是增加一句调试,或者为已有的函数增加log监控

举个栗子:

除此以外,还可以编写接收参数的装饰器,其实就是在原本的装饰器上的外层又嵌套了一个函数:

但是像上面那样使用装饰器的话有一个问题:

也就是说原函数已经被装饰器里的new_fun函数替代掉了。调用经过装饰的函数,相当于调用一个新函数。查看原函数的参数、注释、甚至函数名的时候,只能看到装饰器的相关信息。为了解决这个问题,我们可以使用

Python 自带的functools.wraps方法。

functools.wraps

リストの先頭から切断を開始する場合は、開始ビットの 0 を無視してください、list [:4] など

リストの最後までカットする場合は、list[3:] など、最後のビットの 0 を無視します。 code>

🎜リストをカットする際、開始インデックスや終了インデックスが境界を越えても問題ありません🎜🎜リストのスライスでは元のリストは変わりません。インデックスをすべて空白のままにすると、元のリストのコピーが生成されます🎜🎜🎜🎜リスト内包表記🎜🎜リスト内包表記を使用して mapfilter を置き換えます🎜🎜🎜🎜2 つ以上の式を含むリスト内包表記は使用しないでください🎜🎜🎜🎜データが多い場合、リスト内包表記は多くのメモリを消費する可能性があります。この場合は、リスト内包表記を使用することをお勧めしますジェネレータ式を使用するには🎜🎜🎜🎜反復🎜🎜enumerate🎜🎜enumerate は、反復中に index に追加される値として 2 番目のパラメーターを受け入れることができます🎜🎜🎜🎜zip を使用して、同時に🎜🎜🎜🎜zipトラバース時にタプルを返します🎜🎜🎜🎜forwhile ループ後の else ブロック 🎜🎜 ループが正常に終了した後、else のコード はループを通じて 🎜🎜 呼び出されます。break ループから抜け出すと、else は実行されません。🎜🎜トラバースされるシーケンスが空の場合、else🎜🎜🎜🎜逆反復🎜🎜 通常のシーケンス (リスト) の場合、組み込みの reversed() 関数を使用して逆反復を実行できます: 🎜🎜🎜🎜さらに、 __reversed__を実装することでクラスを逆反復することもできます。 > クラス内のメソッド: 🎜🎜🎜🎜 try/excel/else/finally🎜🎜if try コード内で例外が発生しない場合>、<code>else 内のコードが呼び出されます🎜🎜else code> は <code>finally の前に実行されます🎜🎜 最終的には finally が実行され、そこでクリーンアップ作業を実行できます🎜🎜 関数はデコレータを使用します🎜🎜 デコレータは、既存の関数を変更せずに既存の関数を変更するために使用されますオリジナルの機能コード。一般的なシナリオは、デバッグ文を追加するか、既存の関数の log モニタリングを追加することです🎜🎜例: 🎜🎜🎜🎜さらに、パラメーターを受け取るデコレーターを記述することもできます。実際、関数は元のデコレーターの外側の層にネストされています。 :🎜🎜🎜🎜しかし、上記のようなデコレータを使用すると質問があります: 🎜🎜🎜🎜つまり、元の関数は、デコレーターの new_fun 関数に置き換えられました。装飾された関数の呼び出しは、新しい関数の呼び出しと同じです。元の関数のパラメータ、コメント、さらには関数名を表示すると、デコレータに関連する情報のみが表示されます。この問題を解決するには、Python に付属の functools.wraps メソッドを使用します。 🎜🎜functools.wraps は非常にハックなメソッドで、デコレーター内で返される関数のデコレーターとして使用できます。つまり、デコレータの中のデコレータであり、元の関数をパラメータとして取り、後で修飾された元の関数の情報を参照するときに、その情報がそのまま残るように、元の関数のさまざまな情報を保持する機能があります。本来の機能と同じです。 🎜🎜🎜🎜

さらに、デコレータが複数のことを行う場合もあります。その場合、イベントは追加機能として分離する必要があります。ただし、デコレータにのみ関連する可能性があるため、現時点ではデコレータ クラスを構築できます。原理は非常に簡単で、主なことは、クラスを関数のように呼び出せるようにクラス内に __call__ メソッドを記述することです。 __call__方法,使类能够像函数一样的调用。

使用生成器

考虑使用生成器来改写直接返回列表的函数

用这种方法有几个小问题:

每次获取到符合条件的结果,都要调用append方法。但实际上我们的关注点根本不在这个方法,它只是我们达成目的的手段,实际上只需要index就好了

返回的result可以继续优化

数据都存在result里面,如果数据量很大的话,会比较占用内存

因此,使用生成器generator会更好。生成器是使用yield表达式的函数,调用生成器时,它不会真的执行,而是返回一个迭代器,每次在迭代器上调用内置的next函数时,迭代器会把生成器推进到下一个yield表达式:

获取到一个生成器以后,可以正常的遍历它:

如果你还是需要一个列表,那么可以将函数的调用结果作为参数,再调用list方法

可迭代对象

需要注意的是,普通的迭代器只能迭代一轮,一轮之后重复调用是无效的。解决这种问题的方法是,你可以定义一个可迭代的容器类

这样的话,将类的实例迭代重复多少次都没问题:

但要注意的是,仅仅是实现__iter__方法的迭代器,只能通过for循环来迭代;想要通过next方法迭代的话则需要使用iter方法:

使用位置参数

有时候,方法接收的参数数目可能不一定,比如定义一个求和的方法,至少要接收两个参数:

对于这种接收参数数目不一定,而且不在乎参数传入顺序的函数,则应该利用位置参数*args

但要注意的是,不定长度的参数args在传递给函数时,需要先转换成元组tuple。这意味着,如果你将一个生成器作为参数带入到函数中,生成器将会先遍历一遍,转换为元组。这可能会消耗大量内存:

使用关键字参数

关键字参数可提高代码可读性

可以通过关键字参数给函数提供默认值

便于扩充函数参数

定义只能使用关键字参数的函数

普通的方式,在调用时不会强制要求使用关键字参数

使用 Python3 中强制关键字参数的方式

使用 Python2 中强制关键字参数的方式

关于参数的默认值

算是老生常谈了:函数的默认值只会在程序加载模块并读取到该函数的定义时设置一次

也就是说,如果给某参数赋予动态的值(

比如[]或者{}

🎜🎜ジェネレーターを使用する🎜
🎜検討するジェネレーターを使用して、リストを直接返す関数を書き換えます🎜
🎜🎜🎜このメソッドにはいくつかの小さな問題があります: 🎜🎜条件を満たす結果を得るたびに、append メソッドを呼び出す必要があります。しかし実際には、このメソッドは目的を達成するための手段にすぎません。実際に必要なのは、返された result だけです。 > 引き続き最適化できます 🎜🎜 データは result に保存されます。データ量が大きい場合は、より多くのメモリを占有します。そのため、ジェネレーター を使用することをお勧めします。ジェネレーター。ジェネレーターは、yield 式を使用する関数です。ジェネレーターが呼び出されるとき、実際には実行されませんが、組み込みの next 関数を使用すると、イテレータはジェネレータを次の <code>yield 式に進めます: 🎜🎜🎜🎜ジェネレーターを取得した後は、通常通り走査できます: 🎜🎜🎜🎜それでもリストが必要な場合は、関数呼び出しの結果をパラメータとして使用して、list メソッドを呼び出すことができます 🎜 🎜🎜🎜反復可能なオブジェクト🎜🎜注意してください。通常の反復子は 1 ラウンドしか反復できず、1 ラウンド後の反復呼び出しは無効です。この問題を解決する方法は、反復可能なコンテナ クラスを定義することです: 🎜🎜🎜🎜この場合、クラスのインスタンスを何回繰り返しても問題ありません: 🎜🎜🎜🎜ただし、これは __iter__ メソッドを実装する単なるイテレータであることに注意してください。 for ループを介してのみ反復処理を行うことができます。next メソッドを介して反復処理を行う場合は、iter メソッドを使用する必要があります。 🎜🎜🎜位置パラメータを使用する🎜🎜場合によっては、メソッドによって受け取られるパラメータは、合計メソッドの定義など、不確かな場合があります。少なくとも 2 つのパラメータを受け取る必要があります: 🎜🎜🎜🎜 特定の数のパラメータを持たず、パラメータが渡される順序を気にしないこの種の関数の場合、位置パラメータ * args を使用する必要があります: 🎜🎜🎜🎜ただし、可変長パラメータ args を最初にタプル tuple に変換する必要があることに注意してください。これは、ジェネレーターをパラメーターとして関数に渡すと、ジェネレーターが最初に走査され、タプルに変換されることを意味します。これは大量のメモリを消費する可能性があります: 🎜🎜🎜🎜使用キーワード ワード パラメーター🎜🎜キーワード パラメーターにより、コードの可読性が向上します🎜🎜キーワード パラメーターを通じて関数にデフォルト値を提供できます🎜🎜関数パラメーターの拡張が簡単です🎜🎜キーワード パラメーターのみを使用できる関数を定義する🎜 🎜通常の方法では、呼び出し時にキーワードパラメータは必要ありません🎜🎜 🎜🎜Python3 でキーワード引数を強制する方法を使用します🎜🎜🎜🎜Python2 でキーワード パラメータを強制する方法を使用します🎜🎜🎜🎜パラメータのデフォルト値について🎜🎜は決まり文句です:関数のデフォルト値は、プログラムがモジュールをロードし、これは、 関数の定義時に一度設定されます🎜🎜つまり、動的な値がパラメータ (🎜🎜[]{})、後で関数を呼び出すときに他のパラメーターがパラメーターに割り当てられている場合、この関数が将来再び呼び出されるとき、以前に定義されたデフォルト値が変更され、最後の呼び出しで割り当てられた値になります。 🎜<p><img alt="" src="https://img.php.cn/upload/article/000/000/007/c86d9b3433dbdd9243a7ea7c863f9498-28.png"></p> <p> したがって、デフォルトパラメータとして <code>None を使用し、関数内で判断して値を割り当てることをお勧めします: None作为默认参数,在函数内进行判断之后赋值:

__slots__

默认情况下,Python 用一个字典来保存一个对象的实例属性。这使得我们可以在运行的时候动态的给类的实例添加新的属性:

然而这个字典浪费了多余的空间 -— 很多时候我们不会创建那么多的属性。因此通过__slots__可以告诉 Python

不要使用字典而是固定集合来分配空间。

__call__

通过定义类中的__call__方法,可以使该类的实例能够像普通函数一样调用。

通过这种方式实现的好处是,可以通过类的属性来保存状态,而不必创建一个闭包或者全局变量。

@classmethod & @staticmethod

@classmethod@staticmethod很像,但他们的使用场景并不一样。

类内部普通的方法,都是以self作为第一个参数,代表着通过实例调用时,将实例的作用域传入方法内;

@classmethodcls作为第一个参数,代表将类本身的作用域传入。无论通过类来调用,还是通过类的实例调用,默认传入的第一个参数都将是类本身

@staticmethod不需要传入默认参数,类似于一个普通的函数

来通过实例了解它们的使用场景:

假设我们需要创建一个名为Date的类,用于储存 年/月/日 三个数据

上述代码创建了Date类,该类会在初始化时设置day/month/year属性,并且通过property设置了一个getter,可以在实例化之后,通过time获取存储的时间:

但如果我们想改变属性传入的方式呢?毕竟,在初始化时就要传入年/月/日三个属性还是很烦人的。能否找到一个方法,在不改变现有接口和方法的情况下,可以通过传入2016-11-09这样的字符串来创建一个Date实例?

你可能会想到这样的方法:

但不够好:

在类外额外多写了一个方法,每次还得格式化以后获取参数

这个方法也只跟Date类有关

没有解决传入参数过多的问题

此时就可以利用@classmethod,在类的内部新建一个格式化字符串,并返回类的实例的方法:

这样,我们就可以通过Date类来调用from_string方法创建实例,并且不侵略、修改旧的实例化方式:

好处:

@classmethod内,可以通过cls参数,获取到跟外部调用类时一样的便利

可以在其中进一步封装该方法,提高复用性

更加符合面向对象的编程方式

@staticmethod,因为其本身类似于普通的函数,所以可以把和这个类相关的 helper

方法作为@staticmethod,放在类里,然后直接通过类来调用这个方法。

将与日期相关的辅助类函数作为@staticmethod方法放在Date类内后,可以通过类来调用这些方法:

创建上下文管理器

上下文管理器,通俗的介绍就是:在代码块执行前,先进行准备工作;在代码块执行完成后,做收尾的处理工作。with

🎜🎜クラス __slots__🎜🎜 デフォルトでは、Python は辞書を使用してインスタンス属性を保存しますオブジェクト。これにより、実行時に新しい属性をクラス インスタンスに動的に追加できます: 🎜🎜🎜🎜ただし、この辞書は余分なスペースを無駄にします。多くの場合、それほど多くの属性は作成されません。したがって、__slots__ は、スペースを割り当てるために辞書ではなく固定コレクションを使用するように Python🎜🎜 に指示できます。 🎜🎜🎜🎜__call__🎜 🎜クラス内で __call__ メソッドを定義すると、通常の関数と同様にクラスのインスタンスを呼び出すことができます。 🎜🎜🎜🎜 この方法で実装する利点は、クロージャやグローバル変数を作成しなくても、クラス プロパティを通じて状態を保存できます。 🎜🎜@classmethod@staticmethod🎜🎜@classmethod@staticmethod は非常に似ていますが、使用シナリオは異なります同じではありません。 🎜🎜クラス内の通常のメソッドはすべて、最初のパラメータとして self を使用します。これは、インスタンスを通じて呼び出される場合、インスタンスのスコープがメソッドに渡されることを意味します。🎜🎜@classmethod は最初のパラメータとして <code>cls を受け取ります。これは、クラス自体のスコープを渡すことを意味します。クラスを介して呼び出されるか、クラスのインスタンスを介して呼び出されるかに関係なく、デフォルトで渡される最初のパラメータはクラス自体になります🎜🎜@staticmethod は、次のようにデフォルトのパラメータを渡す必要はありません。普通の関数です🎜🎜例を通して使用シナリオを理解しましょう: 🎜🎜年/月/日の 3 つのデータを保存するために Date という名前のクラスを作成する必要があるとします🎜🎜🎜🎜上記のコードは Date クラスを作成します。 day/month/year 属性は初期化中に設定され、getterproperty を通じて設定され、インスタンス化後に に渡すことができます。 time保存された時刻を取得します: 🎜🎜🎜 🎜しかし、プロパティの受け渡し方法を変更したい場合はどうすればよいでしょうか?結局のところ、初期化中に年/月/日の 3 つの属性を渡さなければならないのは面倒です。既存のインターフェースとメソッドを変更せずに、2016-11-09 のような文字列を渡すことで Date インスタンスを作成する方法は見つかりますか? 🎜🎜次の方法が考えられます: 🎜🎜🎜 🎜しかし、それだけでは十分ではありません: 🎜🎜クラスの外に追加のメソッドを作成すると、パラメーターを取得するために毎回フォーマットする必要があります🎜🎜このメソッドは Date クラスにのみ関連します🎜🎜受信パラメータを解決できません問題が多すぎます🎜🎜現時点では、@classmethod を使用してクラス内に新しいフォーマット文字列を作成し、クラスのインスタンスのメソッドを返すことができます: 🎜🎜🎜🎜このようにして、Date クラス >from_string メソッドは、古いインスタンス化メソッドに侵入したり変更したりせずにインスタンスを作成します: 🎜🎜🎜🎜利点: 🎜🎜@classmethod では、cls パラメータを使用して同じものを取得できます外部クラスを呼び出すときと同様の利便性🎜🎜再利用性を向上させるためにメソッドをさらにカプセル化することができます🎜🎜オブジェクト指向プログラミングに沿ったものです🎜🎜@staticmethod は次のものに似ているためです🎜🎜通常の関数をこのクラス関連ヘルパーと組み合わせることができます🎜🎜 メソッドは @staticmethod としてクラスに配置され、クラスを通じて直接メソッドが呼び出されます。 🎜🎜🎜🎜 日付関連の補助クラス関数を After として使用する@staticmethod メソッドは Date クラスに配置されており、これらのメソッドはクラスを通じて呼び出すことができます: 🎜🎜🎜🎜コンテキストマネージャーを作成する🎜🎜コンテキストマネージャーの一般的な導入は次のとおりです: コードブロックが実行される前に、準備作業が行われます。最初に実行; in コードブロックが実行された後、最終処理を実行します。 with ステートメントは、コンテキスト マネージャーと一緒に使用されることがよくあります。古典的なシナリオには次のようなものがあります。

with ステートメントを通じて、コードはファイルを開く操作を完了し、呼び出しが終了するか読み取り中に例外が発生したとき、つまりファイルの読み取りと書き込み後の処理でファイルを自動的に閉じます。完成されました。コンテキストマネージャーを渡さない場合、コードは次のようになります: with语句,代码完成了文件打开操作,并在调用结束,或者读取发生异常时自动关闭文件,即完成了文件读写之后的处理工作。如果不通过上下文管理器的话,则会是这样的代码:

比较繁琐吧?所以说使用上下文管理器的好处就是,通过调用我们预先设置好的回调,自动帮我们处理代码块开始执行和执行完毕时的工作。而通过自定义类的__enter____exit__方法,我们可以自定义一个上下文管理器。

然后可以以这样的方式进行调用:

在调用的时候:

with语句先暂存了ReadFile类的__exit__方法

然后调用ReadFile类的__enter__方法

__enter__方法打开文件,并将结果返回给with语句

上一步的结果被传递给file_read参数

with语句内对file_read参数进行操作,读取每一行

读取完成之后,with语句调用之前暂存的__exit__方法

__exit__方法关闭了文件

要注意的是,在__exit__方法内,我们关闭了文件,但最后返回True,所以错误不会被with语句抛出。否则with

🎜🎜かなり複雑ですよね?したがって、コンテキスト マネージャーを使用する利点は、プリセット コールバックを呼び出すことによって、コード ブロックの実行の開始時と終了時の作業の処理が自動的に支援されることです。カスタム クラスの __enter__ メソッドと __exit__ メソッドをカスタマイズすることで、コンテキスト マネージャーをカスタマイズできます。 🎜🎜🎜🎜その後、次のように呼び出すことができます。 🎜🎜🎜🎜呼び出し時: 🎜🎜 with ステートメントは、まず ReadFile クラスの __exit__ メソッドを一時的に保存します🎜🎜 次に、ReadFile の <code>__enter__ を呼び出します。 code> クラス code> メソッド🎜🎜<code>__enter__ メソッドはファイルを開き、結果を with ステートメントに返します🎜🎜 前のステップの結果は に渡されますfile_read パラメータ🎜 🎜with ステートメント内の file_read パラメータに対する操作、各行の読み取り🎜🎜 読み取りが完了すると、with code> ステートメントは、以前に保存された <code>__exit__ メソッドを呼び出します🎜🎜__exit__ メソッドはファイルを閉じます🎜🎜 __exit__ メソッド内では、ファイルは閉じますが、最後に True を返すため、with ステートメントによってエラーはスローされません。それ以外の場合、with ステートメントは対応するエラーをスローします。 🎜

以上が効率的な Python コードの書き方の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート