2016-11-22 4 views
3

Я пытаюсь создать класс с несколькими переменными уровня класса, некоторые из которых имеют рассчитанные значения, которые ссылаются на ранее объявленные переменные уровня класса. Однако у меня возникают трудности с ссылкой на переменные в определенных точках.Неравномерность присвоения переменной класса Python

Моя первая попытка:

#!/usr/bin/env python 
from decimal import Decimal 
import math 

class Foo(object): 
    NUM_BUCKETS = 10 
    BUCKET_SIZE = Decimal(1.0/NUM_BUCKETS) 
    BUCKET_LABELS = tuple("BUCKET_{}".format(int(BUCKET_SIZE * i * 100)) for i in xrange(1, NUM_BUCKETS + 1)) 

print Foo.BUCKET_LABELS 

Результат:

> python test.py 
Traceback (most recent call last): 
    File "test.py", line 5, in <module> 
    class Foo(object): 
    File "test.py", line 8, in Foo 
    BUCKET_LABELS = tuple("BUCKET_{}".format(int(BUCKET_SIZE * i * 100)) for i in xrange(1, NUM_BUCKETS + 1)) 
    File "test.py", line 8, in <genexpr> 
    BUCKET_LABELS = tuple("BUCKET_{}".format(int(BUCKET_SIZE * i * 100)) for i in xrange(1, NUM_BUCKETS + 1)) 
NameError: global name 'BUCKET_SIZE' is not defined 

Попытка получить доступ к переменной класса через имя класса не работает либо:

#!/usr/bin/env python 
from decimal import Decimal 
import math 

class Foo(object): 
    NUM_BUCKETS = 10 
    BUCKET_SIZE = Decimal(1.0/NUM_BUCKETS) 
    BUCKET_LABELS = tuple("BUCKET_{}".format(int(Foo.BUCKET_SIZE * i * 100)) for i in xrange(1, NUM_BUCKETS + 1)) 

print Foo.BUCKET_LABELS 

Результат:

> python test2.py 
Traceback (most recent call last): 
    File "test2.py", line 5, in <module> 
    class Foo(object): 
    File "test2.py", line 8, in Foo 
    BUCKET_LABELS = tuple("BUCKET_{}".format(int(Foo.BUCKET_SIZE * i * 100)) for i in xrange(1, NUM_BUCKETS + 1)) 
    File "test2.py", line 8, in <genexpr> 
    BUCKET_LABELS = tuple("BUCKET_{}".format(int(Foo.BUCKET_SIZE * i * 100)) for i in xrange(1, NUM_BUCKETS + 1)) 
NameError: global name 'Foo' is not defined 

Замена ссылки на BUCKET_SIZE с жестким кодом устраняет проблему; несмотря на то, что есть еще одна ссылка на переменную уровня класса в одной и той же линии, она работает просто отлично:

#!/usr/bin/env python 
from decimal import Decimal 
import math 

class Foo(object): 
    NUM_BUCKETS = 10 
    BUCKET_SIZE = Decimal(1.0/NUM_BUCKETS) 
    BUCKET_LABELS = tuple("BUCKET_{}".format(int(Decimal(0.1) * i * 100)) for i in xrange(1, NUM_BUCKETS + 1)) 

print Foo.BUCKET_LABELS 

Результат:

> python test3.py 
('BUCKET_10', 'BUCKET_20', 'BUCKET_30', 'BUCKET_40', 'BUCKET_50', 'BUCKET_60', 'BUCKET_70', 'BUCKET_80', 'BUCKET_90', 'BUCKET_100') 

Кто-нибудь знает правильный способ ссылки BUCKET_SIZE в этом месте? Это ошибка в самом Python? (Я бегу на Python 2.7.5, BTW.)

+2

Это похоже на проблему с областью выражения генератора, потому что ошибка исчезла, когда я изменил ее на понимание списка. – TigerhawkT3

+1

Также NUM_BUCKETS работает хорошо в генераторе, выпуская только часть с BUCKET_SIZE –

+1

По существу, обман [этого вопроса] (http://stackoverflow.com/questions/20136955/python3-nested-list-comprehension-scope), кроме этот вопрос находится на Python 3 и использует понимание списка. Я не вижу никаких хороших кандидатов-кандидатов, использующих genexps. – user2357112

ответ

2

Прежде всего, решение среди других, просто редактируя одну строку (обратите внимание на скобки):

BUCKET_LABELS = tuple(["BUCKET_{}".format(int(BUCKET_SIZE * i * 100)) for i in xrange(1, NUM_BUCKETS + 1)]) 

Теперь, если вам интересно, чтобы почему он работает, как, что в Python, и почему это не ошибка ... Ну, это не легко один :-):

[я * 2 для г в xrange (3)] ​​является список постигает. Он генерирует реальный список, который может быть использован, как это, например:

>>> a = [i*2 for i in xrange(3)] 
>>> a 
[0, 2, 4] 

(я * 2 для г в xrange (3)) является выражениемгенератора. Он работает в довольно сходным образом, но не совсем, так как он не генерирует список или кортеж, но, скорее, генератор:

>>> a = (i*2 for i in xrange(3)) 
>>> a 
<generator object <genexpr> at 0x02CEE058> 
>>> a.next() 
0 
>>> a.next() 
2 
>>> a.next() 
4 
>>> a.next() 
Traceback (most recent call last): 
    File "<input>", line 1, in <module> 
StopIteration 
>>> a = (i*2 for i in xrange(3)) 
>>> tuple(a) 
(0, 2, 4) 
>>> tuple(a) 
() 

Вы можете найти более подробную информацию здесь, если Вам интересно: generator expressions.

Версия tl; dr заключается в том, что к генератору не удается получить доступ напрямую (вы должны попросить его сгенерировать его содержимое, используя следующий(), например) и что каждое значение может генерироваться только один раз (а затем генератор перемещается к следующему, следовательно, имя функции next()).

Итак, вернемся к вашей проблеме. В приведенном ниже выражении вы фактически просите сгенерировать кортеж с выражением генератора, что само по себе прекрасно. Тем не менее, вы делаете это, используя переменные класса Foo, которые в случае генератора могут быть проблематичными.

BUCKET_LABELS = tuple("BUCKET_{}".format(int(BUCKET_SIZE * i * 100)) for i in xrange(1, NUM_BUCKETS + 1)) 

В частности, генератор на самом деле не знает о переменной Foo.BUCKET_SIZE вообще, когда вы попросите его, чтобы создать кортеж из него (генератор работает в пределах в его объеме, в отличие от списка). Вот почему вы получаете эту ошибку.

Итак, одним из решений является просто использовать понимание списка (которое в любом случае проще в обращении/интуитивном).

PS: функция Decimal(), вероятно, не делать то, что вы думаете, что делает:

>>> NUM_BUCKETS = 10 
>>> print Decimal(1.0/NUM_BUCKETS) 
0.1000000000000000055511151231257827021181583404541015625 
>>> print round(1.0/NUM_BUCKETS, 2) 
0.1 

PPS: причина, почему вы не получите сообщение об ошибке с xrange (1, NUM_BUCKETS + 1) часть , если вам интересно, это потому, что он оценивается до генератор построен, поэтому переменная класса фактически заменяется значением для генератора ... который не жалуется на это.

+2

Предлагаемые исправления перерывы на Python 3. Как правило, лучше всего не использовать genexps или понимания вообще внутри оператора класса. – user2357112

+0

А, да, действительно, я тестировал все это на Python 2.7. – Deneb

+0

Спасибо! Это работает. (Кроме того, я знаю, что элемент десятичного деления ошибочен. Последняя версия изменяет его на «Десятичный (1)/NUM_BUCKETS', который работает так, как ожидалось.) – macdjord

Смежные вопросы