2017-11-01 1 views
1

최근에 HTTP Range 필드를 사용하여 동시에 많은 블록을 다운로드하는 다운로드 프로그램을 작성 중입니다.func (* iterable)을 실행할 때 CPython이 len (iterable)을 호출하는 이유는 무엇입니까?

header = {'Range': 'bytes={}-{}'.format(*the_range)} 

그리고 len(the_range) 얼마나 많은입니다 :

class ClosedRange: 
    def __init__(self, begin, end): 
     self.begin = begin 
     self.end = end 

    def __iter__(self): 
     yield self.begin 
     yield self.end 

    def __str__(self): 
     return '[{0.begin}, {0.end}]'.format(self) 

    def __len__(self): 
     return self.end - self.begin + 1 

__iter__ 마법 방법은 튜플 풀기를 지원하는 것입니다 : 나는 범위를 나타내는 파이썬 클래스를 작성 (HTTP 헤더의 범위는 폐쇄 구간입니다) 해당 범위의 바이트.

이제는 'bytes={}-{}'.format(*the_range)MemoryError 인 경우가 종종 있습니다. 일부 디버깅 후 나는 CPython 인터프리터가 func(*iterable)을 실행할 때 len(iterable)을 호출하려고 시도하고 길이에 따라 메모리를 할당 할 수 있음을 발견했습니다. 내 컴퓨터에서 len(the_range)이 1GB보다 크면 MemoryError이 표시됩니다.

이 간단 하나입니다

class C: 
    def __iter__(self): 
     yield 5 

    def __len__(self): 
     print('__len__ called') 
     return 1024**3 

def f(*args): 
    return args 

>>> c = C() 
>>> f(*c) 
__len__ called 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
MemoryError 
>>> # BTW, `list(the_range)` have the same problem. 
>>> list(c) 
__len__ called 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
MemoryError 

그래서 제 질문은 다음과 같습니다

  1. len(iterable) 전화 CPython에? this question에서 iterator가 반복 될 때까지 iterator의 길이를 알 수 없습니다. 이것이 최적화입니까?

  2. __len__ 메서드는 객체의 '가짜'길이 (즉, 메모리의 실제 요소 수가 아님)를 반환합니까?

+1

https://www.python.org/dev/peps/pep-0424/. 어쨌든이 작업을하지 않는 것이 좋습니다 (범위의 요소를 산출하지 못하는'__iter__ '을 가짐). – Ryan

+0

반복자가 두 개의 항목을 생성하면 시퀀스의 길이는 2입니다. – jasonharper

+0

@ juanpa.arrivillaga @Ryan 알아요. 'list (it)'또는'f (* it)'는 모두 시퀀스를 생성하고'operator.length_hint (it) '를 호출하여 공간을 미리 할당합니다. 그리고'operator.length_hint'는'it'은'__len__' 메쏘드를 가지고 있으므로'len (it)'을 반환합니다 - 그래서 너무 큰 할당 순서. 그게 맞습니까? – CSM

답변

2

len(iterable) 전화 CPython에? 이 질문에서 반복자가 반복 할 때까지 반복자 길이를 알 수 없다는 것을 알 수 있습니다. 이것이 최적화입니까?

파이썬 (가정 python3)이 f(*c)을 실행 오피 CALL_FUNCTION_EX 사용된다 c 같은

0 LOAD_GLOBAL    0 (f) 
2 LOAD_GLOBAL    1 (c) 
4 CALL_FUNCTION_EX   0 
6 POP_TOP 

는 반복 가능하다 PySequence_Tuple 튜플로 변환이라고하고 PyObject_LengthHint는을 결정이라고 새로운 튜플의 길이는 __len__ 메서드가 c에 정의되어 있으므로 호출되고 해당 반환 값은 malloc이 실패한 MemoryError 오류가 발생하여 새 튜플에 대한 메모리를 할당하는 데 사용됩니다.

/* Guess result size and allocate space. */ 
n = PyObject_LengthHint(v, 10); 
if (n == -1) 
    goto Fail; 
result = PyTuple_New(n); 

__len__ 방법은 '가짜'길이를 리턴 (즉, 메모리가 아닌 요소의 실수) 물체?

, 예.

__len__의 값이 필요 이상이면, 파이썬은 튜플을 채울 때 적합하도록 새로운 튜플 객체의 메모리 공간을 조정합니다. 그것이 필요 이상인 경우, 파이썬이 여분의 메모리를 할당하지만, 초과 할당 공간을 되찾기 위해 _PyTuple_Resize이 결국 호출됩니다.

관련 문제