>백엔드 개발 >파이썬 튜토리얼 >효율적인 Python 코드를 작성하는 방법

효율적인 Python 코드를 작성하는 방법

巴扎黑
巴扎黑원래의
2017-09-09 11:38:381415검색

이 글은 효율적이고 우아한 Python 코드를 작성하는 방법을 주로 소개하고 공유합니다. 필요한 친구들은 참고해도 됩니다.

이 글의 일부는 "Effective Python" & "Python3 Cookbook"이라는 책에서 발췌되었지만, 수정 및 추가된 내용은 작성자가 직접 이해하고 적용한 모범 사례입니다.

전체 텍스트는 약 9956 단어이며, 읽는 데 24분이 걸릴 수 있습니다.

Pythonic 목록 자르기

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🎜🎜🎜🎜두 개 이상의 표현식이 포함된 목록 이해를 사용하지 마세요🎜🎜🎜🎜데이터가 많은 경우 리스트 컴프리헨션은 메모리를 많이 소모할 수 있습니다. 이 경우 권장됩니다. 생성기 표현식을 사용하려면🎜🎜🎜🎜Iteration🎜🎜 enumerate🎜🎜enumerate는 반복 중에 index에 추가된 값으로 두 번째 매개변수를 허용할 수 있습니다🎜🎜🎜🎜zip을 사용하여 두 개의 반복기를 순회하세요. 동시에🎜🎜🎜🎜zip순회할 때 튜플을 반환합니다🎜🎜🎜🎜forwhile 루프 뒤의 else 블록 🎜🎜루프가 정상적으로 종료된 후 else의 코드 는 루프break를 통해 🎜🎜호출됩니다. 루프를 벗어나면 else는 실행되지 않습니다.🎜🎜순회할 시퀀스가 비어 있으면 else🎜🎜🎜🎜역방향 반복🎜🎜일반 시퀀스(목록)의 경우 내장된 reversed() 함수를 사용하여 역방향 반복을 수행할 수 있습니다. 🎜🎜🎜🎜또한 __reversed__를 구현하여 클래스를 역반복할 수도 있습니다. > 클래스의 메소드: 🎜🎜🎜🎜 try/excess/else/finally🎜🎜if try code> 내에서 예외가 발생하지 않으면 <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 메서드를 호출해야 합니다. 그러나 사실 우리의 초점은 이 방법에 전혀 있지 않습니다. 실제로는 index만 필요합니다. > 계속해서 최적화할 수 있습니다. 🎜🎜데이터는 결과에 저장됩니다. 데이터 양이 많으면 더 많은 메모리를 차지하게 됩니다.🎜🎜생성기를 사용하는 것이 좋습니다. 생성기. 생성기는 yield 표현식을 사용하는 함수입니다. 생성기가 호출되면 실제로 실행되지 않지만 내장된 next 함수를 사용하면 반복자는 생성기를 다음 <code>yield 표현식으로 진행합니다: 🎜🎜🎜🎜생성기를 얻은 후 정상적으로 탐색할 수 있습니다: 🎜🎜🎜🎜 그래도 목록이 필요한 경우 함수 호출 결과를 매개변수로 사용한 다음 list 메서드를 호출할 수 있습니다 🎜 🎜🎜🎜Iterable 개체🎜🎜주의해야 할 점은 , 일반 반복자는 한 라운드 동안만 반복할 수 있으며 한 라운드 이후의 반복 호출은 유효하지 않습니다. 이 문제를 해결하는 방법은 반복 가능한 컨테이너 클래스를 정의할 수 있다는 것입니다: 🎜🎜🎜🎜이 경우 클래스 인스턴스를 몇 번 반복하는지는 중요하지 않습니다. 🎜🎜🎜🎜그러나 이는 __iter__ 메서드를 구현하는 반복자일 뿐이라는 점에 유의해야 합니다. 반복하려면 for 루프를 통해서만 전달할 수 있습니다. next 메서드를 통해 반복하려면 iter 메서드를 사용해야 합니다. 🎜 🎜🎜🎜위치 매개변수를 사용하세요🎜🎜때때로 합산 방법 정의와 같이 메소드에서 수신한 매개변수는 확실하지 않을 수 있으며 최소한 두 개의 매개변수를 수신해야 합니다. 🎜🎜🎜🎜 특정 개수의 매개변수가 없고 매개변수가 전달되는 순서를 신경 쓰지 않는 이런 종류의 함수의 경우 위치 매개변수 * 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

