if-else)if and binaryelse)not)is andisnot)Further consideration of this PEP has been deferred until Python 3.8 at theearliest.
Inspired byPEP 335,PEP 505,PEP 531, and the related discussions, this PEPproposes the definition of a new circuit breaking protocol (using themethod names__then__ and__else__) that provides a common underlyingsemantic foundation for:
LHSifCONDelseRHSLHSandRHSLHSorRHSTaking advantage of the new protocol, it further proposes that the definitionof conditional expressions be revised to also permit the use ofif andelse respectively as right-associative and left-associative generalpurpose short-circuiting operators:
LHSifRHSLHSelseRHSIn order to make logical inversion (notEXPR) consistent with the abovechanges, it also proposes the introduction of a new logical inversion protocol(using the method name__not__).
To force short-circuiting of a circuit breaker without having to evaluatethe expression creating it twice, a newoperator.short_circuit(obj)helper function will be added to the operator module.
Finally, a new standardtypes.CircuitBreaker type is proposed to decouplean object’s truth value (as used to determine control flow) from the valueit returns from short-circuited circuit breaking expressions, with thefollowing factory functions added to the operator module to representparticularly common switching idioms:
bool(obj):operator.true(obj)notbool(obj):operator.false(obj)objisvalue:operator.is_sentinel(obj,value)objisnotvalue:operator.is_not_sentinel(obj,value)This PEP builds on an extended history of work in other proposals. Some ofthe key proposals are discussed below.
This PEP is a direct successor toPEP 531, replacing the existence checkingprotocol and the new?then and?else syntactic operators defined therewith the new circuit breaking protocol and adjustments to conditionalexpressions and thenot operator.
This PEP complements the None-aware operator proposals inPEP 505, by offeringan underlying protocol-driven semantic framework that explains theirshort-circuiting behaviour as highly optimised syntactic sugar for particularuses of conditional expressions.
Given the changes proposed by this PEP:
LHS??RHS would roughly beis_not_sentinel(LHS,None)elseRHSEXPR?.attr would roughly beEXPR.attrifis_not_sentinel(EXPR,None)EXPR?[key] would roughly beEXPR[key]ifis_not_sentinel(EXPR,None)In all three cases, the dedicated syntactic form would be optimised to avoidactually creating the circuit breaker instance and instead implement theunderlying control flow directly. In the latter two cases, the syntactic formwould also avoid evaluatingEXPR twice.
This means that while the None-aware operators would remain highly specialisedand specific to None, other sentinel values would still be usable through themore general protocol-driven proposal in this PEP.
PEP 335 proposed the ability to overload the short-circuitingand andor operators directly, with the ability to overload the semantics ofcomparison chaining being one of the consequences of that change. Theproposal in an earlier version of this PEP to instead handle the element-wisecomparison use case by changing the semantic definition of comparison chainingis drawn directly from Guido’s rejection ofPEP 335[1].
However, initial feedback on this PEP indicated that the number of differentproposals that it covered made it difficult to read, so that part of theproposal has been separated out asPEP 535.
As noted above,PEP 535 is a proposal to build on the circuit breaking protocoldefined in this PEP in order to expand the rich comparison support introducedinPEP 207 to also handle comparison chaining operations likeLEFT_BOUND<VALUE<RIGHT_BOUND.
if-else)Conditional expressions (LHSifCONDelseRHS) are currently interpretedas an expression level equivalent to:
ifCOND:_expr_result=LHSelse:_expr_result=RHS
This PEP proposes changing that expansion to allow the checked condition toimplement a new “circuit breaking” protocol that allows it to see, andpotentially alter, the result of either or both branches of the expression:
_cb=COND_type_cb=type(cb)if_cb:_expr_result=LHSifhasattr(_type_cb,"__then__"):_expr_result=_type_cb.__then__(_cb,_expr_result)else:_expr_result=RHSifhasattr(_type_cb,"__else__"):_expr_result=_type_cb.__else__(_cb,_expr_result)
As shown, interpreter implementations would be required to access only theprotocol method needed for the branch of the conditional expression that isactually executed. Consistent with other protocol methods, the special methodswould be looked up via the circuit breaker’s type, rather than directly on theinstance.
if and binaryelse)The proposed name of the protocol doesn’t come from the proposed changes tothe semantics of conditional expressions. Rather, it comes from the proposedaddition ofif andelse as general purpose protocol drivenshort-circuiting operators to complement the existingTrue andFalsebased short-circuiting operators (or andand, respectively) as wellas theNone based short-circuiting operator proposed inPEP 505 (??).
Together, these two operators would be known as the circuit breaking operators.
In order to support this usage, the definition of conditional expressions inthe language grammar would be updated to make both theif clause andtheelse clause optional:
test:else_test['if'or_test['else'test]]|lambdefelse_test:or_test['else'test]
Note that we would need to avoid the apparent simplification toelse_test('if'else_test)* in order to make it easier for compilerimplementations to correctly preserve the semantics of normal conditionalexpressions.
The definition of thetest_nocond node in the grammar (which deliberatelyexcludes conditional expressions) would remain unchanged, so the circuitbreaking operators would require parentheses when used in theifclause of comprehensions and generator expressions just as conditionalexpressions themselves do.
This grammar definition means precedence/associativity in the otherwiseambiguous case ofexpr1ifcondelseexpr2elseexpr3 resolves as(expr1ifcondelseexpr2)elseepxr3. However, a guideline will also beadded toPEP 8 to say “don’t do that”, as such a construct will be inherentlyconfusing for readers, regardless of how the interpreter executes it.
The right-associative circuit breaking operator (LHSifRHS) would thenbe expanded as follows:
_cb=RHS_expr_result=LHSif_cbelse_cb
While the left-associative circuit breaking operator (LHSelseRHS) wouldbe expanded as:
_cb=LHS_expr_result=_cbif_cbelseRHS
The key point to note in both cases is that when the circuit breakingexpression short-circuits, the condition expression is used as the result ofthe expressionunless the condition is a circuit breaker. In the lattercase, the appropriate circuit breaker protocol method is called as usual, butthe circuit breaker itself is supplied as the method argument.
This allows circuit breakers to reliably detect short-circuiting by checkingfor cases when the argument passed in as the candidate expression result isself.
not)Any circuit breaker definition will have a logical inverse that is still acircuit breaker, but inverts the answer as to when to short circuit theexpression evaluation. For example, theoperator.true andoperator.false circuit breakers proposed in this PEP are each other’slogical inverse.
A new protocol method,__not__(self), will be introduced to permit circuitbreakers and other types to overridenot expressions to return theirlogical inverse rather than a coerced boolean result.
To preserve the semantics of existing language optimisations (such aseliminating double negations directly in a boolean context as redundant),__not__ implementations will be required to respect the followinginvariant:
assertnotbool(obj)==bool(notobj)
However, symmetric circuit breakers (those that implement all of__bool__,__not__,__then__ and__else__) would only be expected to respectthe full semantics of boolean logic when all circuit breakers involved in theexpression are using a consistent definition of “truth”. This is coveredfurther inRespecting De Morgan’s Laws.
Invocation of a circuit breaker’s short-circuiting behaviour can be forced byusing it as all three operands in a conditional expression:
objifobjelseobj
Or, equivalently, as both operands in a circuit breaking expression:
objifobjobjelseobj
Rather than requiring the using of any of these patterns, this PEP proposesto add a dedicated function to theoperator to explicitly short-circuita circuit breaker, while passing other objects through unmodified:
defshort_circuit(obj)"""Replace circuit breakers with their short-circuited result Passes other input values through unmodified. """returnobjifobjelseobj
is andisnot)In the absence of any standard circuit breakers, the proposedif andelse operators would largely just be unusual spellings of the existingand andor logical operators.
However, this PEP further proposes to provide a new general purposetypes.CircuitBreaker type that implements the appropriate shortcircuiting logic, as well as factory functions in the operator modulethat correspond to theis andisnot operators.
These would be defined in such a way that the following expressions produceVALUE rather thanFalse when the conditional check fails:
EXPRifis_sentinel(VALUE,SENTINEL)EXPRifis_not_sentinel(VALUE,SENTINEL)
And similarly, these would produceVALUE rather thanTrue when theconditional check succeeds:
is_sentinel(VALUE,SENTINEL)elseEXPRis_not_sentinel(VALUE,SENTINEL)elseEXPR
In effect, these comparisons would be defined such that the leadingVALUEif and trailingelseVALUE clauses can be omitted as implied inexpressions of the following forms:
# To handle "if" expressions, " else VALUE" is implied when omittedEXPRifis_sentinel(VALUE,SENTINEL)elseVALUEEXPRifis_not_sentinel(VALUE,SENTINEL)elseVALUE# To handle "else" expressions, "VALUE if " is implied when omittedVALUEifis_sentinel(VALUE,SENTINEL)elseEXPRVALUEifis_not_sentinel(VALUE,SENTINEL)elseEXPR
The proposedtypes.CircuitBreaker type would represent this behaviourprogrammatically as follows:
classCircuitBreaker:"""Simple circuit breaker type"""def__init__(self,value,bool_value):self.value=valueself.bool_value=bool(bool_value)def__bool__(self):returnself.bool_valuedef__not__(self):returnCircuitBreaker(self.value,notself.bool_value)def__then__(self,result):ifresultisself:returnself.valuereturnresultdef__else__(self,result):ifresultisself:returnself.valuereturnresult
The key characteristic of these circuit breakers is that they areephemeral:when they are told that short circuiting has taken place (by receiving areference to themselves as the candidate expression result), they return theoriginal value, rather than the circuit breaking wrapper.
The short-circuiting detection is defined such that the wrapper will alwaysbe removed if you explicitly pass the same circuit breaker instance to bothsides of a circuit breaking operator or use one as all three operands in aconditional expression:
breaker=types.CircuitBreaker(foo,fooisNone)assertoperator.short_circuit(breaker)isfooassert(breakerifbreaker)isfooassert(breakerelsebreaker)isfooassert(breakerifbreakerelsebreaker)isfoobreaker=types.CircuitBreaker(foo,fooisnotNone)assertoperator.short_circuit(breaker)isfooassert(breakerifbreaker)isfooassert(breakerelsebreaker)isfooassert(breakerifbreakerelsebreaker)isfoo
The factory functions in theoperator module would then make itstraightforward to create circuit breakers that correspond to identitychecks using theis andisnot operators:
defis_sentinel(value,sentinel):"""Returns a circuit breaker switching on 'value is sentinel'"""returntypes.CircuitBreaker(value,valueissentinel)defis_not_sentinel(value,sentinel):"""Returns a circuit breaker switching on 'value is not sentinel'"""returntypes.CircuitBreaker(value,valueisnotsentinel)
Due to their short-circuiting nature, the runtime logic underlying theandandor operators has never previously been accessible through theoperator ortypes modules.
The introduction of circuit breaking operators and circuit breakers allowsthat logic to be captured in the operator module as follows:
deftrue(value):"""Returns a circuit breaker switching on 'bool(value)'"""returntypes.CircuitBreaker(value,bool(value))deffalse(value):"""Returns a circuit breaker switching on 'not bool(value)'"""returntypes.CircuitBreaker(value,notbool(value))
LHSorRHS would be effectivelytrue(LHS)elseRHSLHSandRHS would be effectivelyfalse(LHS)elseRHSNo actual change would take place in these operator definitions, the newcircuit breaking protocol and operators would just provide a way to make thecontrol flow logic programmable, rather than hardcoding the sense of the checkat development time.
Respecting the rules of boolean logic, these expressions could also beexpanded in their inverted form by using the right-associative circuitbreaking operator instead:
LHSorRHS would be effectivelyRHSiffalse(LHS)LHSandRHS would be effectivelyRHSiftrue(LHS)If both this PEP andPEP 505’s None-aware operators were accepted, then theproposedis_sentinel andis_not_sentinel circuit breaker factorieswould be used to encapsulate the notion of “None checking”: seeing if a valueisNone and either falling back to an alternative value (an operation knownas “None-coalescing”) or passing it through as the result of the overallexpression (an operation known as “None-severing” or “None-propagating”).
Given these circuit breakers,LHS??RHS would be roughly equivalent toboth of the following:
is_not_sentinel(LHS,None)elseRHSRHSifis_sentinel(LHS,None)Due to the way they inject control flow into attribute lookup and subscriptingoperations, None-aware attribute access and None-aware subscripting can’t beexpressed directly in terms of the circuit breaking operators, but they canstill be defined in terms of the underlying circuit breaking protocol.
In those terms,EXPR?.ATTR[KEY].SUBATTR() would be semanticallyequivalent to:
_lookup_base=EXPR_circuit_breaker=is_not_sentinel(_lookup_base,None)_expr_result=_lookup_base.ATTR[KEY].SUBATTR()if_circuit_breaker
Similarly,EXPR?[KEY].ATTR.SUBATTR() would be semantically equivalentto:
_lookup_base=EXPR_circuit_breaker=is_not_sentinel(_lookup_base,None)_expr_result=_lookup_base[KEY].ATTR.SUBATTR()if_circuit_breaker
The actual implementations of the None-aware operators would presumably beoptimised to skip actually creating the circuit breaker instance, but theabove expansions would still provide an accurate description of the observablebehaviour of the operators at runtime.
Refer toPEP 535 for a detailed discussion of this possible use case.
No changes are proposed to if statements, while statements, comprehensions,or generator expressions, as the boolean clauses they contain are usedentirely for control flow purposes and never return a result as such.
However, it’s worth noting that while such proposals are outside the scope ofthis PEP, the circuit breaking protocol defined here would already besufficient to support constructs like:
defis_not_none(obj):returnis_sentinel(obj,None)whileis_not_none(dynamic_query())asresult:...# Code using result
and:
ifis_not_none(re.search(pattern,text))asmatch:...# Code using match
This could be done by assigning the result ofoperator.short_circuit(CONDITION) to the name given in theas clause,rather than assigningCONDITION to the given name directly.
The following additions toPEP 8 are proposed in relation to the new featuresintroduced by this PEP:
if-else) and the standalonecircuit breaking operators (if andelse) in a single expression -use one or the other depending on the situation, but not both.if-else) and the standalonecircuit breaking operators (if andelse) as part ofifconditions inif statements and the filter clauses of comprehensionsand generator expressions.Similar toPEP 335, early drafts of this PEP focused on making the existingand andor operators less rigid in their interpretation, rather thanproposing new operators. However, this proved to be problematic for a few keyreasons:
and andor operators have a long established and stable meaning,so readers would inevitably be surprised if their meaning now becamedependent on the type of the left operand. Even new users would be confusedby this change due to 25+ years of teaching material that assumes thecurrent well-known semantics for these operatorsand andor when defining runtime andcompile time optimisations, which would all need to be reviewed andpotentially discarded if the semantics of those operations changedProposing short-circuiting binary variants of the existingif-else ternaryoperator instead resolves all of those issues:
and andor remain entirely unchangednot operator do change, the invariantrequired of__not__ implementations means that existing expressionoptimisations in boolean contexts will remain valid.__else__ is the short-circuiting outcome forif expressions due tothe absence of a trailingelse clause__then__ is the short-circuiting outcome forelse expressions due tothe absence of a leadingif clause (this connection would be even clearerif the method name was__if__, but that would be ambiguous given theother uses of theif keyword that won’t invoke the circuit breakingprotocol)The names “circuit breaking operator”, “circuit breaking protocol” and“circuit breaker” are all inspired by the phrase “short circuiting operator”:the general language design term for operators that only conditionallyevaluate their right operand.
The electrical analogy is that circuit breakers in Python detect and handleshort circuits in expressions before they trigger any exceptions similar to theway that circuit breakers detect and handle short circuits in electricalsystems before they damage any equipment or harm any humans.
The Python level analogy is that just as abreak statement lets youterminate a loop before it reaches its natural conclusion, a circuit breakingexpression lets you terminate evaluation of the expression and produce a resultimmediately.
Using existing keywords has the benefit of allowing the new operators tobe introduced without a__future__ statement.
if andelse are semantically appropriate for the proposed new protocol,and the only additional syntactic ambiguity introduced arises when the newoperators are combined with the explicitif-else conditional expressionsyntax.
The PEP handles that ambiguity by explicitly specifying how it should behandled by interpreter implementers, but proposing to point out inPEP 8that even though interpreters will understand it, human readers probablywon’t, and hence it won’t be a good idea to use both conditional expressionsand the circuit breaking operators in a single expression.
Naming the__else__ method was straightforward, as reusing the operatorkeyword name results in a special method name that is both obvious andunambiguous.
Naming the__then__ method was less straightforward, as there was anotherpossible option in using the keyword-based name__if__.
The problem with__if__ is that there would continue to be many caseswhere theif keyword appeared, with an expression to its immediate right,but the__if__ special method would not be invoked. Instead, thebool() builtin and its underlying special methods (__bool__,__len__) would be invoked, while__if__ had no effect.
With the boolean protocol already playing a part in conditional expressions andthe new circuit breaking protocol, the less ambiguous name__then__ waschosen based on the terminology commonly used in computer science andprogramming language design to describe the first clause of anifstatement.
if right-associativeThe precedent set by conditional expressions means that a binaryshort-circuitingif expression must necessarily have the condition on theright as a matter of consistency.
With the right operand always being evaluated first, and the left operand notbeing evaluated at all if the right operand is true in a boolean context,the natural outcome is a right-associative operator.
When used solely with the left-associative circuit breaking operator,explicit circuit breaker names for unary checks read well if they start withthe prepositionif_:
operator.if_true(LHS)elseRHSoperator.if_false(LHS)elseRHS
However, incorporating theif_ doesn’t read as well when performinglogical inversion:
notoperator.if_true(LHS)elseRHSnotoperator.if_false(LHS)elseRHS
Or when using the right-associative circuit breaking operator:
LHSifoperator.if_true(RHS)LHSifoperator.if_false(RHS)
Or when naming a binary comparison operation:
operator.if_is_sentinel(VALUE,SENTINEL)elseEXPRoperator.if_is_not_sentinel(VALUE,SENTINEL)elseEXPR
By contrast, omitting the preposition from the circuit breaker name gives aresult that reads reasonably well in all forms for unary checks:
operator.true(LHS)elseRHS# Preceding "LHS if " impliedoperator.false(LHS)elseRHS# Preceding "LHS if " impliednotoperator.true(LHS)elseRHS# Preceding "LHS if " impliednotoperator.false(LHS)elseRHS# Preceding "LHS if " impliedLHSifoperator.true(RHS)# Trailing " else RHS" impliedLHSifoperator.false(RHS)# Trailing " else RHS" impliedLHSifnotoperator.true(RHS)# Trailing " else RHS" impliedLHSifnotoperator.false(RHS)# Trailing " else RHS" implied
And also reads well for binary checks:
operator.is_sentinel(VALUE,SENTINEL)elseEXPRoperator.is_not_sentinel(VALUE,SENTINEL)elseEXPREXPRifoperator.is_sentinel(VALUE,SENTINEL)EXPRifoperator.is_not_sentinel(VALUE,SENTINEL)
This PEP has been designed specifically to address the risks and concernsraised when discussing PEPs 335, 505 and 531.
and andoroperatorsandandor rather than focusing solely and inflexibly on identity checkingagainstNonenot unary operator and theis andisnotbinary comparison operators are defined in such a way that control flowoptimisations based on the existing semantics remain validOne consequence of this approach is that this PEPon its own doesn’t producemuch in the way of direct benefits to end users aside from making it possibleto omit some commonNoneif prefixes andelseNone suffixes fromparticular forms of conditional expression.
Instead, what it mainly provides is a common foundation that would allow theNone-aware operator proposals inPEP 505 and the rich comparison chainingproposal inPEP 535 to be pursued atop a common underlying semantic frameworkthat would also be shared with conditional expressions and the existingandandor operators.
The following diagram illustrates the core concepts behind the circuitbreaking protocol (although it glosses over the technical detail of lookingup the special methods via the type rather than the instance):
We will work through the following expression:
>>>defis_not_none(obj):...returnoperator.is_not_sentinel(obj,None)>>>xifis_not_none(data.get("key"))elsey
is_not_none is a helper function that invokes the proposedoperator.is_not_sentineltypes.CircuitBreaker factory withNone asthe sentinel value.data is a container (such as a builtindictinstance) that returnsNone when theget() method is called with anunknown key.
We can rewrite the example to give a name to the circuit breaker instance:
>>>maybe_value=is_not_none(data.get("key"))>>>xifmaybe_valueelsey
Here themaybe_value circuit breaker instance corresponds tobreakerin the diagram.
The ternary condition is evaluated by callingbool(maybe_value), which isthe same as Python’s existing behavior. The change in behavior is that insteadof directly returning one of the operandsx ory, the circuit breakingprotocol passes the relevant operand to the circuit breaker used in thecondition.
Ifbool(maybe_value) evaluates toTrue (i.e. the requestedkey exists and its value is notNone) then the interpreter callstype(maybe_value).__then__(maybe_value,x). Otherwise, it callstype(maybe_value).__else__(maybe_value,y).
The protocol also applies to the newif andelse binary operators,but in these cases, the interpreter needs a way to indicate the missing thirdoperand. It does this by re-using the circuit breaker itself in that role.
Consider these two expressions:
>>>xifdata.get("key")isNone>>>xifoperator.is_sentinel(data.get("key"),None)
The first form of this expression returnsx ifdata.get("key")isNone,but otherwise returnsFalse, which almost certainly isn’t what we want.
By contrast, the second form of this expression still returnsx ifdata.get("key")isNone, but otherwise returnsdata.get("key"), whichis significantly more useful behaviour.
We can understand this behavior by rewriting it as a ternary expression withan explicitly named circuit breaker instance:
>>>maybe_value=operator.is_sentinel(data.get("key"),None)>>>xifmaybe_valueelsemaybe_value
Ifbool(maybe_value) isTrue (i.e.data.get("key") isNone),then the interpreter callstype(maybe_value).__then__(maybe_value,x). Theimplementation oftypes.CircuitBreaker.__then__ doesn’t see anything thatindicates short-circuiting has taken place, and hence returnsx.
By contrast, ifbool(maybe_value) isFalse (i.e.data.get("key")isnotNone), the interpreter callstype(maybe_value).__else__(maybe_value,maybe_value). The implementation oftypes.CircuitBreaker.__else__ detects that the instance method has receiveditself as its argument and returns the wrapped value (i.e.data.get("key"))rather than the circuit breaker.
The same logic applies toelse, only reversed:
>>>is_not_none(data.get("key"))elsey
This expression returnsdata.get("key") if it is notNone, otherwise itevaluates and returnsy. To understand the mechanics, we rewrite theexpression as follows:
>>>maybe_value=is_not_none(data.get("key"))>>>maybe_valueifmaybe_valueelsey
Ifbool(maybe_value) isTrue, then the expression short-circuits andthe interpreter callstype(maybe_value).__else__(maybe_value,maybe_value).The implementation oftypes.CircuitBreaker.__then__ detects that theinstance method has received itself as its argument and returns the wrappedvalue (i.e.data.get("key")) rather than the circuit breaker.
Ifbool(maybe_value) isTrue, the interpreter callstype(maybe_value).__else__(maybe_value,y). The implementation oftypes.CircuitBreaker.__else__ doesn’t see anything that indicatesshort-circuiting has taken place, and hence returnsy.
Similar toand andor, the binary short-circuiting operators willpermit multiple ways of writing essentially the same expression. Thisseeming redundancy is unfortunately an implied consequence of defining theprotocol as a full boolean algebra, as boolean algebras respect a pair ofproperties known as “De Morgan’s Laws”: the ability to express the resultsofand andor operations in terms of each other and a suitablecombination ofnot operations.
Forand andor in Python, these invariants can be described as follows:
assertbool(AandB)==bool(not(notAornotB))assertbool(AorB)==bool(not(notAandnotB))
That is, if you take one of the operators, invert both operands, switch to theother operator, and then invert the overall result, you’ll get the sameanswer (in a boolean sense) as you did from the original operator. (This mayseem redundant, but in many situations it actually lets you eliminate doublenegatives and find tautologically true or false subexpressions, thus reducingthe overall expression size).
For circuit breakers, defining a suitable invariant is complicated by thefact that they’re often going to be designed to eliminate themselves from theexpression result when they’re short-circuited, which is an inherentlyasymmetric behaviour. Accordingly, that inherent asymmetry needs to beaccounted for when mapping De Morgan’s Laws to the expected behaviour ofsymmetric circuit breakers.
One way this complication can be addressed is to wrap the operand that wouldotherwise short-circuit inoperator.true, ensuring that whenbool isapplied to the overall result, it uses the same definition of truth that wasused to decide which branch to evaluate, rather than applyingbool directlyto the circuit breaker’s input value.
Specifically, for the new short-circuiting operators, the following propertieswould be reasonably expected to hold for any well-behaved symmetric circuitbreaker that implements both__bool__ and__not__:
assertbool(Biftrue(A))==bool(not(true(notA)elsenotB))assertbool(true(A)elseB)==bool(not(notBiftrue(notA)))
Note the order of operations on the right hand side (applyingtrueafter inverting the input circuit breaker) - this ensures that anassertion is actually being made abouttype(A).__not__, rather thanmerely being about the behaviour oftype(true(A)).__not__.
At the very least,types.CircuitBreaker instances would respect thislogic, allowing existing boolean expression optimisations (like doublenegative elimination) to continue to be applied.
Unlike PEPs 505 and 531, the proposal in this PEP readily handles customsentinel objects:
_MISSING=object()# Using the sentinel to check whether or not an argument was supplieddefmy_func(arg=_MISSING):arg=make_default()ifis_sentinel(arg,_MISSING)# "else arg" implied
A never-posted draft of this PEP explored the idea of special casing theis andisnot binary operators such that they were automaticallytreated as circuit breakers when used in the context of a circuit breakingexpression. Unfortunately, it turned out that this approach necessarilyresulted in one of two highly undesirable outcomes:
bool totypes.CircuitBreaker, potentially creating a backwards compatibilityproblem (especially when working with extension module APIs thatspecifically look for a builtin boolean value withPyBool_Check ratherthan passing the supplied value throughPyObject_IsTrue or usingthep (predicate) format in one of the argument parsing functions)Neither of those possible outcomes seems warranted by the proposal in this PEP,so it reverted to the current design where circuit breaker instances must becreated explicitly via API calls, and are never produced implicitly.
As withPEP 505, actual implementation has been deferred pending in-principleinterest in the idea of making these changes.
…TBD…
Thanks go to Steven D’Aprano for his detailed critique[2] of the initialdraft of this PEP that inspired many of the changes in the second draft, aswell as to all of the other participants in that discussion thread[3].
This document has been placed in the public domain under the terms of theCC0 1.0 license:https://creativecommons.org/publicdomain/zero/1.0/
Source:https://github.com/python/peps/blob/main/peps/pep-0532.rst
Last modified:2025-02-01 08:59:27 GMT