Ability to modify python types by simple class declaration (original) (raw)
Currently, if you define a class like this:
class str(str):
def titlecase(self):
return [word[0].upper() + word[1:] for word in self]
It works on objects that you call the str
method on, but it doesn’t work on regular string literals. I suggest that when you do something like this :
class str(builtin=True):
# Stuff goes here
it then applies to all strings, even string literals.
That was just an example, it should be implemented for all types.
facundo (Facundo Batista) July 23, 2023, 7:09pm 2
What for? What is the idea behind this feature? Do you have real cases where this would be useful? Thanks!
jeanas (Jean Abou Samra) July 23, 2023, 7:18pm 3
Ruby allows this, and it is considered a major wart in the language. Search for “Ruby monkeypatching”.
ericvsmith (Eric V. Smith) July 23, 2023, 7:36pm 4
“Because Ruby does it” is not a great reason to add something to Python.
What code could you write using this feature that isn’t possible today?
jeanas (Jean Abou Samra) July 23, 2023, 8:12pm 5
Are you replying to me? I said it was a wart, i.e. it should not be added to Python.
ericvsmith (Eric V. Smith) July 23, 2023, 8:13pm 6
@jeanas Apologies! I read your comment as: it’s a python wart for not having this feature.
csm10495 (Charles Machalow) July 23, 2023, 9:11pm 7
I think this is a terrible idea. Though for science i gave it a try:
import builtins
class str(str):
def title2(self):
return 'lolcats'
def hook():
builtins.str = str
Running hook in a terminal blows up like so
C:\Users\csm10495\Desktop>python -i test.py
>>> hook()
>>> <press enter>
Readline internal error
Traceback (most recent call last):
File "C:\Python3\Lib\site-packages\pyreadline3\console\console.py", line 807, in hook_wrapper_23
raise TypeError('readline must return a string.')
TypeError: readline must return a string.
Exception ignored on calling ctypes callback function: <function hook_wrapper_23 at 0x000002BE3D4EF240>
Traceback (most recent call last):
File "C:\Python3\Lib\site-packages\pyreadline3\console\console.py", line 821, in hook_wrapper_23
_strncpy(cast(p, c_char_p), res, n + 1)
ctypes.ArgumentError: argument 2: <class 'TypeError'>: wrong type
Things are unhappy that a str
is not a str
. I’m pleasantly surprised it doesn’t work.
kknechtel (Karl Knechtel) July 23, 2023, 11:18pm 8
“works on” isn’t meaningful language to describe the behaviour.
str
, by default, is a built-in type, not a “method”. Calling it - like calling any other type, such as the user-defined ones created with the class
statement - creates a new instance of the type (this behaviour can be altered using __new__
). The code that you show doesn’t modify the built-in type; it creates a new type and reuses the name for that new type. So of course existing objects of the type aren’t affected, and of course subsequent literals aren’t affected - when they’re created, their type is known directly, and is not looked up by name.
Syntax like this makes no sense because the purpose of the class
statement fundamentally is to create new types, not to modify existing ones.
Aside from that, anything that tries to make literals use a different type from their usual built-in one is a non-starter, because objects get created and “frozen” as part of compiling the code for functions.
What you seem to actually want is to be able to add methods to the built-in types. That is called “monkey-patching”, as noted:
Actual monkey-patching looks fundamentally different. It is supported already in Python for user-defined types:
class Example:
pass
x = Example()
try:
x.method()
except AttributeError:
print('the method does not exist.')
# Recall, the name "self" is only a convention. Written outside the class,
# that name isn't so meaningful.
def method(instance):
print('calling the method on', instance)
# monkey-patch the method.
Example.method = method
# Now it can be looked up from an instance, whether created prior:
x.method()
# or later:
Example().method()
However, it is not supported for built-in types, for good reason. Aside from causing untold potential for confusion (suppose I import
your library and now suddenly 1 + 1
literally doesn’t give 2
any more! And how am I meant to undo the effect?), it would require forgoing a huge suite of optimizations that take advantage of the fact that these built-in types are always exactly what the documentation says they are. In particular, methods on builtin types don’t need to be looked up by the usual attribute-lookup mechanisms; behind-the-scenes code can directly call the appropriate implementation function.
suppose I
import
your library and now suddenly1 + 1
literally doesn’t give2
any more! And how am I meant to undo the effect?
You could have a module variable module.modified_builtins
that you would have to set to True
for those changes to take effect.
Rosuav (Chris Angelico) July 24, 2023, 9:13am 10
This wouldn’t work, since the objects (eg the str
type object) are the same everywhere. You can’t have the changes take effect only within a module.
Fine, I guess it’s a no. But I have a similar suggestion, which I will post soon.
TheTripleV (Vasista Vovveti) July 26, 2023, 6:38pm 12
wim (wim) July 28, 2023, 2:40am 13
It’s actually already possible to “curse” builtin types like str
:
>>> def mymethod(self):
... return self + " ook ook"
...
>>> def monkeypatch(type_, func):
... gc.get_referents(type_.__dict__)[0][func.__name__] = func
...
>>> import gc
>>> monkeypatch(str, mymethod)
>>> "hello".mymethod()
'hello ook ook'
Curse with an existing function:
>>> monkeypatch(str, textwrap.dedent)
>>> """
... hello
... world
... """.dedent()
'\nhello\nworld\n'
This is not a recommendation. It’s hackish, that’s an abuse of reference counting, and it probably only works in CPython.
If anyone asks, you didn’t hear it from me…
ericvsmith (Eric V. Smith) July 28, 2023, 9:07pm 14
Wow. Bravo!
Actually, you could have a module with the builtins unmodified, and import that last if you don’t want the builtin changes.