Reputation: 103
all! I have a big problem with my code. I try to write simple code as simple framework for understanding meta programming. And I have the next problem.
doc1 = Document()
doc1.id = 20
doc2 = Document()
print (doc2.id) # show 20, but it's wrong!!! It should be 0
This is my code list
class BaseModel(type):
def __new__(cls, name, bases, attrs, **kwargs):
instance = super().__new__
parents = [b for b in bases if isinstance(b, BaseModel)]
if not parents:
return instance(cls, name, bases, attrs)
module_ = attrs.pop('__module__')
attrs_ = {'__module__': module_}
classcell_ = attrs.pop('__classcell__', None)
if classcell_ is not None:
attrs_['__classcell__'] = classcell_
for key, value in attrs.items():
if not isinstance(value, Field):
continue
print (value.__dict__)
attrs_[key] = value
instance = instance(cls, name, bases, attrs_, **kwargs)
return instance
class Model(metaclass=BaseModel):
pass
class Field:
def __init__(self, value=0):
self._value = value
def __get__(self, instance, owner):
return self._value
def __set__(self, instance, value):
self._value = value
class Document(Model):
id = Field()
What's going on? I think the main issue is new method in metaclass. Am I right? How to fix it?
Upvotes: 0
Views: 98
Reputation: 22324
What you are trying to accomplish with your metaclass is confusing, although it is not the source of the issue. Implementing unique ids does not require a metaclass, it only requires a well-crafted descriptor.
In your case, your Field
descriptor keeps track of a single value instead of existing pairs of instances and ids.
To fix this you need to keep weak references of the already taken values.
import weakref, itertools
class UniqueId:
def __init__(self):
self._values = weakref.WeakKeyDictionary()
def __get__(self, instance, owner):
if instance is None:
return self
else:
return self._values[instance]
def __set__(self, instance, value):
if value not in self._values.values():
self._values[instance] = value
else:
raise ValueError('Unique id {} already taken'.format(value))
def get_unique_id(self):
existing_ids = set(self._values.values())
return next(i for i in itertools.count() if i not in existing_ids)
class Model:
def __new__(cls, *args, **kwargs):
instance = super().__new__(cls, *args, **kwargs)
for name, attr in cls.__dict__.items():
if isinstance(attr, UniqueId):
setattr(instance, name, attr.get_unique_id())
return instance
class Document(Model):
id = UniqueId()
doc1 = Document()
print(doc1.id) # 0
doc2= Document()
print(doc2.id) # 1
doc2.id = 0 # ValueError: Unique id 0 already taken
Upvotes: 2