12 мар. 2019 г.

Не самый очевидный del

В Python 3 следующий код
exc = None
try:
    assert False
except Exception as exc:
    pass
print(exc)
даёт ошибку NameError: name 'exc' is not defined. Это, конечно, здорово, что исключение, образующее циклические ссылки, очищается при выходе из except. Не здорово, что при этом удаляется явно присвоенное значение.

8 июн. 2015 г.

Wildcard DNS

Памятка себе любимому, а то всё время что-нибудь забываю и заново ищу:

  • *.vcap.me → 127.0.0.1
  • *.42foo.com → 127.0.0.1, ::1
  • *.lvh.me → 127.0.0.1
  • *.localhost.tv → 127.0.0.1, ::1
  • *.lacolhost.com → 127.0.0.1
  • *.ip.xip.io → ip
  • *.ip.nip.io → ip

Кто не в курсе, использования доменных имён по этим шаблонам позволяет вести разработку с виртуальными хостами (поддоменами) локально и делиться ссылками с коллегами без необходимости править (просить править) /etc/hosts или настраивать dnsmasq.

17 дек. 2014 г.

Грязные технологии: миксины моделей SQLAlchemy

SQLAlchemy позволяет выносить часть определения модели в отдельный базовый класс, который будет потом «подмешиваться» к другим. Очень удобно, когда есть какой-то повторяющийся кусок в большом количестве классов моделей. Но есть один неприятный момент: все поля должны «знать», к какой модели они принадлежат, а для этого нужно копировать объект поля. С первым уровнем SQLAlchemy хорошо справляется, так что с простым Column всё путём, но ведь поля могут содержать ссылки на другие объекты (например, ForeignKey), к которым предъявляются такие же требования. И тут авторы SQLAlchemy пошли самым простым путём: если нельзя сделать автоматически на все случаи жизни, то не пусть делает пользователь вручную. В результате код миксина должен выглядеть примерно так:
class WithParent(object):
    @declared_attr
    def parent_id(cls):
        return Column(ForeignKey(Parent.id))
    @declared_attr
    def parent(cls):
        return relationship(Parent)
По сути обработанные declared_attr свойства решают ту же проблему, которую мы уже решали декоратором return_locals, а именно позволить выполнять код в определении класса несколько раз. Поэтому и решение напрашивается то же, только теперь нам нужно все свойства дополнительно завернуть в задекорированный метод. Понятно, что мы не хотим делать отдельный вызов нашей фабрики на каждый дескриптор, поэтому однажды полученный результат надо закешировать:
def declared_mixin(*args):

    def wrapper(func):
        attrs = weakref.WeakKeyDictionary()
        def create_descriptor(name):
            def get_attr(cls):
                if cls not in attrs:
                    # Call func only once per class
                    attrs[cls] = return_locals(func)()
                return attrs[cls][name]
            get_attr.__name__ = name
            return declared_attr(get_attr)
        dict_ = {name: create_descriptor(name)
                 for name in func.func_code.co_varnames}
        dict_['__doc__'] = func.__doc__
        return type(func.__name__, args, dict_)

    if len(args)==1 and not isinstance(args[0], type):
        # Short form (without args) is used
        func = args[0]
        args = ()
        return wrapper(func)
    else:
        return wrapper
Теперь наш пример миксина выглядит гораздо приятнее:
@declared_mixin
def WithParent():
    parent_id = Column(ForeignKey(Parent.id))
    parent = relationship(Parent)
В комментариях к прошлому посту Андрей Светлов резонно заметил, что хак слишком грязен для столь небольшого эффекта. В ситуации же с миксином полученный эффект уже больше: если в исходном варианте на одну смысловую строчку кода приходилось две строчки шума, то здесь мы от шума полностью избавились. И дело даже не в том, что строк стало меньше, зашумлённый код гораздо сложнее читать. Вопрос поиска менее грязных путей получения нужного результата остаётся открытым.