Skip to content

Instantly share code, notes, and snippets.

@asher-dev
Last active January 25, 2019 02:04
Show Gist options
  • Save asher-dev/9160be3641d577adea45a009274f0d2a to your computer and use it in GitHub Desktop.
Save asher-dev/9160be3641d577adea45a009274f0d2a to your computer and use it in GitHub Desktop.
Something approximating a python struct
#!/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