Last active
December 14, 2015 04:29
-
-
Save rubensayshi/5028772 to your computer and use it in GitHub Desktop.
I want to be able to completely mimic a builtin str() with a class
This file contains hidden or 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
| import sys | |
| from time import time | |
| # limit recursion to avoid spam during debugging | |
| sys.setrecursionlimit(50) | |
| # ---------------------- | |
| # I want to be able to completely mimic a builtin str() with a class | |
| # ---------------------- | |
| # ---------------------- | |
| # define the StringLike class we're working on | |
| # ---------------------- | |
| class StringLike(str): | |
| """ | |
| Class to mimic the behavior of a regular string. Classes that inherit (or mixin) this class | |
| must implement the `__str__` magic method. Whatever that method returns is used by the various | |
| string-like methods. | |
| __getattribute__ intercepts 'everything' and unless the attribute is in the `attrs_on_object` list | |
| we'll call it on the string. | |
| a lot of builtins bypass __getattribute__ so we still have a bunch of magic methods defined | |
| such as __str__, __len__, __gt__, etc. | |
| """ | |
| """ | |
| when extending StringLike and adding some custom attributes they should be added to this list like: | |
| class MyComplexString(StringLike): | |
| attrs_on_object = StringLike.attrs_on_object + ['value', '_value'] | |
| def __init__(self): | |
| self._value = None | |
| @property | |
| def value(self): | |
| if self._value is None: | |
| self._value = "mycomplexstring" | |
| return self._value | |
| def __str__(self): | |
| return self.value | |
| """ | |
| attrs_on_object = [] | |
| def __getattribute__(self, attr): | |
| if attr in object.__getattribute__(self, 'attrs_on_object'): | |
| return object.__getattribute__(self, attr) | |
| else: | |
| string = str(self) | |
| return object.__getattribute__(string, attr) | |
| def __len__(self): | |
| return len(str(self)) | |
| def __getitem__(self, key): | |
| return str(self)[key] | |
| def __iter__(self): | |
| return iter(str(self)) | |
| def __contains__(self, item): | |
| return item in str(self) | |
| def __add__(self, other): | |
| return str(self) + other | |
| def __radd__(self, other): | |
| return other + str(self) | |
| def __mul__(self, other): | |
| return str(self) * other | |
| def __rmul__(self, other): | |
| return other * str(self) | |
| def __lt__(self, other): | |
| return str(self) < other | |
| def __le__(self, other): | |
| return str(self) <= other | |
| def __eq__(self, other): | |
| return str(self) == other | |
| def __ne__(self, other): | |
| return str(self) != other | |
| def __gt__(self, other): | |
| return str(self) > other | |
| def __ge__(self, other): | |
| return str(self) >= other | |
| # ---------------------- | |
| # first simple implementation of StringLike | |
| # we only define the __str__ | |
| # ---------------------- | |
| class MyString(StringLike): | |
| def __str__(self): | |
| return "mystring" | |
| # start testing | |
| s = MyString() | |
| print str(s) | |
| assert str(s) == 'mystring' | |
| assert isinstance(s, str) | |
| assert s.capitalize() == 'Mystring' | |
| assert s[2] == 's' | |
| assert "".join([p for p in s]) == 'mystring' | |
| assert "string" in s | |
| assert s + 'lol' == 'mystringlol' | |
| assert 'lol' +s == 'lolmystring' | |
| assert s * 3 == 'mystringmystringmystring' | |
| assert 3 * s == 'mystringmystringmystring' | |
| assert s < "mystring2" | |
| assert s > "mystrin" | |
| assert s == "mystring" | |
| assert s != "mystring2" | |
| assert len(s) == 8 | |
| try: | |
| s.unknown_attr | |
| assert False | |
| except AttributeError: | |
| assert True | |
| # ---------------------- | |
| # second more complex implementation of StringLike | |
| # the string value is lazy loaded so we also defined .value and ._value | |
| # ---------------------- | |
| class MyComplexString(StringLike): | |
| attrs_on_object = StringLike.attrs_on_object + ['value', '_value'] | |
| def __init__(self): | |
| self._value = None | |
| @property | |
| def value(self): | |
| if self._value is None: | |
| self._value = "mycomplexstring" | |
| return self._value | |
| def __str__(self): | |
| return self.value | |
| # start testing | |
| s = MyComplexString() | |
| print str(s) | |
| assert str(s) == 'mycomplexstring' | |
| assert isinstance(s, str) | |
| assert s.capitalize() == 'Mycomplexstring' | |
| assert s[2] == 'c' | |
| assert "".join([p for p in s]) == 'mycomplexstring' | |
| assert "string" in s | |
| assert s + 'lol' == 'mycomplexstringlol' | |
| assert 'lol' +s == 'lolmycomplexstring' | |
| assert s * 3 == 'mycomplexstringmycomplexstringmycomplexstring' | |
| assert 3 * s == 'mycomplexstringmycomplexstringmycomplexstring' | |
| assert s < "mycomplexstring2" | |
| assert s > "mycomplexstrin" | |
| assert s == "mycomplexstring" | |
| assert s != "mycomplexstring2" | |
| assert len(s) == 15 | |
| try: | |
| s.unknown_attr | |
| assert False | |
| except AttributeError: | |
| assert True | |
| # ---------------------- | |
| # test the differences in performance | |
| # ---------------------- | |
| # builtin string | |
| t = time() | |
| s = 'mystring' | |
| for i in range(0,100): | |
| s += str(i) | |
| s.capitalize(), len(s), isinstance(s, str) | |
| builtin_time = time() - t | |
| # MyString | |
| t = time() | |
| s = MyString() | |
| for i in range(0,100): | |
| s += str(i) | |
| s.capitalize(), len(s), isinstance(s, str) | |
| mystring_time = time() - t | |
| # MyComplexString | |
| t = time() | |
| s = MyComplexString() | |
| for i in range(0,100): | |
| s += str(i) | |
| s.capitalize(), len(s), isinstance(s, str) | |
| mycomplexstring_time = time() - t | |
| print "str: " + str(builtin_time) | |
| print "MyString: " + str(mystring_time) | |
| print "MyComplexString: " + str(mycomplexstring_time) | |
| # being less then 10% slower then the base string is acceptable for me | |
| assert mystring_time < (builtin_time * 1.1) | |
| assert mycomplexstring_time < (builtin_time * 1.1) |
This file contains hidden or 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
| # ---------------------- | |
| # This is our actual implementation | |
| # ---------------------- | |
| class LazyConfigString(StringLike): | |
| """LazyConfigString provides a way to do lazy string replacements. | |
| this allows us to overload the values that are used in the replacement in our env specific classes. | |
| we inherit StringLike which provides all the usual string operations and it's all done on str(self), | |
| so all we have to do is provide __str__ | |
| for example: | |
| class Config(object): | |
| REDIS_SERVER='localhost'; | |
| REDIS_URL = LazyConfigString('redis://%(REDIS_SERVER)s:9999/1') | |
| class DevConfig(Config): | |
| REDIS_SERVER='redis1.dev' | |
| print DevConfig().REDIS_URL # redis://redis1.dev:9999/1 | |
| """ | |
| def __init__(self, pattern): | |
| self._pattern = pattern | |
| self._value = None | |
| @property | |
| def config(self): | |
| from jaws.config import config | |
| return config | |
| @property | |
| def pattern(self): | |
| return self._pattern | |
| @property | |
| def value(self): | |
| # generate the value if we haven't already | |
| if self._value is None: | |
| if self.pattern in self.config: | |
| self._value = self.config[self.pattern] | |
| else: | |
| self._value = self.pattern % self.config | |
| return self._value | |
| def __str__(self): | |
| """StringLike uses this for all the other string operations""" | |
| return self.value | |
| class Config(object): | |
| REDIS_SERVER='localhost'; | |
| REDIS_URL = LazyConfigString('redis://%(REDIS_SERVER)s:9999/1') | |
| class DevConfig(Config): | |
| REDIS_SERVER='redis1.dev' | |
| print DevConfig().REDIS_URL # redis://redis1.dev:9999/1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment