The crawler subdirectory was the only active project. Moving it to the repo root simplifies paths and removes the unnecessary nesting. The vqa/ and immoweb/ directories were legacy/unused and have been removed. Updated .drone.yml, .gitignore, .claude/ docs, and skills to reflect the new flat structure.
132 lines
4.3 KiB
Markdown
132 lines
4.3 KiB
Markdown
---
|
|
name: python-parentheses-comparison-bug
|
|
description: |
|
|
Debug Python comparison bug where parentheses around a variable cause unexpected behavior.
|
|
Use when: (1) condition always evaluates to False/True unexpectedly, (2) code like
|
|
"if (mylist) == 0" never triggers, (3) length check seems to not work, (4) comparison
|
|
with list/dict returns unexpected results. Common mistake where parentheses cause the
|
|
variable itself to be compared instead of its length.
|
|
author: Claude Code
|
|
version: 1.0.0
|
|
date: 2026-01-31
|
|
---
|
|
|
|
# Python Parentheses Comparison Bug
|
|
|
|
## Problem
|
|
A subtle Python bug where unnecessary parentheses around a variable in a comparison
|
|
cause the wrong value to be compared. The expression `(mylist) == 0` compares the list
|
|
itself to 0, not its length. Since a list is never equal to an integer, this always
|
|
returns False.
|
|
|
|
## Context / Trigger Conditions
|
|
- Condition that should sometimes be True is always False (or vice versa)
|
|
- Code pattern like `if (existing_items) == 0:` or `if (result) == expected:`
|
|
- The parentheses don't cause a syntax error but change semantics
|
|
- Often appears when copying/adapting code or during refactoring
|
|
- May pass code review because it "looks" correct
|
|
|
|
## Solution
|
|
|
|
### Identify the Bug Pattern
|
|
```python
|
|
# BUG: Compares list to 0, always False
|
|
if (existing_listings) == 0:
|
|
return True
|
|
|
|
# Also wrong: compares list to integer
|
|
if (items) == 5:
|
|
do_something()
|
|
```
|
|
|
|
### Fix: Use len() for Length Comparisons
|
|
```python
|
|
# CORRECT: Compares length to 0
|
|
if len(existing_listings) == 0:
|
|
return True
|
|
|
|
# Alternative: Use truthiness for empty check
|
|
if not existing_listings:
|
|
return True
|
|
|
|
# CORRECT: Compares length to integer
|
|
if len(items) == 5:
|
|
do_something()
|
|
```
|
|
|
|
## Verification
|
|
1. Add a debug print before the condition: `print(f"list={existing_listings}, len={len(existing_listings)}")`
|
|
2. Verify the condition now evaluates correctly
|
|
3. Write a unit test that exercises both branches of the condition
|
|
|
|
## Example
|
|
|
|
### Before (Broken)
|
|
```python
|
|
class FetchListingDetailsStep:
|
|
async def needs_processing(self, listing_id: int) -> bool:
|
|
existing_listings = await self.listing_repository.get_listings(
|
|
only_ids=[listing_id]
|
|
)
|
|
# BUG: This compares the list object to 0, which is always False
|
|
# The parentheses around existing_listings are misleading
|
|
if (existing_listings) == 0:
|
|
return True
|
|
return False
|
|
```
|
|
|
|
### After (Fixed)
|
|
```python
|
|
class FetchListingDetailsStep:
|
|
async def needs_processing(self, listing_id: int) -> bool:
|
|
existing_listings = await self.listing_repository.get_listings(
|
|
only_ids=[listing_id]
|
|
)
|
|
# CORRECT: Check if list is empty using len()
|
|
if len(existing_listings) == 0:
|
|
return True
|
|
return False
|
|
```
|
|
|
|
### Even Better (Pythonic)
|
|
```python
|
|
class FetchListingDetailsStep:
|
|
async def needs_processing(self, listing_id: int) -> bool:
|
|
existing_listings = await self.listing_repository.get_listings(
|
|
only_ids=[listing_id]
|
|
)
|
|
# Most Pythonic: Use truthiness
|
|
return not existing_listings
|
|
```
|
|
|
|
## Notes
|
|
- Python's truthiness: empty collections are falsy, non-empty are truthy
|
|
- This bug is particularly insidious because:
|
|
- It's syntactically valid
|
|
- It doesn't raise an exception
|
|
- The parentheses make it look intentional
|
|
- Code review may miss it
|
|
- Linters like pylint or flake8 won't catch this specific pattern
|
|
- Type checkers like mypy may warn about comparing incompatible types
|
|
- When debugging, add print statements to verify actual vs expected values
|
|
|
|
## Prevention
|
|
- Prefer `if not mylist:` over `if len(mylist) == 0:`
|
|
- Prefer `if mylist:` over `if len(mylist) > 0:`
|
|
- Remove unnecessary parentheses around single variables
|
|
- Enable mypy's strict mode which may catch type comparison issues
|
|
- Write unit tests that exercise both branches of conditions
|
|
|
|
## Related Patterns
|
|
```python
|
|
# These are all wrong (comparing object to number):
|
|
if (mydict) == 0: # Always False
|
|
if (mylist) > 0: # TypeError in Python 3
|
|
if (mystring) == 0: # Always False
|
|
|
|
# These are correct:
|
|
if len(mydict) == 0: # True if empty
|
|
if not mydict: # True if empty (preferred)
|
|
if len(mylist) > 0: # True if non-empty
|
|
if mylist: # True if non-empty (preferred)
|
|
```
|