Найти - Пользователи
Полная версия: Вызов метода класса вместо метода экземпляра
Начало » Python для экспертов » Вызов метода класса вместо метода экземпляра
1
maxfox
Здравствуйте!

Пока не очень разобрался с метаклассами и прочими премудростями Python и вот возник такой вопрос.
Хочется реализовать что то вроде Manager в Django ORM. Т.е. есть несколько классов, унаследованных от базового. У них есть экземпляры в некотором количестве. Хочется сделать что-то вроде этого:
class Foo(BaseClass):
    #Определение класса
    pass
#Где-то в другом модуле..
from myapp.models import Foo
x = Foo.objects.get_all() # Возвращается итератор
for item in x:
    #Перебираем объекты
    pass

Т.е. идея та же, что в Django. Создаем объекты, сохраняем их в БД. Когда нужно - получаем итератор по всем нужным объектам через вызов метода базового класса (BaseClass в моем примере).
Мне пришло в голову только использование @classmethod, который будет возвращать итератор или менеджер объектов, который, в свою очередь будет возвращать итератор.
Ковырял исходники django, но своих мозгов разобраться в этом не хватает. Может кто-нибудь объяснит на пальцах, как это работает? Конкретно я не нашел:
1. Как изолируется случай вызова .objects.get_all() из экземпляра.
2. .objects возвращает QuerySet, а его методы тоже возвращают QuerySet.. Когда пользуешься этим - все удобно и логично. Но как это реализовано, я не очень понимаю.
Можно, конечно, до посинения читать исходники, но там очень много всего накручено, мне бы на пальцах..
Большое спасибо всем откликнувшимся.
FishHook
class classproperty(object):
    def __init__(self, fget):
        self.fget = fget
    def __get__(self, owner, cls):
        if owner is not None:
            raise AttributeError("objects is class only")
        return self.fget(cls)
class Field(object):
    def __init__(self, **kwargs):
        self.null = kwargs.get("null", False)
    def _set_value(self, value):
        if value is None:
            if not self.null:
                raise ValueError("Field %s IS_NULL=FALSE" % self.__class__.__name__)
            self.value = None
            return
        try:
            self.value = self.validate(value)
        except:
            raise ValueError("Invalid value")
        return self
    def validate(self, value):
        pass
class ModelMeta(type):
    def __new__(cls, name, bases, dct):
        fields = dict()
        for attr_name, value in dct.iteritems():
            if isinstance(value, Field):
                fields[attr_name] = value
        dct['_fields'] = fields
        return type.__new__(cls, name, bases, dct)
db = dict(mymodel=[
                dict(id=1, a=100, b=3),
                dict(id=2, a=100, b=None),
                dict(id=3, a=3, b=None)
    ])
class Manager(object):
    def __init__(self, model_class, **kwargs):
        self.model_class = model_class
        self.where = kwargs.get("where") or dict()
        self.db_model_name = model_class.__name__.lower()
    def get(self, **kwargs):
        data = iter(db[self.db_model_name])
        for field_name, value in kwargs.iteritems():
            data = (x for x in data if x[field_name] == value)
        return self.model_class(**list(data)[0])
    def filter(self, **kwargs):
        if not kwargs:
            raise AttributeError("Empty args")
        self.where.update(kwargs)
        return self
    def all(self):
        return Manager(self.model_class, where=self.where)
    def __str__(self):
        return str(self.execute())
    def __iter__(self):
        return self.execute()
    def execute(self):
        data = db[self.db_model_name]
        for field_name, value in self.where.iteritems():
            data = (x for x in data if x[field_name] == value)
        return (self.model_class(**item) for item in data)
class Model(object):
    __metaclass__ = ModelMeta
    def __init__(self, **kwargs):
        setattr(self, "id", kwargs.get("id"))
        for field_name, field_class in self._fields.iteritems():
            value = kwargs.get(field_name)
            setattr(self, field_name, self._fields[field_name]._set_value(value))
    @classproperty
    def objects(cls):
        return Manager(cls)
class IntegerField(Field):
    def validate(self, value):
        return int(value)
    def __str__(self):
        return str(self.value)
class MyModel(Model):
    a = IntegerField()
    b = IntegerField(null=True)
print MyModel.objects.get(a=3)
for x in MyModel.objects.all():
    print x.a, x.id
print MyModel(a=1)
maxfox
Спасибо, высплюсь - буду разбираться..

Код предельно понятен, прям спасибище вам, просто слов нет.
У меня только появился глупый вопрос.. Когда я пытался осмыслить код django, то столкнулся с проблемой: я просто не смог найти определение метода objects ни для одного из базовых классов модели. Поиск по “def objects”, как и подробный просмотр кода ничего не дал. У меня так и не хватило терпения “распутать клубок” и найти определение метода. Если использован какой-то хитрый способ реализации, то хотелось бы понять - зачем?
Понимаю, что это лишь детали реализации, в таком большом проекте как Django есть куча нюансов и т.д. Просто я часто слышу, что Django - чуть ли не образец pythonic кода, но, честно говоря, там черт ногу сломит, я несколько часов подряд вчитывался в код, толку ноль. А на разбор вашего примера у меня ушло меньше минуты. Конечно, синтетический пример и реальный проект - совсем разные вещи, но все же..
PanovSergey
maxfox
У меня только появился глупый вопрос.. Когда я пытался осмыслить код django, то столкнулся с проблемой: я просто не смог найти определение метода objects ни для одного из базовых классов модели. Поиск по “def objects”, как и подробный просмотр кода ничего не дал. У меня так и не хватило терпения “распутать клубок” и найти определение метода. Если использован какой-то хитрый способ реализации, то хотелось бы понять - зачем?

Здесь
Это

Если в двух словах то модель перед использованием обрабатывается метаклассом. Не нашли вы просто потому что код немного сложнее, вынесен в отдельный метод и использован setattr.
FishHook
maxfox
чуть ли не образец pythonic кода
Это вряд ли. Джанго - довольно старый проект, с каждым релизом фреймворк наворачивают новым функционалом, но не переписывают заново сомнительные решения для сохранения обратной совместимости. То есть объективно Джанга - набор костылей.
maxfox
2PanovSergey
Спасибо!

2FishHook
Ок. Значит оо мной все в порядке. У меня тоже сложилось впечатление, что костылей в коде несколько больше чем нужно..
Еще раз спасибо за помощь!
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB