Это поведение срабатывает, потому что вы используете лямбда-выражение () => variable * 2
, где внешняя область variable
фактически не определена во внутренней области лямбда.
Лямбда-выражения (в C # 3 +, а также анонимные методы в C # 2) по-прежнему создают реальные методы. Передача переменных этим методам связана с некоторыми дилеммами (передать по значению? Pass по ссылке? C # идет по ссылке - но это открывает еще одну проблему, когда ссылка может пережить реальную переменную). Что C # для решения всех этих дилемм заключается в создании нового вспомогательного класса («замыкание») с полями, соответствующими локальным переменным, используемым в лямбда-выражениях, и методам, соответствующим фактическим лямбда-методам. Любые изменения в variable
в вашем коде фактически преобразуются для изменения в этом ClosureClass.variable
. Таким образом, ваш цикл while обновляет ClosureClass.variable
до тех пор, пока он не достигнет 10, тогда вы для циклов выполняете действия, которые все работают на одном и том же ClosureClass.variable
.
Чтобы получить ожидаемый результат, вам необходимо создать разделение между переменной цикла и переменной, которая закрывается. Вы можете сделать это, введя другую переменную, то есть:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
var t = variable; // now t will be closured (i.e. replaced by a field in the new class)
actions.Add(() => t * 2);
++variable; // changing variable won't affect the closured variable t
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Вы также можете переместить замыкание на другой метод для создания этого разделения:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
actions.Add(Mult(variable));
++variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Вы можете реализовать Mult как лямбда-выражение (неявное замыкание)
static Func<int> Mult(int i)
{
return () => i * 2;
}
или с фактическим вспомогательным классом:
public class Helper
{
public int _i;
public Helper(int i)
{
_i = i;
}
public int Method()
{
return _i * 2;
}
}
static Func<int> Mult(int i)
{
Helper help = new Helper(i);
return help.Method;
}
В любом случае «Closures» не являются концепцией, связанной с циклами, но скорее, к анонимным методам / лямбда-выражениям используют локальные переменные с областью - хотя некоторые неосторожное использование циклов демонстрируют закрытие ловушек.
get_membervalues
не определено внутри myclass
, поэтому a
не будет иметь к нему доступа.
Он определен внутри myproperty
, поэтому следующий логический вывод - попытаться добраться до объекта myproperty
, связанного с a.data
. Поскольку Python хранит функции в myclass
в myclass.__dict__
, мы можем найти объект свойства следующим образом:
a.__class__.__dict__['data']
И затем вызвать функцию оттуда:
a.__class__.__dict__['data'].get_membervalues()
>>> {'name': 'data', 'cls_name': 'AnotherClass', 'tooltip': 'help'}
Как получить доступ к методу "get_memvervalues" или атрибутам _name, _tooltip и т. д.?
blockquote>Из класса (
myclass
):[1115 ] Из экземпляра класса (myclass.__dict__['data']
a
):
type(a).__dict__['data']
В обоих случаях
data
является именем атрибута.Например:
a = myclass() desc = type(a).__dict__['data'] print(desc.get_membervalues()) # { # 'name': 'data', # 'cls_name': 'AnotherClass', # 'tooltip': 'help' # } print(desc._tooltip) # help print(vars(desc)) # { # '_name': 'data', # '_value': 4, # '_cls_name': 'AnotherClass', # '_datatype': <class 'int'>, # '_tooltip': 'help', # '_kwargs': {} # }
Но, на первый взгляд, есть проблема с вашей реализацией:
a = myclass() b = myclass() print("Before:") # Before: print("a:", a.data) # a: 4 print("b:", b.data) # b: 4 a.data = 10 print("After:") # After: print("a:", a.data) # a: 10 print("b:", b.data) # b: 10 (!!!)
Если это имеет смысл для вас, тогда вы можете прекратить чтение и вы используете дескриптор метода нетрадиционным способом.
Несмотря на наличие двух экземпляров класса, существует только один экземпляр дескриптора:
type(a).__dict__['data'] is type(b).__dict__['data']
(что должно быть очевидно как
type(a) is type(b)
).Чтобы поддержать дескриптор, «действующий по-другому», первый аргумент (вы называете его
instance
в__set__
, но он также передается в__get__
) является ссылкой на экземпляр класса - это то, что [1112 ] будет внутри традиционного метода.Обычно вы хотите использовать это как-то.