Skip to content

pranav4156/Python-datatypes-from-scratch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

🐍 Custom Datatypes in Python — OOP & Magic Methods

A hands-on project exploring how to build your own fully-featured datatypes in Python from scratch, using Object-Oriented Programming (OOP) and magic (dunder) methods to make custom types behave exactly like built-in Python types.


📌 What This Project Is About

Python lets you define how your objects behave with operators like +, ==, in, and built-in functions like abs(), str(), len(). This project has two modules, each going deeper than the last:

Module Concept Level What It Teaches
fraction.py Beginner → Intermediate A single class, arithmetic & comparison operators, auto-simplification
geometry2d.py Intermediate → Advanced Multiple classes, composition, @property, @classmethod, __contains__, __matmul__

📁 Project Structure

Fraction-Custom_Datatype/
│
├── fraction.py      # A custom Fraction number type
├── geometry2d.py    # 2-D geometry types: Point, Vector, Line, Circle, Rectangle, Triangle
└── README.md        # This file

📦 Module 1 — fraction.py

A complete Fraction number type that behaves like a built-in Python number. Auto-simplifies on creation (Fraction(10, 20)1/2) and keeps the sign always on the numerator.

Features

Category Operations
Arithmetic +, -, *, /, **
Comparison ==, !=, <, <=, >, >=
Unary -f, abs(f)
Conversion float(f), int(f), str(f), repr(f), bool(f)
Reverse ops 2 + Fraction(1,3) — works with int on the left
Utilities .reciprocal(), .to_mixed()

Quick Example

from fraction import Fraction

f1 = Fraction(3, 4)
f2 = Fraction(1, 2)

print(f1 + f2)                       # 5/4
print(f1 * f2)                       # 3/8
print(f1 > f2)                       # True
print(2 + f1)                        # 11/4  ← reverse operation
print(Fraction(7, 3).to_mixed())     # 2 and 1/3
print(Fraction(10, 20))              # 1/2   ← auto-simplified

Magic Methods Used

Method Triggered By What It Does
__init__ Fraction(3, 4) Validates, normalises sign, auto-simplifies with GCD
__str__ print(f) Human-readable: "3/4"
__repr__ repr(f) Dev-friendly: "Fraction(3, 4)"
__add__ f1 + f2 Cross-multiply and add
__sub__ f1 - f2 Cross-multiply and subtract
__mul__ f1 * f2 Numerator × numerator, denominator × denominator
__truediv__ f1 / f2 Multiply by reciprocal
__pow__ f1 ** n Raise num and den to the power; flip for negative n
__radd__ 2 + f1 Reverse add so int-first operations work
__rsub__ 2 - f1 Reverse subtract
__rmul__ 3 * f1 Reverse multiply
__rtruediv__ 3 / f1 Reverse divide
__neg__ -f1 Negate numerator
__abs__ abs(f1) Absolute value of numerator
__eq__ f1 == f2 Cross-multiplication equality check
__lt__ f1 < f2 Cross-multiplication comparison
__float__ float(f1) num / den as float
__int__ int(f1) Truncated integer division
__bool__ if f1: False only when numerator is 0

📦 Module 2 — geometry2d.py

Five interacting 2-D geometry datatypes that compose together, showing how real-world complex custom types are built.

Classes at a Glance

Point ──────► used by ──────► Vector  (interaction via operators)
  │                            Line   (start, end)
  │                            Circle (center)
  │                            Rectangle (origin)
  └──────────────────────────► Triangle (p1, p2, p3)

Key concept: Line, Circle, Rectangle, and Triangle are all built on top of Point. This is composition — complex types made from simpler ones.


Point — A location in 2-D space

from geometry2d import Point, Vector

p1 = Point(3, 4)
p2 = Point(7, 1)

print(p1.distance_to(p2))          # 5.0
print(p1.midpoint(p2))             # (5.0, 2.5)
print(p1.reflect_x())              # (3, -4)
print(p1.rotate(90))               # (-4, 3)  ← 90° around origin
print(p1 + Vector(1, 0))           # (4, 4)   ← Point + Vector = new Point
print(p2 - p1)                     # (4, -3)  ← Point - Point = Vector
Magic Method Behaviour
__add__(Vector) Point + Vector → translated Point
__sub__(Point) Point - Point → displacement Vector
__sub__(Vector) Point - Vector → reverse-translated Point
__eq__ Uses math.isclose() for float-safe equality

