> 백엔드 개발 > 파이썬 튜토리얼 > 개발 중에 자주 직면하는 Python의 함정과 예방 조치

개발 중에 자주 직면하는 Python의 함정과 예방 조치

大家讲道理
풀어 주다: 2016-11-07 16:07:50
원래의
1242명이 탐색했습니다.

최근 Python을 사용하는 과정에서 변수 객체 datetime.datetime.now()를 함수의 기본 매개변수로 사용하는 경우, 모듈 순환 종속성 등 몇 가지 함정에 직면했습니다.

향후 문의 및 추가를 위해 여기에 기록해 주세요.

변경 가능한 객체를 기본 매개변수로 사용하지 마세요

함수를 사용할 때 기본 매개변수가 포함되는 경우가 많습니다. Python에서 변경 가능한 객체를 기본 매개변수로 사용하면 예기치 않은 결과가 발생할 수 있습니다.

아래 예를 살펴보세요.

1

2

3

4

5

6

7

def append_item(a = 1, b = []):

    b.append(a)

    print b

      

append_item(a=1)

append_item(a=3)

append_item(a=5)

로그인 후 복사


결과는 다음과 같습니다.

[1]

[ 1, 3]

[1, 3, 5]


결과에서 알 수 있듯이, 나중에append_item 함수를 두 번 호출하면 함수 매개변수 b []로 초기화되지 않지만 이전 함수 호출의 값을 유지합니다.

이러한 결과가 나오는 이유는 Python에서는 함수 정의 시 함수 매개변수의 기본값이 한 번만 초기화되기 때문입니다.

Python의 이 기능을 증명하는 예를 살펴보겠습니다.


1

2

3

4

5

6

7

8

9

class Test(object): 

    def __init__(self): 

        print("Init Test"

            

def arg_init(a, b = Test()): 

    print(a) 

arg_init(1) 

arg_init(3) 

arg_init(5)

로그인 후 복사


결과는 다음과 같습니다.

초기 테스트

1

3

5


결과에서 알 수 있듯이 이 예에서 Test 클래스는 한 번만 인스턴스화됩니다. 즉, 기본 매개변수는 함수 호출 수와 관련이 없으며 함수가 정의될 ​​때 한 번만 초기화됩니다.

가변 기본 매개변수의 올바른 사용

가변 기본 매개변수의 경우 다음 패턴을 사용하여 위와 같은 예상치 못한 결과를 방지할 수 있습니다.

1

2

3

4

5

6

7

8

9

def append_item(a = 1, b = None):

    if b is None:

        b = []

    b.append(a)

    print b

      

append_item(a=1)

append_item(a=3)

append_item(a=5)

로그인 후 복사


결과는 다음과 같습니다.

[1]

[3]

[5]


Python의 범위

Python의 범위 확인 순서는 Local, Enclosing, Global, 내장입니다. 이는 Python 인터프리터가 이 순서에 따라 변수를 구문 분석한다는 의미입니다.

간단한 예를 살펴보세요.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

global_var = 0

def outer_func():

    outer_var = 1

      

    def inner_func():

        inner_var = 2

          

        print "global_var is :", global_var

        print "outer_var is :", outer_var

        print "inner_var is :", inner_var

          

    inner_func()

      

outer_func()

로그인 후 복사


결과는 다음과 같습니다.


global_var is : 0

outer_var is : 1

inner_var is : 2


Python에서는 범위 1에 대해 주목할 점은 범위의 변수에 값을 할당할 때 Python은 해당 변수를 현재 범위의 지역 변수로 간주한다는 것입니다.


이 역시 비교적 이해하기 쉽습니다. 다음 코드의 경우 var_func는 num 변수에 값을 할당하므로 여기서 num은 var_func 범위의 로컬 변수입니다.


1

2

3

4

5

6

num = 0

def var_func():

    num = 1

    print "num is :", num

      

var_func()

로그인 후 복사


질문 1

단, 다음과 같이 사용할 때에는 way 변수를 사용하면 문제가 발생합니다.


1

2

3

4

5

6

num = 0

def var_func():

    print "num is :", num

    num = 1

      

var_func()

로그인 후 복사


결과는 다음과 같습니다.


1

UnboundLocalError: local variable 'num' referenced before assignment

로그인 후 복사


이 오류가 발생하는 이유는 var_func의 num 변수에 값을 할당했기 때문입니다. , 그래서 Python 인터프리터는 num이 var_func 범위의 지역 변수라고 생각하지만 코드가 "num is:" 인쇄로 실행될 때 num 문은 여전히 ​​정의되지 않습니다.


질문 2

위 오류는 비교적 명백하며, 다음과 같이 좀 더 미묘한 오류 형태도 있습니다.


1

2

3

4

5

6

7

8

9

li = [1, 2, 3]

def foo():

    li.append(4)

    print li

foo()

def bar():

    li +=[5]

    print li

bar()

로그인 후 복사


코드 결과는 다음과 같습니다.



[1, 2, 3, 4]

1

UnboundLocalError: local variable 'li' referenced before assignment

로그인 후 복사


foo 함수에서는 Python의 범위 결정 순서에 따라 전역 함수를 사용합니다. li 변수; 그러나 bar 함수에서는 li 변수에 값이 할당되므로 li는 bar 범위에서 변수로 처리됩니다.


bar 함수의 이 문제에는 전역 키워드를 사용할 수 있습니다.

1

2

3

4

5

6

7

8

9

10

11

12

li = [1, 2, 3]

def foo():

    li.append(4)

    print li

      

foo()

def bar():

    global li

    li +=[5]

    print li

      

bar()

로그인 후 복사


클래스 속성 숨김

파이썬에는 클래스 속성과 인스턴스 속성이 있습니다. 클래스 속성은 클래스 자체에 속하며 모든 클래스 인스턴스에서 공유됩니다.

클래스 속성은 클래스 이름을 통해 액세스 및 수정이 가능하며, 클래스 인스턴스를 통해서도 액세스 및 수정이 가능합니다. 그러나 인스턴스가 클래스와 동일한 이름의 속성을 정의하면 클래스 속성이 숨겨집니다.

다음 예를 보세요.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

class Student(object):

    books = ["Python", "JavaScript", "CSS"]

    def __init__(self, name, age):

        self.name = name

        self.age = age

    pass

      

wilber = Student("Wilber", 27)

print "%s is %d years old" %(wilber.name, wilber.age)

print Student.books

print wilber.books

wilber.books = ["HTML", "AngularJS"]

print Student.books

print wilber.books

del wilber.books

print Student.books

print wilber.books

로그인 후 복사


코드의 결과는 다음과 같습니다. 처음에는 Wilber 인스턴스에서 책에 직접 액세스할 수 있습니다. 하지만 Wilber 인스턴스가 정의된 경우 books라는 인스턴스 속성을 삭제한 후 Wilber 인스턴스의 books 속성은 Wilber 인스턴스의 books 속성인 wilber.books를 삭제한 후 클래스의 books 속성을 "숨깁니다". 다시 클래스의 books 속성에 해당합니다.

1

2

3

4

5

6

7

Wilber is 27 years old

['Python', 'JavaScript', 'CSS']

['Python', 'JavaScript', 'CSS']

['Python', 'JavaScript', 'CSS']

['HTML', 'AngularJS']

['Python', 'JavaScript', 'CSS']

['Python', 'JavaScript', 'CSS']

로그인 후 복사


파이썬 값에서 상속을 사용할 때는 클래스 속성이 숨겨지는 것도 주의해야 합니다. 클래스의 경우 클래스의 __dict__ 속성을 통해 모든 클래스 속성을 볼 수 있습니다.

클래스 이름을 통해 클래스 속성에 액세스할 때 클래스의 __dict__ 속성을 먼저 검색합니다. 클래스 속성을 찾을 수 없으면 계속해서 상위 클래스를 검색합니다. 그러나 하위 클래스가 상위 클래스와 동일한 이름을 가진 클래스 속성을 정의하는 경우 하위 클래스의 클래스 속성은 상위 클래스의 클래스 속성을 숨깁니다.

예를 보면

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

class A(object):

    count = 1

      

class B(A):

    pass   

      

class C(A):

    pass       

      

print A.count, B.count, C.count     

B.count = 2

print A.count, B.count, C.count     

A.count = 3

print A.count, B.count, C.count    

print B.__dict__

print C.__dict__

로그인 후 복사


클래스 B가 count 속성을 정의하면 상위 클래스의 count 속성은 다음과 같습니다.

1

2

3

4

5

1 1 1

1 2 1

3 2 3

{'count': 2, '__module__': '__main__', '__doc__': None}

{'__module__': '__main__', '__doc__': None}

로그인 후 복사


튜플은 "변경 가능"합니다.

在Python中,tuple是不可变对象,但是这里的不可变指的是tuple这个容器总的元素不可变(确切的说是元素的id),但是元素的值是可以改变的。

1

2

3

4

5

6

7

tpl = (1, 2, 3, [4, 5, 6])

print id(tpl)

print id(tpl[3])

tpl[3].extend([7, 8])

print tpl

print id(tpl)

print id(tpl[3])

로그인 후 복사

代码结果如下,对于tpl对象,它的每个元素都是不可变的,但是tpl[3]是一个list对象。也就是说,对于这个tpl对象,id(tpl[3])是不可变的,但是tpl[3]确是可变的。

1

2

3

4

5

36764576

38639896

(1, 2, 3, [4, 5, 6, 7, 8])

36764576

38639896

로그인 후 복사

Python的深浅拷贝

在对Python对象进行赋值的操作中,一定要注意对象的深浅拷贝,一不小心就可能踩坑了。

当使用下面的操作的时候,会产生浅拷贝的效果:

使用切片[:]操作

使用工厂函数(如list/dir/set)

使用copy模块中的copy()函数

使用copy模块里面的浅拷贝函数copy():

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

import copy

will = ["Will", 28, ["Python", "C#", "JavaScript"]]

wilber = copy.copy(will)

print id(will)

print will

print [id(ele) for ele in will]

print id(wilber)

print wilber

print [id(ele) for ele in wilber]

will[0] = "Wilber"

will[2].append("CSS")

print id(will)

print will

print [id(ele) for ele in will]

print id(wilber)

print wilber

print [id(ele) for ele in wilber]

로그인 후 복사


使用copy模块里面的深拷贝函数deepcopy():

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

import copy

will = ["Will", 28, ["Python", "C#", "JavaScript"]]

wilber = copy.deepcopy(will)

print id(will)

print will

print [id(ele) for ele in will]

print id(wilber)

print wilber

print [id(ele) for ele in wilber]

will[0] = "Wilber"

will[2].append("CSS")

print id(will)

print will

print [id(ele) for ele in will]

print id(wilber)

print wilber

print [id(ele) for ele in wilber]

로그인 후 복사


模块循环依赖

在Python中使用import导入模块的时候,有的时候会产生模块循环依赖,例如下面的例子,module_x模块和module_y模块相互依赖,运行module_y.py的时候就会产生错误。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

# module_x.py

import module_y

      

def inc_count():

    module_y.count += 1

    print module_y.count

      

      

# module_y.py

import module_x

count = 10

def run():

    module_x.inc_count()

      

run()

로그인 후 복사

其实,在编码的过程中就应当避免循环依赖的情况,或者代码重构的过程中消除循环依赖。

当然,上面的问题也是可以解决的,常用的解决办法就是把引用关系搞清楚,让某个模块在真正需要的时候再导入(一般放到函数里面)。

对于上面的例子,就可以把module_x.py修改为如下形式,在函数内部导入module_y:

1

2

3

4

# module_x.py

def inc_count():

    import module_y

    module_y.count += 1

로그인 후 복사
관련 라벨:
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