2015-04-10 2 views
2

Я испытываю нечетное поведение в методе __new__ метакласса Python. Я знаю, что следующий код работает отлично:Нечетное поведение в методе __new__ метакласса Python

def create_property(name, _type): 

    def getter(self): 
     return self.__dict__.get(name) 

    def setter(self, val): 
     if isinstance(val, _type): 
      self.__dict__[name] = val 
     else: 
      raise ValueError("Type not correct.") 

    return property(getter, setter) 


class Meta(type): 

    def __new__(cls, clsname, bases, clsdict): 
     for key, val in clsdict.items(): 

      if isinstance(val, type): 
       clsdict[key] = create_property(key, val) 

     return super().__new__(cls, clsname, bases, clsdict) 

Но когда избежать определения функции define_property и ввода коды внутри for в __new__ странных вещей происходит. Ниже приведен модифицированный код:

class Meta(type): 

    def __new__(meta, name, bases, clsdict): 

     for attr, data_type in clsdict.items(): 
      if not attr.startswith("_"): 

       def getter(self): 
        return self.__dict__[attr] 

       def setter(self, val): 
        if isinstance(val, data_type): 
         self.__dict__[attr] = val 
        else: 
         raise ValueError(
          "Attribute '" + attr + "' must be " + str(data_type) + ".") 

       clsdict[attr] = property(getter, setter) 

     return super().__new__(meta, name, bases, clsdict) 

Идея заключается в возможности создавать классы, которые ведут себя как формы, то есть:

class Company(metaclass=Meta): 
    name = str 
    stock_value = float 
    employees = list 

if __name__ == '__main__': 

    c = Company() 
    c.name = 'Apple' 
    c.stock_value = 125.78 
    c.employees = ['Tim Cook', 'Kevin Lynch'] 

    print(c.name, c.stock_value, c.employees, sep=', ') 

При выполнении различных ошибок начинают происходить, например, как:

Traceback (most recent call last): 
    File "main.py", line 37, in <module> 
    c.name = 'Apple' 
    File "main.py", line 13, in setter 
    if isinstance(val, data_type): 
TypeError: isinstance() arg 2 must be a type or tuple of types 

Traceback (most recent call last): 
    File "main.py", line 38, in <module> 
    c.stock_value = 125.78 
    File "main.py", line 17, in setter 
    "Attribute '" + attr + "' must be " + str(data_type) + ".") 
ValueError: Attribute 'name' must be <class 'str'>. 

Traceback (most recent call last): 
    File "main.py", line 37, in <module> 
    c.name = 'Apple' 
    File "main.py", line 17, in setter 
    "Attribute '" + attr + "' must be " + str(data_type) + ".") 
ValueError: Attribute 'stock_value' must be <class 'float'>. 

Traceback (most recent call last): 
    File "main.py", line 37, in <module> 
    c.name = 'Apple' 
    File "main.py", line 17, in setter 
    "Attribute '" + attr + "' must be " + str(data_type) + ".") 
ValueError: Attribute 'employees' must be <class 'list'>. 

Итак, что здесь происходит? В чем разница между наличием create_property, определенным отдельно, чем в методе __new__?

+0

Престола [ Локальные переменные в вложенных функциях Python] (http://stackoverflow.com/questions/12423614/local-variables-in-python-nested-functions). Любопытно, что эта же тема появилась вчера в [Python Dispatcher Definitions in a Function] (http://stackoverflow.com/questions/29541833/python-dispatcher-definitions-in-a-function) –

ответ

1

Это связано с тем, как привязка области видимости и переменная работает в python. Вы определяете функцию в цикле, которая обращается к локальной переменной; но это локальный переменный ищутся во время исполнения функции, не связанного во время его определения:

fcts = [] 
for x in range(10): 
    def f(): print x 
    fcts.append(f) 
for f in fcts: f() #prints '9' 10 times, as x is 9 after the loop 

Как вы обнаружили, вы можете просто создать замыкание по сравнению с текущим значением контура с помощью функции полезности :

fcts = [] 
def make_f(x): 
    def f(): print x 
    return f 

for x in range(10): 
    fcts.append(make_f(x)) 
for f in fcts: f() #prints '0' to '9' 

Другая возможность состоит в том, чтобы (аб) использовать аргумент по умолчанию, как те, которые назначаются при создании функции:

fcts = [] 
for x in range(10): 
    def f(n=x): print n 
    fcts.append(f) 
for f in fcts: f() #prints '0' to '9' 
+0

спасибо! примеры сделали это очень ясным. –

+2

Другая возможность - использовать functools 'partial', как показано в ссылках в моем комментарии к вопросу. Мои личные предпочтения относятся к подходу функции полезности. :) –