Vector — Direction and magnitude

from geometry2d import Vector

v1 = Vector(3, 4)
v2 = Vector(1, 0)

print(v1.magnitude)                # 5.0
print(v1.normalized())             # (0.6, 0.8)
print(v1 * 3)                      # (9, 12)   ← scalar multiply
print(3 * v1)                      # (9, 12)   ← reverse scalar multiply
print(v1 @ v2)                     # 3.0       ← dot product via @
print(v1.angle_between(v2))        # 53.13°
print(v1.cross(v2))                # -4
Magic Method Behaviour
__matmul__ v1 @ v2 → dot product using the @ operator
__abs__ abs(v) → magnitude
__neg__ -v → reversed direction
__rmul__ 3 * v → scalar-first multiply
magnitude @property — computed on access, not stored

Line — A segment between two Points

line = Line(Point(0, 0), Point(3, 4))
print(line.length)     # 5.0
print(line.midpoint)   # (1.5, 2.0)
print(line.slope)      # 1.333...
print(line.direction()) # (3, 4) ← a Vector
print(len(line))       # 5  ← __len__ truncates to int

Circle — Center Point + radius

c = Circle(Point(0, 0), 5)
print(round(c.area, 2))          # 78.54
print(round(c.circumference, 2)) # 31.42
print(Point(3, 4) in c)         # True  ← __contains__
print(c.intersects(Circle(Point(8,0), 4)))  # True
print(c.scale(2))                # Circle(center=(0, 0), r=10)

Rectangle — Corner Point + width + height

rect = Rectangle(Point(0, 0), 6, 4)
print(rect.area)           # 24
print(rect.center)         # (3.0, 2.0)
print(rect.is_square())    # False
print(Point(3, 2) in rect) # True  ← __contains__

# Alternate constructor
rect2 = Rectangle.from_corners(Point(0, 0), Point(6, 4))
print(rect == rect2)       # True

Triangle — Three Points

t = Triangle(Point(0, 0), Point(4, 0), Point(0, 3))
print(t.area)              # 6.0
print(t.centroid)          # (1.333..., 1.0)
print(t.classify())        # 'right'
print(t.classify_sides())  # 'scalene'
print(Point(1, 1) in t)   # True  ← __contains__ via barycentric coordinates

🧠 Advanced OOP Concepts Demonstrated

Concept Where Used What It Means
Composition Line, Circle, Rectangle, Triangle Complex types built from Point objects
@property Vector.magnitude, Circle.area, Rectangle.center Computed attributes accessed like variables, not methods
@classmethod Rectangle.from_corners() Alternate constructor — a second clean way to create an object
__contains__ Circle, Rectangle, Triangle Powers point in shape with the in keyword
__matmul__ Vector Uses the @ operator for dot product — a rare dunder method
__len__ Line Powers len(line)
Cross-type ops Point + Vector, Point - Point Same operator returns different types based on operands
Float safety All equality checks math.isclose() instead of == avoids floating-point bugs

🛡️ Error Handling

Fraction(1, 0)                                    # ValueError — zero denominator
Fraction(1.5, 3)                                  # TypeError  — non-integer inputs
Circle(Point(0,0), -5)                            # ValueError — negative radius
Line(Point(1,1), Point(1,1))                      # ValueError — identical endpoints
Triangle(Point(0,0), Point(1,0), Point(2,0))      # ValueError — collinear points
Rectangle(Point(0,0), 0, 5)                       # ValueError — zero dimension

🚀 Running the Demos

Each file has a full demo built in — just run:

python fraction.py
python geometry2d.py

No installation needed. Both files use only Python's standard math module.


📚 Recommended Learning Path

  1. Start with fraction.py — one class, clear arithmetic logic, easy to follow.
  2. Move to geometry2d.py, reading top to bottom: PointVectorLineCircleRectangleTriangle.
  3. Focus on cross-type interactions — how Point + Vector and Point - Point return different types from the same operator.
  4. Study __contains__ in Circle and Triangle — the math (distance formula, barycentric coordinates) is as interesting as the Python.
  5. Notice @property vs method — ask yourself: when should something be a property versus a regular method?

📝 License

Open for learning, experimentation, and extension. Feel free to fork and add more shapes!

About

From fractions to 2D geometry — custom Python classes that feel like native types, built using operator overloading and OOP principles.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages