Okay, I'll let myself get sucked into responding ONE TIME, but only
because you gave me such a nice API to work with :)
On 10/18/2021 9:11 PM, Piotr Waszkiewicz wrote:
> class User(DBModel):
> phone: str | None
>
> class Publisher(DBModel):
> owner: ForeignKey[User] | None
>
> class Book(DBModel)
> publisher: ForeignKey[Publisher] | None
Imagine wanting to get the phone number of the person that published a
certain book from the database.
In this situation, with maybe-dot operator I can write:
> phone_number = book.publisher?.owner?.phone
Consider today, you wrote this as "book.publisher.owner.phone". You
would potentially get AttributeError, from any one of the elements - no
way to tell which, and no way to react.
Generally, AttributeError indicates that you've provided a value to an
API which doesn't fit its pattern. In other words, it's an error about
the *type* rather than the value.
But in this case, the (semantic, not implementation) *type* is known and
correct - it's a publisher! It just happens that the API designed it
such that when the *value* is unknown, the *type* no longer matches.
This is PRECISELY the kind of (IMHO, bad) API design that None-aware
operators will encourage.
Consider an alternative:
class ForeignKey:
...
def __bool__(self):
return not self.is_dbnull
def value(self):
if self.is_dbnull:
return self.Type.empty() # that is, DBModel.empty()
return self._value
class DBModel:
@classmethod
def empty(cls):
return cls(__secret_is_empty_flag=True)
def __bool__(self):
return not self._is_empty
def __getattr__(self, key):
if not self:
t = self._get_model_type(key)
return t.empty() if isinstance(t, DBModel) else None
...
class User(DBModel):
phone: str | None
class Publisher(DBModel):
owner: ForeignKey[User]
class Book(DBModel)
publisher: ForeignKey[Publisher]
Okay, so as the API implementer, I've had to do a tonne more work.
That's fine - *that's my job*. The user hasn't had to stick "| None"
everywhere (and when we eventually get around to allowing named
arguments in indexing then they could use "ForeignKey[User,
non_nullable=True]", but I guess for now that would be some subclass of
ForeignKey).
But now here's the example again:
> book.publisher.owner.phone
If there is no publisher, it'll return None. If there is no owner, it'll
return None. If the owner has no phone number, it'll return None.
BUT, if you misspell "owner", it will raise AttributeError, because you
referenced something that is not part of the *type*. And that error will
be raised EVERY time, not just in the cases where 'publisher' is
non-null. It takes away the random value-based errors we've come to love
from poorly coded web sites and makes them reliably based on the value's
type (and doesn't even require a type checker ;) ).
Additionally, if you want to explicitly check whether a FK is null, you
can do everything with regular checks:
if book.publisher.owner:
# we know the owner!
else:
# we don't know
# Get all owner names - including where the name is None - but only if
# Mrs. None actually published a book (and not just because we don't
# know a book's publisher or a publisher's owner)
owners = {book.id: book.publisher.owner.name
for book in all_books
if book.publisher.owner}
# Update a null FK with a lazy lookup
book.publisher = book.publisher or publishers.get(...)
You can't do anything useful with a native None here besides test for
it, and there are better ways to do that test. So None is not a useful
value compared to a rich DBModel subclass that *knows* it is empty.
---
So to summarise my core concern - allowing an API designer to "just use
None" is a cop out, and it lets people write lazy/bad APIs rather than
coming up with good ones.
Cheers,
Steve
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at
https://mail.python.org/archives/list/python-dev@python.org/message/BRTRKGY6RLTHZJQ2US4LO7DYLSGXQ5GM/
Code of Conduct: http://python.org/psf/codeofconduct/