8000 Create a primer section for the descriptor howto guide by rhettinger · Pull Request #22906 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

Create a primer section for the descriptor howto guide #22906

New issue 8000

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 33 commits into from
Oct 23, 2020
Merged
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4285090
Simplest example: Return a constant
rhettinger Oct 22, 2020
fc4865a
Add directory size example of a dynamic descriptor
rhettinger Oct 23, 2020
83d7240
Add the managed attributes example
rhettinger Oct 23, 2020
6e1bae3
Add stub sections
rhettinger Oct 23, 2020
5cd1b8d
Fix hardwired value
rhettinger Oct 23, 2020
e8db254
Add the set_name example.
rhettinger Oct 23, 2020
3624731
Add the data validators complete practical example.
rhettinger Oct 23, 2020
dab7c25
Minor touch-ups
rhettinger Oct 23, 2020
95e1513
Add technical section for __set_name__
rhettinger Oct 23, 2020
65a0ab3
Line wrap
rhettinger Oct 23, 2020
8e5f962
Use the @-notation where possible
rhettinger Oct 23, 2020
7f52aee
Modern style uses "cls" instead of "klass"
rhettinger Oct 23, 2020
48329d2
Fix typo
rhettinger Oct 23, 2020
1583c25
Missing colon
rhettinger Oct 23, 2020
45466fc
Note false positive in the suspcious entry extension
rhettinger Oct 23, 2020
31fb230
Note false positive in the suspcious entry extension
rhettinger Oct 23, 2020
58275e7
Note false positive in the suspcious entry extension
rhettinger Oct 23, 2020
80bfd08
Note false positive in the suspcious entry extension
rhettinger Oct 23, 2020
112a272
Fix typos. Minor grammar edits.
rhettinger Oct 23, 2020
1a899c8
Fix method name
rhettinger Oct 23, 2020
b3934e8
Fix SyntaxError and misspelled predicate name
rhettinger Oct 23, 2020
90992bf
Add references to and from the glossary
rhettinger Oct 23, 2020
2599cff
Fix markup
rhettinger Oct 23, 2020
11e790a
Update Doc/howto/descriptor.rst
rhettinger Oct 23, 2020
b0c435c
Minor comment and variable name improvements
rhettinger Oct 23, 2020
0f6df85
Simplify examples. Add closing thoughts section.
rhettinger Oct 23, 2020
1e9482e
Add more section headings
rhettinger Oct 23, 2020
512fa4b
More wordsmithing
rhettinger Oct 23, 2020
0dc5f72
Wrap long lines
rhettinger Oct 23, 2020
10b9cd4
Clarify the motivation and the caller/callee relationship
rhettinger Oct 23, 2020
12b0fa2
Beautify technical tutorial sections
rhettinger Oct 23, 2020
8ca8c77
Move comments of the the code and into the main text
rhettinger Oct 23, 2020
cc22b88
Remove outdated references to Python 2 and new-style classes
rhettinger Oct 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add the data validators complete practical example.
  • Loading branch information
rhettinger committed Oct 23, 2020
commit 36247316f2279e372bbfe9035fb3ba468879af75
107 changes: 106 additions & 1 deletion Doc/howto/descriptor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,112 @@ The two *Person* instances contain only the private names::
Complete Practical Example
^^^^^^^^^^^^^^^^^^^^^^^^^^

XXX: Add the validators example
In this example, we create a practical and powerful tool for locating notoriously hard to find data corruption bugs.

A validator is a descriptor for managed attribute access. Prior to storing any data, it verifies that the new value meets various type and range restrictions. If those restrictions aren't met, it raises an exception and prevents data corruption at its source.

This :class:`Validator` class is both an :term:`abstract base class` and a managed attribute descriptor::

from abc import ABC, abstractmethod

class Validator(ABC):

def __set_name__(self, owner, name):
self.private_name = f'_{name}'

def __get__(self, obj, objtype=None):
return getattr(obj, self.private_name)

def __set__(self, obj, value):
self.validate(value)
setattr(obj, self.private_name, value)

@abstractmethod
def validate(self, value):
pass

Custom validators need to subclass from :class:`Validator` and supply a :meth:`validate` method to test various restrictions as needed.

Here are three practical data validation utilities:

1) :class:`OneOf` verifies that a value is one of a restricted set of options.

2) :class:`Number` verifies that a value is either an :class:`int` or :class:`float`. Optionally, it verifies that a value is between a given minimum or maximum.

3) :class:`String` verifies that a value is a :class:`str`. Optionally, it validates a given minimum or maximum length. Optionally, it can test for another predicate as well.

::

class OneOf(Validator):

def __init__(self, *options):
self.options = set(options)

def validate(self, value):
if value not in self.options:
raise ValueError(f'Expected {value!r} to be one of {self.options!r}')

class Number(Validator):

def __init__(self, minvalue=None, maxvalue=None):
self.minvalue = minvalue
self.maxvalue = maxvalue

def validate(self, value):
if not isinstance(value, (int, float)):
raise TypeError(f'Expected {value!r} to be an int or float')
if self.minvalue is not None and value < self.minvalue:
raise ValueError(
f'Expected {value!r} to be at least {self.minvalue!r}'
)
if self.maxvalue is not None and value > self.maxvalue:
raise ValueError
(f'Expected {value!r} to be no more than {self.maxvalue!r}'
)

class String(Validator):

def __init__(self, minsize=None, maxsize=None, predicate=None):
self.minsize = minsize
self.maxsize = maxsize
self.predicate = predicate

def validate(self, value):
if not isinstance(value, str):
raise TypeError(f'Expected {value!r} to be an str')
if self.minsize is not None and len(value) < self.minsize:
raise ValueError(
f'Expected {value!r} to be no smaller than {self.minsize!r}'
)
if self.maxsize is not None and len(value) > self.maxsize:
raise ValueError(
f'Expected {value!r} to be no bigger than {self.maxsize!r}'
)
if self.predicate is not None and not self.predicate(value):
raise ValueError(
f'Expected {self.predicate} to be true for {value!r}'
)

Here's how the data validators can be used in a real class::

class Component:

name = String(minsize=3, maxsize=10, predicate=str.upper)
kind = OneOf('plastic', 'metal')
quantity = Number(minvalue=0)

def __init__(self, name, kind, quantity):
self.name = name
self.kind = kind
self.quantity = quantity

The descriptors invalid instances from being created::

Component('WIDGET', 'metal', 5) # Allowed.
Component('Widget', 'metal', 5) # Blocked: 'Widget' is not all uppercase
Component('WIDGET', 'metle', 5) # Blocked: 'metle' is misspelled
Component('WIDGET', 'metal', -5) # Blocked: -5 is negative
Component('WIDGET', 'metal', 'V') # Blocked: 'V' isn't a number


Technical Tutorial
Expand Down
0