Last active
January 25, 2019 02:04
-
-
Save asher-dev/9160be3641d577adea45a009274f0d2a to your computer and use it in GitHub Desktop.
Something approximating a python struct
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
[Created: March 2018] | |
Oh hello, I'm here to destroy everything you love about Python. | |
This is kind of ridiculous, but I wanted to create an easy way to | |
define struct-like classes in Python and it seemed like a good way | |
to learn some more about the python data model. | |
It's more of a syntax hack than anything else, but it was fun to | |
make. | |
It does more or less the same thing as `namedtuple` but uses class syntax. | |
UPDATE: Data Classes were added to Python 3.7, released in June 2018, making this entirely redundant! | |
""" | |
import string | |
from itertools import chain | |
class struct(object): | |
def __init_subclass__(cls, **kwargs): | |
super().__init_subclass__(**kwargs) | |
cls._FIELDS = tuple(set(key for key in cls.__dict__ | |
if key[:2] + key[-2:] != "____" and key != "_static")) | |
if "_static" in cls.__dict__: | |
for k, v in cls._static.items(): | |
setattr(cls, k, v) | |
delattr(cls, "_static") | |
for field in cls._FIELDS: | |
val = getattr(cls, field) | |
delattr(cls, field) | |
setattr(cls, "__" + field, val) | |
def _attr_error(self, key): | |
raise AttributeError("'{}' object has no member '{}'".format(type(self).__name__, key)) | |
def __init__(self, **kwargs): | |
if type(self) is struct: | |
raise NotImplementedError("Cannot initialize abstract class. Define a struct subtype.") | |
self.__dict__.update({k: getattr(type(self), "__" + k) for k in self.get_fields()}) | |
for key in kwargs: | |
if key not in self._FIELDS: | |
self._attr_error(key) | |
self.__dict__.update(**kwargs) | |
def __setattr__(self, name, value): | |
if name not in self._FIELDS: | |
self._attr_error(name) | |
self.__dict__[name] = value | |
def get_fields(self): | |
return tuple(self._FIELDS) | |
def _as_dict(self): | |
return {k: self.__dict__[k] for k in self._FIELDS} | |
def values(self): | |
return (self.__dict__[k] for k in self._FIELDS) | |
def __str__(self): | |
template_base = ",".join("{}=${{{}}}".format(field, field) for field in self.get_fields()) | |
return string.Template("{}({})".format( | |
type(self).__name__, template_base) | |
).substitute(**{k: repr(v) for k, v in self._as_dict().items()}) | |
# sorry bout that ^^ | |
def __copy__(self): | |
return type(self)(**self._as_dict()) | |
if __name__ == "__main__": | |
# usage/test | |
# ----- Example 1 ----- | |
class Name(struct): | |
first = "" # initialize members as if they were class attributes | |
last = "" | |
knownothing = Name(first="Jon", last="Snow") | |
assert knownothing.first == "Jon" | |
assert knownothing.last == "Snow" | |
print(knownothing) | |
# Name(last='Snow',first='Jon') | |
# Note that we cannot add new attributes to the class | |
try: | |
knownothing.nickname = "Bastard" | |
except AttributeError as e: | |
print("Error:", e) | |
# Error: 'Name' object has no member 'nick' | |
# Note that the "class" attributes defined above do not exist in the class definition | |
try: | |
print(Name.first) | |
except AttributeError as e: | |
print("Error:", e) | |
# Error: type object 'Name' has no attribute 'first' | |
# ----- Example 2 ----- | |
class Vector3(struct): | |
x, y, z = 0, 0, 0 # shorthand for setting default values | |
v1 = Vector3() | |
print(v1) | |
# Vector3(z=0,y=0,x=0) | |
v2 = Vector3(x=1, y=2) | |
v2.z = 3 | |
print(v2) | |
# Vector3(x=1,y=2,z=3) | |
# I'mma add a method | |
setattr(Vector3, "add", lambda self, *others: Vector3(**dict(zip(self.get_fields(), (sum(row) for row in zip(*(v.values() for v in [self] + list(others)))))))) | |
# lol ^ | |
print(v1.add(v2)) | |
# Vector3(x=1,y=2,z=3) | |
v3 = Vector3(x=5) | |
print(v1.add(v2, v3)) | |
# Vector3(y=2,x=6,z=3) | |
setattr(Vector3, "__add__", lambda self, other: self.add(other)) | |
print(Vector3(x=0, y=3, z=2) + Vector3(x=1, y=2, z=3)) | |
# Vector3(x=1,y=5,z=5) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment