Python does not provide truly private attributes and methods. Its __name convention only uses name mangling, meaning private members can still be accessed from outside the class.
This library introduces a new kind of class designed for genuine encapsulation. It allows you to define truly private attributes and methods that cannot be directly accessed or bypassed from outside the class, providing a stronger form of privacy than Python's built-in classes.
Installation
You can install classx via pip:
pip install classx
Operations on classes
import classx
# classx type constructor and methods must be separated as functions.
# The best practice in this case when each method and the constructor are
# separated out of one body is to name each method and the constructor
# with the name of the class for example Person and after it the name of
# method or constructor: Person_constructor, Person_some_method_name.
# ----------------- Controllers -----------------
# Private Method
def Person_name_controller(new_name: str) -> str:
if not isinstance(new_name, str):
raise TypeError("The name must be text")
new_name = new_name.strip()
if not new_name:
raise ValueError("The name must not be empty")
if not new_name.isalpha():
raise ValueError("The name must be a valid text")
return new_name
# Private Method
def Person_age_controller(new_age: int) -> int:
if not isinstance(new_age, int):
raise TypeError("Age must be an integer")
if new_age < 0 or new_age > 150:
raise ValueError("Age is irrational")
return new_age
# --------- Constructor ---------
# Always Public
def Person_constructor(name: str, age: int) -> None:
# If arguments are the same private attributes don't rewrite them
# inside the constructor body (auto declared) but if the private
# attribute is not one of the args you need to declare it inside.
# Every attribute inside the constructor is private in default
# and no way to make it public.
# Also you can write any logic inside the constructor.
Person_name_controller(name)
Person_age_controller(age)
# If no logic needed inside here just write pass not return
# it will raise a TypeError
# --------- Setters ---------
# Public Method
def Person_set_name(new_name: str) -> None:
# Here we name the argument 'new_name' not 'name' like the private
# attribute 'name' declared in the constructor because if we name it
# 'name' we confuse the compiler and it will raise a SyntaxError
# because no self here like in traditional classes self.name the
# attribute and the argument 'name' not the same but here 'name' the
# private attribute and 'name' the argument are the same in naming and
# the best practice in all cases here and traditional classes is to
# write 'new_name' / self.new_name because is a true new name to set
# from the user even if it's the same old name as value but we set it
# again that mean new.
name = Person_name_controller(new_name)
# Public Method
def Person_set_age(new_age: int) -> None:
# Here we name the argument new_age not age like the private attribute
# age declared in the constructor because if we name it age we confuse
# the compiler and it will raise a SyntaxError because no self here
# like in traditional classes self.age the attribute and the argument
# age not the same but here age the private attribute and age the
# argument are the same in naming and the best practice in all cases
# here and traditional classes is to write new_age / self.new_age
# because is a true new age to set from the user even if it's the same
# old age as value but we set it again that mean new.
age = Person_age_controller(new_age)
# ----- Getters -----
# Public Method
def Person_get_name() -> str:
return name
# Public Method
def Person_get_age() -> int:
return age
# ----- Display -----
# Public Method
def Person_display() -> None:
print(f"Your name is {name} and your age is {age}")
# All classes it will be created inside classx namespaces not here.
# Here we create a new namespace in classx using his engine and point
# to them using a pointer named ns
ns = classx.create_namespace("ns")
# Here we create the Person class inside the same namespace we point
# to them like this you can create many classes with the same name
# in different namespaces without any collision and without removing
# it from the current namespace.
Person = ns.create_class(
name="Person",
constructor=Person_constructor,
public_methods={
"set_name": Person_set_name,
"set_age": Person_set_age,
"get_name": Person_get_name,
"get_age": Person_get_age,
"display": Person_display,
},
private_methods=[
Person_name_controller,
Person_age_controller,
],
)
# Entry Point
def main() -> None:
p1 = Person("ahmed", 24) # Create a new Person object
p1.display()
print("------")
p1.set_name("mohamed") # Set a new name.
p1.set_age(23) # Set a new age.
p1.display()
print("------")
try:
p1.set_name("")
except ValueError as e0:
print("ValueError handled") # Because invalid input.
try:
p1.set_age(-7)
except ValueError as e1:
print("ValueError handled") # Because invalid input.
try:
print(p1.name)
except AttributeError as e2:
print("AttributeError handled") # Because attribute name is private.
try:
print(p1.age)
except AttributeError as e3:
print("AttributeError handled") # Because attribute age is private.
try:
p1.name = "unknown"
except AttributeError as e4:
print("AttributeError handled") # Because attribute name is private.
try:
p1.age = 22
except AttributeError as e5:
print("AttributeError handled") # Because attribute age is private.
try:
p1.set_name = None
except AttributeError as e6: # Because methods are just attributes.
print("AttributeError handled") # You can't assign to public methods
try:
p1.Person_name_controller("user")
except AttributeError as e7: # Because methods are just attributes.
print("AttributeError handled") # Because this method is private.
try:
p1.Person_age_controller(7)
except AttributeError as e8: # Because methods are just attributes.
print("AttributeError handled") # Because this method is private.
try:
p1.gender = "male"
except AttributeError as e9:
print("AttributeError handled") # You can't create attributes from outside.
if __name__ == "__main__":
main()
import classx
# Custom Error
class BankError(Exception):
pass
# Private Method
def BankAccount_owner_name_controller(name: str) -> str:
if not isinstance(name, str):
raise TypeError("The name must be text")
name = name.strip()
if not name:
raise ValueError("The name must not be empty")
if not name.isalpha():
raise ValueError("The name must be a valid text")
return name
# Private Method
def BankAccount_amount_controller(amount: int) -> int:
if not isinstance(amount, (int, float)):
raise TypeError("The amount must be a number")
if amount <= 0:
raise ValueError("The amount must be greater than 0")
return amount
# Constructor (Always Public)
def BankAccount_constructor(owner: str, balance: int) -> None:
BankAccount_owner_name_controller(owner)
BankAccount_amount_controller(balance)
if balance < 10000:
raise BankError("You need amount of 10000 or greater to open a new account")
# Public Method
def BankAccount_deposit(amount: int) -> None:
balance += BankAccount_amount_controller(amount)
print(f"Deposited {amount}. Current Balance: {balance}")
# Public Method
def BankAccount_withdraw(amount: int) -> None:
BankAccount_amount_controller(amount)
if amount > balance:
raise BankError("Not enough balance")
balance -= amount
print(f"Withdrawn {amount}. Current Balance: {balance}")
# Public Method
def BankAccount_display_balance() -> None:
print(f"Account Holder: {owner}")
print(f"Balance: {balance}")
ns = classx.create_namespace("ns")
BankAccount = ns.create_class(
name="BankAccount",
constructor=BankAccount_constructor,
public_methods={
"deposit": BankAccount_deposit,
"withdraw": BankAccount_withdraw,
"display_balance": BankAccount_display_balance,
},
private_methods=[
BankAccount_owner_name_controller,
BankAccount_amount_controller,
],
# You need to mention all used components inside methods and constructor.
components={BankError.__name__: BankError},
)
# Entry Point
def main() -> None:
account = BankAccount("mohamed", 10000)
account.display_balance()
print("------")
account.deposit(5000)
account.withdraw(3000)
print("------")
account.display_balance()
print("------")
try:
account.withdraw(50000)
except BankError as e0:
print("BankError handled")
try:
account.deposit(-10)
except ValueError as e1:
print("ValueError handled")
if __name__ == "__main__":
main()
import classx
ns = classx.create_namespace("ns")
Database = ns.create_class(
name="Database",
singleton=True, # To make a singleton class
)
def main() -> None:
db1 = Database()
db2 = Database()
print(id(db1)) # Same id as db2 id.
print(id(db2)) # Same id as db1 id.
print(db1 is db2) # True
# To check if a class singleton or not.
print(ns.is_singleton(Database)) # True
if __name__ == "__main__":
main()
import classx
ns = classx.create_namespace("ns")
User = ns.create_class(
name="User",
overwrite=True, # To make the class overwritable
)
def main() -> None:
global User
User = ns.create_class(name="User") # overwrite is False in default.
# Now you can't overwrite again directly you need to get
# the permissions from the last overwritable class version.
try:
User = ns.create_class(name="User")
except TypeError as e0:
# Because the last overwritable class version is not overwritable
print("TypeError handled")
# To check if a class is overwritable or not
print(ns.is_overwritable(User)) # False
if __name__ == "__main__":
main()
import classx
ns = classx.create_namespace("ns")
Image = ns.create_class(
name="Image",
public_constants={"format": "PNG"},
)
def main() -> None:
img = Image()
print(img.format) # read only public attribute.
if __name__ == "__main__":
main()
import classx
# In this case we don't write Circle_square or Square_square because is
# used in the two classes is common between them with neutral name and
# also you can use square directly as a normal function.
def square(value: float) -> float:
return value * value
def Circle_constructor(radius: float) -> None:
pass
def Circle_area() -> float:
return 3.14159 * square(radius)
def Square_constructor(side: float) -> None:
pass
def Square_area() -> float:
return square(side)
ns = classx.create_namespace("ns")
Circle = ns.create_class(
name="Circle",
constructor=Circle_constructor,
public_methods={
"area": Circle_area,
},
private_methods=[square],
)
Square = ns.create_class(
name="Square",
constructor=Square_constructor,
public_methods={
"area": Square_area,
},
private_methods=[square],
)
def main() -> None:
ci = Circle(3)
print(ci.area())
print("------")
sq = Square(3)
print(sq.area())
if __name__ == "__main__":
main()
import classx
ns1 = classx.create_namespace("ns1")
ns2 = classx.create_namespace("ns2")
def main() -> None:
ns1.set_class(name="User")
ns2.set_class(name="User")
print(ns1.contains(ns1.get_class("User"))) # True
print(ns2.contains(ns2.get_class("User"))) # True
print(ns1.contains(ns2.get_class("User"))) # False
print(ns2.contains(ns1.get_class("User"))) # False
if __name__ == "__main__":
main()
import classx
ns = classx.create_namespace("ns")
User = ns.create_class(name="User")
def main() -> None:
global User
print(User) # <function User at @ddress>
print(User.__name__) # User
# Just like JavaScript typeof AnyCls === "function"
print(type(User)) # User class is function object (meta class function).
print(type(User).__name__) # function
print(type(type(User))) # function class is type object (super meta class type).
print(type(type(User)).__name__) # type
print(ns.methods()) # Show all namespace methods
# To check if a class exist inside a namespace or not
print(ns.exists("User")) # True
# or
# print(ns.exists(User.__name__)) # True
print(ns.length()) # Show how many classes in this namespace.
print(ns.classes()) # Show all classes in this namespace.
# Check if ns contains this User class version or not
print(ns.contains(User)) # True
user = User()
print(user) # User() object with no attributes/methods (empty).
print(type(user)) # <class 'classx.User'>
print(type(user).__name__) # User
print(ns.type(user)) # <function User at @ddress>
print(ns.type(user).__name__) # User
print(ns.isinstance(user, User)) # True
ns.remove_class("User") # Remove exact class User with name.
# or
# ns.remove_class(User.__name__)
print(ns.classes()) # () zero class
ns.set_class(name="User")
print(
ns.contains(User)
) # False (Bceause this class version with this pointer are deleted from ns)
# Now global User pointer point to the new User class
User = ns.get_class("User")
print(ns.contains(User)) # True
ns.clear_classes()
print(ns.classes()) # () zero class
if __name__ == "__main__":
main()
import classx
import typing
ns = classx.create_namespace("ns")
User1 = ns.create_class(name="User1")
User2 = ns.create_class(name="User2")
def main() -> None:
user1 = User1()
user2 = User2()
print(ns.isinstance(user1, User1)) # True
print(ns.isinstance(user2, User2)) # True
print(ns.isinstance(user1, (User1, user2))) # True
print(ns.isinstance(user2, (User1, User2))) # True
print(ns.isinstance(user1, typing.Union[User1, User2])) # True
print(ns.isinstance(user2, typing.Union[User1, User2])) # True
print(ns.isinstance(user1, User2)) # False
print(ns.isinstance(user2, User1)) # False
if __name__ == "__main__":
main()
import classx
class Node:
__slots__: tuple[str, str] = ("data", "next")
def __init__(self, data: object) -> None:
self.data = data
self.next: None | Node = None
def LinkedList_constructor() -> None:
head: None | Node = None
tail: None | Node = None
length: int = 0
def LinkedList_add(data: object) -> None:
node = Node(data)
if head is None:
head = node
else:
tail.next = node
tail = node
length += 1
def LinkedList_length() -> None:
return length
def LinkedList_show() -> None:
current: Node = head
string: list = []
while current:
string.append(f"{current.data} -> ")
current = current.next
string.append("None")
print("".join(string))
ns = classx.create_namespace("ns")
LinkedList = ns.create_class(
name="LinkedList",
constructor=LinkedList_constructor,
public_methods={
"add": LinkedList_add,
"length": LinkedList_length,
"show": LinkedList_show,
},
# This library intentionally omits inheritance in favor of composition.
components={Node.__name__: Node},
)
def main() -> None:
obj = LinkedList()
obj.show()
print(f"length: {obj.length()}")
print("------")
obj.add(1)
obj.show()
print(f"length: {obj.length()}")
print("------")
obj.add(2)
obj.show()
print(f"length: {obj.length()}")
print("------")
obj.add(3)
obj.show()
print(f"length: {obj.length()}")
if __name__ == "__main__":
main()
import classx
# Here we named the constructor with the same class name just like c++
# and java constructor style but when we create another pointer with the
# same class name point to the class it's self after creating it we loose
# the original constructor function pointer but it doesn't matter because
# also when we create a normal class we don't have pointers to the methods
# inside it this is the default because we don't need them.
# If you create the class inside the main function you need to use global
# keyword with the constructor function pointer to avoid UnboundLocalError
def Point(x: int, y: int) -> None:
pass
# We can use private attributes as public attributes if we make setters
# without any validation and the good thing in this case we can make this
# public attributes any time private with adding a validation layer.
def Point_get_x() -> int:
return x
def Point_set_x(new_x: int) -> None:
x = new_x
def Point_get_y() -> int:
return y
def Point_set_y(new_y: int) -> None:
y = new_y
ns = classx.create_namespace("ns")
Point = ns.create_class(
name="Point",
constructor=Point,
public_methods={
"get_x": Point_get_x,
"set_x": Point_set_x,
"get_y": Point_get_y,
"set_y": Point_set_y,
},
)
def main() -> None:
point = Point(1, 3)
print(point.get_x())
print(point.get_y())
point.set_x(5)
point.set_y(7)
print(point.get_x())
print(point.get_y())
if __name__ == "__main__":
main()
import classx
# You can create public static attributes/methods from outside the class
def greet(name: str) -> str:
return f"Hello, {name}!"
# You can access public static attributes/methods inside the class methods
def User_get_name() -> str:
return User.name
def User_set_name(name: str) -> None:
User.name = name
print(User.greet(name))
ns = classx.create_namespace("ns")
User = ns.create_class(
name="User",
public_methods={
"get_name": User_get_name,
"set_name": User_set_name,
},
)
# Public Static Attribute
User.name = "ahmed"
# Chain the greet function with the User class as public static method.
User.greet = greet
def main() -> None:
print(User.name)
print(User.greet(User.name))
user = User()
print(user.get_name())
user.set_name("mohamed")
print(user.get_name())
print(User.name)
if __name__ == "__main__":
main()
Operations on namespaces
import classx as clsx
ns = clsx.create_namespace("ns", overwrite=True) # set and get a new namespace.
def main() -> None:
global ns
print(type(clsx)) # <class 'classx.Engine'>
print(type(ns)) # <class 'classx.Namespace'>
print(clsx.is_overwritable(ns)) # True
clsx.set_namespace("ns") # set a new namespace (overwrite is False in default).
ns = clsx.get_namespace("ns") # get the namespace ns
print(clsx.is_overwritable(ns)) # False
print(ns.name()) # "ns"
print(clsx.exists("ns")) # True
print(clsx.is_namespace(ns)) # True
print(clsx.length()) # 1 namespace
print(clsx.methods()) # Show all namespaces methods
print(clsx.namespaces()) # show all current namespaces
clsx.remove_namespace("ns") # Remove exact namespace ns with name
# or
# clsx.remove_namespace(ns.name())
print(clsx.length()) # 0 namespace
clsx.set_namespace("ns")
print(clsx.length()) # 1 namespace
clsx.clear_namespaces() # Remove all namespaces
print(clsx.length()) # 0 namespace
if __name__ == "__main__":
main()
Modules level
You can use classx to secure your modules like this
# mymodule.py
import sys
import classx
def hello() -> None:
print("Hello!")
def add(a: int, b: int) -> int:
return a + b
class Person:
def __init__(self, name: str) -> None:
self.name = name
def greet(self) -> None:
print(f"Hi, I'm {self.name}")
def Person_wrapper(name: str) -> Person:
return Person(name)
ns = classx.create_namespace("ns")
mymodule = ns.create_class(
name="mymodule",
public_methods={
hello.__name__: hello,
add.__name__: add,
"Person": Person_wrapper,
},
components={Person.__name__: Person},
)
sys.modules[__name__] = mymodule()
def main() -> None:
print("mymodule")
if __name__ == "__main__":
main()
import mymodule
def main() -> None:
print(type(mymodule)) # <class 'classx.mymodule'>
mymodule.hello()
print(mymodule.add(1, 2))
person = mymodule.Person("mohamed")
person.greet()
try:
person.greet = "something"
except AttributeError as e0:
print("AttributeError handled") # When you use a classx type
try:
person.greet()
except TypeError as e1:
print("TypeError handled") # When you use a normal class
try:
mymodule.hello = "something"
except AttributeError as e2:
print("AttributeError handled")
try:
mymodule.add = "something"
except AttributeError as e3:
print("AttributeError handled")
try:
mymodule.Person = "something"
except AttributeError as e4:
print("AttributeError handled")
if __name__ == "__main__":
main()
The same module example using classx type Person for full privacy and encapsulation
# mymodule.py
import sys
import classx
def hello() -> None:
print("Hello!")
def add(a: int, b: int) -> int:
return a + b
def Person_constructor(name: str) -> None:
pass
def Person_greet() -> None:
print(f"Hi, I'm {name}")
ns = classx.create_namespace("ns")
Person = ns.create_class(
name="Person",
constructor=Person_constructor,
public_methods={"greet": Person_greet},
)
def Person_wrapper(name: str) -> Person:
return Person(name)
mymodule = ns.create_class(
name="mymodule",
public_methods={
hello.__name__: hello,
add.__name__: add,
"Person": Person_wrapper,
},
components={Person.__name__: Person},
)
sys.modules[__name__] = mymodule()
def main() -> None:
print("mymodule")
if __name__ == "__main__":
main()
Don't write from classx import * because is not allowed and it's a bad practice.
License
This project is licensed under the MIT LICENSE - see the LICENSE for more details.
Metadata
Release files for classx 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| classx-1.0.0.tar.gz | 18.8 kB | Without provenance | Details for classx-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| classx-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for classx-1.0.0-py3-none-any.whl |
Total release size: 31.6 kB
Release files / classx-1.0.0.tar.gz
| Download URL | classx-1.0.0.tar.gz |
|---|---|
| Size | 18.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3d4a095143f8656976dbdeab5b4a42e5f0ac59931434c440a4342b007bf16874
|
|
BLAKE2b-256 checksum How to use checksums |
15838f84433564a62893594ca5696e9a326f86100d638798846221471602df70
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.2
|
Release files / classx-1.0.0-py3-none-any.whl
| Download URL | classx-1.0.0-py3-none-any.whl |
|---|---|
| Size | 12.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
432d7a8b6deaf0bf6a65c4de5b2c6c4cf6da9f9641c51b299394246b9223fda8
|
|
BLAKE2b-256 checksum How to use checksums |
1e4aeb277ed965208f3b55a152e44d91d44452952ba7ecd9716528f549cb4a46
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.2
|
Security for classx 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.