-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Fix subclasses of string. (Fixes #130) #674
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from testutils import assertRaises | ||
|
||
x = "An interesting piece of text" | ||
assert x is str(x) | ||
|
||
class Stringy(str): | ||
def __new__(cls, value=""): | ||
return str.__new__(cls, value) | ||
|
||
def __init__(self, value): | ||
self.x = "substr" | ||
|
||
y = Stringy(1) | ||
assert type(y) is Stringy, "Type of Stringy should be stringy" | ||
assert type(str(y)) is str, "Str of a str-subtype should be a str." | ||
|
||
assert y + " other" == "1 other" | ||
assert y.x == "substr" | ||
|
||
## Base strings currently get an attribute dict, but shouldn't. | ||
# with assertRaises(AttributeError): | ||
# "hello".x = 5 | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,15 +7,15 @@ use unicode_segmentation::UnicodeSegmentation; | |
|
||
use crate::format::{FormatParseError, FormatPart, FormatString}; | ||
use crate::pyobject::{ | ||
IntoPyObject, OptionalArg, PyContext, PyFuncArgs, PyIterable, PyObjectRef, PyRef, PyResult, | ||
PyValue, TypeProtocol, | ||
IdProtocol, IntoPyObject, OptionalArg, PyContext, PyFuncArgs, PyIterable, PyObjectRef, PyRef, | ||
PyResult, PyValue, TryFromObject, TypeProtocol, | ||
}; | ||
use crate::vm::VirtualMachine; | ||
|
||
use super::objint; | ||
use super::objsequence::PySliceableSequence; | ||
use super::objslice::PySlice; | ||
use super::objtype; | ||
use super::objtype::{self, PyClassRef}; | ||
|
||
#[derive(Clone, Debug)] | ||
pub struct PyString { | ||
|
@@ -788,16 +788,21 @@ fn perform_format( | |
// TODO: should with following format | ||
// class str(object='') | ||
// class str(object=b'', encoding='utf-8', errors='strict') | ||
fn str_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { | ||
if args.args.len() == 1 { | ||
return Ok(vm.new_str("".to_string())); | ||
} | ||
|
||
if args.args.len() > 2 { | ||
panic!("str expects exactly one parameter"); | ||
fn str_new( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that methods exactly match their python names, should we do the same for free-standing functions? i.e. should this just be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe. I quite like the convention, it makes it a bit easier to know what's what. I think I'd call that Of course, that function could be a method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But it can't be a method called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really like the idea of it being a (doesn't need to be in this PR though, just tossing out ideas) |
||
cls: PyClassRef, | ||
object: OptionalArg<PyObjectRef>, | ||
vm: &mut VirtualMachine, | ||
) -> PyResult<PyStringRef> { | ||
let string = match object { | ||
OptionalArg::Present(ref input) => vm.to_str(input)?, | ||
OptionalArg::Missing => vm.new_str("".to_string()), | ||
}; | ||
|
||
vm.to_str(&args.args[1]) | ||
if string.typ().is(&cls) { | ||
TryFromObject::try_from_object(vm, string) | ||
} else { | ||
let payload = string.payload::<PyString>().unwrap(); | ||
PyRef::new_with_type(vm, payload.clone(), cls) | ||
} | ||
} | ||
|
||
impl PySliceableSequence for String { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is more consistent that base strings have attribute dicts, I believe cpython is wrong here. We can deviate. You can use the platform module to check for rustpython.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like a pretty serious performance question, if we have to allocate dicts for intermediate ints and bools in computations.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's important enough that
__slots__
exists so that user classes can take advantage of this.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not just that; this also makes other common optimizations on builtin types that we would probably implement at some point (string interning, small int cache) much weirder and harder to reason about.
In CPython:
So if in one section of code I do
"hello".x = 5
, this may affect any other code (including third-party library) using the same "hello" literal - as it may or may not be interned. Same with other builtins. That's implicit global state.