🎜🎜Class __slots__🎜🎜기본적으로 Python은 사전을 사용하여 인스턴스 속성을 저장합니다. 객체. 이를 통해 런타임 시 클래스 인스턴스에 새 속성을 동적으로 추가할 수 있습니다. 🎜🎜🎜🎜그러나 이 사전은 추가 공간을 낭비합니다. 많은 경우 우리는 그렇게 많은 속성을 생성하지 않을 것입니다. 따라서 __slots__는 Python🎜🎜에 사전을 사용하지 않고 고정 컬렉션을 사용하여 공간을 할당하도록 지시할 수 있습니다. 🎜🎜🎜🎜__call__🎜 🎜클래스에 __call__ 메서드를 정의하면 클래스의 인스턴스를 일반 함수처럼 호출할 수 있습니다. 🎜🎜🎜🎜이런 방식으로 구현할 때의 장점은, 클로저나 전역 변수를 만들지 않고도 클래스 속성을 통해 상태를 저장할 수 있습니다. 🎜🎜@classmethod & @staticmethod🎜🎜@classmethod@staticmethod는 매우 유사하지만 사용 시나리오 동일하지 않습니다. 🎜🎜클래스 내의 일반 메서드는 모두 첫 번째 매개 변수로 self를 사용합니다. 즉, 인스턴스를 통해 호출되면 인스턴스 범위가 메서드에 전달됩니다. 🎜🎜@classmethod는 <code>cls를 첫 번째 매개변수로 사용합니다. 이는 클래스 자체의 범위를 전달한다는 의미입니다. 클래스를 통해 호출되는지, 클래스의 인스턴스를 통해 호출되는지에 관계없이 기본적으로 전달되는 첫 번째 매개변수는 클래스 자체가 됩니다.🎜🎜@staticmethod는 다음과 유사하게 기본 매개변수를 전달할 필요가 없습니다. 일반적인 함수 🎜🎜예제를 통해 사용 시나리오를 이해해 보겠습니다. 🎜🎜연/월/일의 세 가지 데이터를 저장하기 위해 Date라는 클래스를 만들어야 한다고 가정합니다🎜🎜🎜🎜위 코드는 Date 클래스를 생성합니다. 일/월/년 속성은 초기화 중에 설정되고 getter속성을 통해 설정됩니다. 이 속성은 인스턴스화 후에 에 전달될 수 있습니다. time저장된 시간 가져오기: 🎜🎜🎜 🎜하지만 속성이 전달되는 방식을 변경하려면 어떻게 해야 할까요? 결국 초기화 시 연/월/일 3가지 속성을 전달해야 하는 것이 귀찮습니다. 기존 인터페이스와 메서드를 변경하지 않고 2016-11-09와 같은 문자열을 전달하여 Date 인스턴스를 생성하는 방법을 찾을 수 있습니까? 🎜🎜이 방법을 생각해 보세요: 🎜🎜🎜 🎜 하지만 충분하지 않습니다. 🎜🎜클래스 외부에 추가 메서드를 작성하고 매개변수를 가져오려면 매번 형식을 지정해야 합니다.🎜🎜이 메서드는 Date 클래스에만 관련됩니다🎜🎜그렇습니다 들어오는 매개변수를 해결하지 마세요. 문제가 너무 많습니다🎜🎜이때 @classmethod를 사용하여 클래스 내부에 새 형식 문자열을 만들고 클래스 인스턴스의 메서드를 반환할 수 있습니다. 🎜🎜🎜🎜이런 식으로 Date 클래스 >from_string 메소드는 이전 인스턴스화 메소드를 침해하거나 수정하지 않고 인스턴스를 생성합니다: 🎜🎜🎜🎜이점: 🎜🎜@classmethod에서 cls 매개변수를 사용하여 동일한 결과를 얻을 수 있습니다. 외부 클래스를 호출할 때처럼 편리합니다. 🎜🎜재사용성을 높이기 위해 메소드를 추가로 캡슐화할 수 있습니다🎜🎜객체 지향 프로그래밍에 더 가깝습니다🎜🎜그리고 @staticmethod는 다음과 유사합니다. 일반 함수는 이 클래스 관련 도우미와 결합될 수 있습니다🎜🎜 메서드는 @staticmethod로 클래스에 배치되며 클래스를 통해 직접 메서드가 호출됩니다. 🎜🎜🎜🎜 날짜 관련 보조 클래스 함수를 After로 사용하세요 @staticmethod 메서드는 Date 클래스에 배치되며 다음 메서드는 클래스를 통해 호출할 수 있습니다. 🎜🎜🎜🎜컨텍스트 관리자 만들기🎜🎜컨텍스트 관리자, 인기 있는 소개는 다음과 같습니다. 코드 블록이 실행되기 전 준비 작업은 다음과 같습니다. done first; 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>__exit__ 메서드를 호출합니다. 🎜🎜__exit__ 메서드는 파일을 닫습니다.🎜🎜__exit__ 메서드 내에서 주의해야 합니다. , 파일을 닫지만 마지막에 True를 반환하므로 with 문에서 오류가 발생하지 않습니다. 그렇지 않으면 with 문에서 해당 오류가 발생합니다. 🎜

위 내용은 효율적인 Python 코드를 작성하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.