Python: Optimizes copy on basic model classes

Immutable values do not need to be actually copied
pull/430/head
Tpt 2 years ago committed by Thomas Tanon
parent d4e964ac47
commit c40c81447e
  1. 15
      python/generate_stubs.py
  2. 91
      python/src/model.rs
  3. 25
      python/tests/test_model.py

@ -153,12 +153,13 @@ def class_stubs(
) )
doc = inspect.getdoc(cls_def) doc = inspect.getdoc(cls_def)
doc_comment = build_doc_comment(doc) if doc else None
return ast.ClassDef( return ast.ClassDef(
cls_name, cls_name,
bases=[], bases=[],
keywords=[], keywords=[],
body=( body=(
([build_doc_comment(doc)] if doc else []) ([doc_comment] if doc_comment else [])
+ attributes + attributes
+ methods + methods
+ magic_methods + magic_methods
@ -193,7 +194,8 @@ def data_descriptor_stub(
annotation=annotation or AST_TYPING_ANY, annotation=annotation or AST_TYPING_ANY,
simple=1, simple=1,
) )
return (assign, build_doc_comment(doc_comment)) if doc_comment else (assign,) doc_comment = build_doc_comment(doc_comment) if doc_comment else None
return (assign, doc_comment) if doc_comment else (assign,)
def function_stub( def function_stub(
@ -207,7 +209,9 @@ def function_stub(
body: List[ast.AST] = [] body: List[ast.AST] = []
doc = inspect.getdoc(fn_def) doc = inspect.getdoc(fn_def)
if doc is not None: if doc is not None:
body.append(build_doc_comment(doc)) doc_comment = build_doc_comment(doc)
if doc_comment is not None:
body.append(doc_comment)
decorator_list = [] decorator_list = []
if in_class and hasattr(fn_def, "__self__"): if in_class and hasattr(fn_def, "__self__"):
@ -439,14 +443,15 @@ def parse_type_to_ast(
return parse_sequence(stack[0]) return parse_sequence(stack[0])
def build_doc_comment(doc: str) -> ast.Expr: def build_doc_comment(doc: str) -> Optional[ast.Expr]:
lines = [line.strip() for line in doc.split("\n")] lines = [line.strip() for line in doc.split("\n")]
clean_lines = [] clean_lines = []
for line in lines: for line in lines:
if line.startswith((":type", ":rtype")): if line.startswith((":type", ":rtype")):
continue continue
clean_lines.append(line) clean_lines.append(line)
return ast.Expr(value=ast.Constant("\n".join(clean_lines).strip())) text = "\n".join(clean_lines).strip()
return ast.Expr(value=ast.Constant(text)) if text else None
def format_with_black(code: str) -> str: def format_with_black(code: str) -> str:

@ -111,9 +111,22 @@ impl PyNamedNode {
} }
} }
/// :rtype: typing.Any
fn __getnewargs__(&self) -> (&str,) { fn __getnewargs__(&self) -> (&str,) {
(self.value(),) (self.value(),)
} }
/// :rtype: NamedNode
fn __copy__(slf: PyRef<'_, Self>) -> PyRef<Self> {
slf
}
/// :type memo: typing.Any
/// :rtype: NamedNode
#[allow(unused_variables)]
fn __deepcopy__<'a>(slf: PyRef<'a, Self>, memo: &'_ PyAny) -> PyRef<'a, Self> {
slf
}
} }
/// An RDF `blank node <https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node>`_. /// An RDF `blank node <https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node>`_.
@ -221,9 +234,22 @@ impl PyBlankNode {
} }
} }
/// :rtype: typing.Any
fn __getnewargs__(&self) -> (&str,) { fn __getnewargs__(&self) -> (&str,) {
(self.value(),) (self.value(),)
} }
/// :rtype: BlankNode
fn __copy__(slf: PyRef<'_, Self>) -> PyRef<Self> {
slf
}
/// :type memo: typing.Any
/// :rtype: BlankNode
#[allow(unused_variables)]
fn __deepcopy__<'a>(slf: PyRef<'a, Self>, memo: &'_ PyAny) -> PyRef<'a, Self> {
slf
}
} }
/// An RDF `literal <https://www.w3.org/TR/rdf11-concepts/#dfn-literal>`_. /// An RDF `literal <https://www.w3.org/TR/rdf11-concepts/#dfn-literal>`_.
@ -361,6 +387,7 @@ impl PyLiteral {
} }
} }
/// :rtype: typing.Any
fn __getnewargs_ex__<'a>(&'a self, py: Python<'a>) -> PyResult<((&'a str,), &'a PyDict)> { fn __getnewargs_ex__<'a>(&'a self, py: Python<'a>) -> PyResult<((&'a str,), &'a PyDict)> {
let kwargs = PyDict::new(py); let kwargs = PyDict::new(py);
if let Some(language) = self.language() { if let Some(language) = self.language() {
@ -370,6 +397,18 @@ impl PyLiteral {
} }
Ok(((self.value(),), kwargs)) Ok(((self.value(),), kwargs))
} }
/// :rtype: Literal
fn __copy__(slf: PyRef<'_, Self>) -> PyRef<Self> {
slf
}
/// :type memo: typing.Any
/// :rtype: Literal
#[allow(unused_variables)]
fn __deepcopy__<'a>(slf: PyRef<'a, Self>, memo: &'_ PyAny) -> PyRef<'a, Self> {
slf
}
} }
/// The RDF `default graph name <https://www.w3.org/TR/rdf11-concepts/#dfn-default-graph>`_. /// The RDF `default graph name <https://www.w3.org/TR/rdf11-concepts/#dfn-default-graph>`_.
@ -425,9 +464,22 @@ impl PyDefaultGraph {
} }
} }
/// :rtype: typing.Any
fn __getnewargs__<'a>(&self, py: Python<'a>) -> &'a PyTuple { fn __getnewargs__<'a>(&self, py: Python<'a>) -> &'a PyTuple {
PyTuple::empty(py) PyTuple::empty(py)
} }
/// :rtype: DefaultGraph
fn __copy__(slf: PyRef<'_, Self>) -> PyRef<Self> {
slf
}
/// :type memo: typing.Any
/// :rtype: DefaultGraph
#[allow(unused_variables)]
fn __deepcopy__<'a>(slf: PyRef<'a, Self>, memo: &'_ PyAny) -> PyRef<'a, Self> {
slf
}
} }
#[derive(FromPyObject)] #[derive(FromPyObject)]
@ -674,9 +726,22 @@ impl PyTriple {
} }
} }
/// :rtype: typing.Any
fn __getnewargs__(&self) -> (PySubject, PyNamedNode, PyTerm) { fn __getnewargs__(&self) -> (PySubject, PyNamedNode, PyTerm) {
(self.subject(), self.predicate(), self.object()) (self.subject(), self.predicate(), self.object())
} }
/// :rtype: Triple
fn __copy__(slf: PyRef<'_, Self>) -> PyRef<Self> {
slf
}
/// :type memo: typing.Any
/// :rtype: Triple
#[allow(unused_variables)]
fn __deepcopy__<'a>(slf: PyRef<'a, Self>, memo: &'_ PyAny) -> PyRef<'a, Self> {
slf
}
} }
#[derive(FromPyObject)] #[derive(FromPyObject)]
@ -889,6 +954,7 @@ impl PyQuad {
} }
} }
/// :rtype: typing.Any
fn __getnewargs__(&self) -> (PySubject, PyNamedNode, PyTerm, PyGraphName) { fn __getnewargs__(&self) -> (PySubject, PyNamedNode, PyTerm, PyGraphName) {
( (
self.subject(), self.subject(),
@ -897,6 +963,18 @@ impl PyQuad {
self.graph_name(), self.graph_name(),
) )
} }
/// :rtype: Quad
fn __copy__(slf: PyRef<'_, Self>) -> PyRef<Self> {
slf
}
/// :type memo: typing.Any
/// :rtype: Quad
#[allow(unused_variables)]
fn __deepcopy__<'a>(slf: PyRef<'a, Self>, memo: &'_ PyAny) -> PyRef<'a, Self> {
slf
}
} }
/// A SPARQL query variable. /// A SPARQL query variable.
@ -969,9 +1047,22 @@ impl PyVariable {
eq_compare(self, other, op) eq_compare(self, other, op)
} }
/// :rtype: typing.Any
fn __getnewargs__(&self) -> (&str,) { fn __getnewargs__(&self) -> (&str,) {
(self.value(),) (self.value(),)
} }
/// :rtype: Variable
fn __copy__(slf: PyRef<'_, Self>) -> PyRef<Self> {
slf
}
/// :type memo: typing.Any
/// :rtype: Variable
#[allow(unused_variables)]
fn __deepcopy__<'a>(slf: PyRef<'a, Self>, memo: &'_ PyAny) -> PyRef<'a, Self> {
slf
}
} }
pub struct PyNamedNodeRef<'a>(PyRef<'a, PyNamedNode>); pub struct PyNamedNodeRef<'a>(PyRef<'a, PyNamedNode>);

@ -1,3 +1,4 @@
import copy
import pickle import pickle
import unittest import unittest
@ -29,8 +30,9 @@ class TestNamedNode(unittest.TestCase):
def test_pickle(self) -> None: def test_pickle(self) -> None:
node = NamedNode("http://foo") node = NamedNode("http://foo")
self.assertEqual(NamedNode(*node.__getnewargs__()), node)
self.assertEqual(pickle.loads(pickle.dumps(node)), node) self.assertEqual(pickle.loads(pickle.dumps(node)), node)
self.assertEqual(copy.copy(node), node)
self.assertEqual(copy.deepcopy(node), node)
class TestBlankNode(unittest.TestCase): class TestBlankNode(unittest.TestCase):
@ -50,8 +52,13 @@ class TestBlankNode(unittest.TestCase):
def test_pickle(self) -> None: def test_pickle(self) -> None:
node = BlankNode("foo") node = BlankNode("foo")
self.assertEqual(pickle.loads(pickle.dumps(node)), node) self.assertEqual(pickle.loads(pickle.dumps(node)), node)
self.assertEqual(copy.copy(node), node)
self.assertEqual(copy.deepcopy(node), node)
auto = BlankNode() auto = BlankNode()
self.assertEqual(pickle.loads(pickle.dumps(auto)), auto) self.assertEqual(pickle.loads(pickle.dumps(auto)), auto)
self.assertEqual(copy.copy(auto), auto)
self.assertEqual(copy.deepcopy(auto), auto)
class TestLiteral(unittest.TestCase): class TestLiteral(unittest.TestCase):
@ -88,10 +95,18 @@ class TestLiteral(unittest.TestCase):
def test_pickle(self) -> None: def test_pickle(self) -> None:
simple = Literal("foo") simple = Literal("foo")
self.assertEqual(pickle.loads(pickle.dumps(simple)), simple) self.assertEqual(pickle.loads(pickle.dumps(simple)), simple)
self.assertEqual(copy.copy(simple), simple)
self.assertEqual(copy.deepcopy(simple), simple)
lang_tagged = Literal("foo", language="en") lang_tagged = Literal("foo", language="en")
self.assertEqual(pickle.loads(pickle.dumps(lang_tagged)), lang_tagged) self.assertEqual(pickle.loads(pickle.dumps(lang_tagged)), lang_tagged)
self.assertEqual(copy.copy(lang_tagged), lang_tagged)
self.assertEqual(copy.deepcopy(lang_tagged), lang_tagged)
number = Literal("1", datatype=XSD_INTEGER) number = Literal("1", datatype=XSD_INTEGER)
self.assertEqual(pickle.loads(pickle.dumps(number)), number) self.assertEqual(pickle.loads(pickle.dumps(number)), number)
self.assertEqual(copy.copy(number), number)
self.assertEqual(copy.deepcopy(number), number)
class TestTriple(unittest.TestCase): class TestTriple(unittest.TestCase):
@ -176,6 +191,8 @@ class TestTriple(unittest.TestCase):
NamedNode("http://example.com/o"), NamedNode("http://example.com/o"),
) )
self.assertEqual(pickle.loads(pickle.dumps(triple)), triple) self.assertEqual(pickle.loads(pickle.dumps(triple)), triple)
self.assertEqual(copy.copy(triple), triple)
self.assertEqual(copy.deepcopy(triple), triple)
class TestDefaultGraph(unittest.TestCase): class TestDefaultGraph(unittest.TestCase):
@ -185,6 +202,8 @@ class TestDefaultGraph(unittest.TestCase):
def test_pickle(self) -> None: def test_pickle(self) -> None:
self.assertEqual(pickle.loads(pickle.dumps(DefaultGraph())), DefaultGraph()) self.assertEqual(pickle.loads(pickle.dumps(DefaultGraph())), DefaultGraph())
self.assertEqual(copy.copy(DefaultGraph()), DefaultGraph())
self.assertEqual(copy.deepcopy(DefaultGraph()), DefaultGraph())
class TestQuad(unittest.TestCase): class TestQuad(unittest.TestCase):
@ -265,6 +284,8 @@ class TestQuad(unittest.TestCase):
NamedNode("http://example.com/g"), NamedNode("http://example.com/g"),
) )
self.assertEqual(pickle.loads(pickle.dumps(quad)), quad) self.assertEqual(pickle.loads(pickle.dumps(quad)), quad)
self.assertEqual(copy.copy(quad), quad)
self.assertEqual(copy.deepcopy(quad), quad)
class TestVariable(unittest.TestCase): class TestVariable(unittest.TestCase):
@ -281,6 +302,8 @@ class TestVariable(unittest.TestCase):
def test_pickle(self) -> None: def test_pickle(self) -> None:
v = Variable("foo") v = Variable("foo")
self.assertEqual(pickle.loads(pickle.dumps(v)), v) self.assertEqual(pickle.loads(pickle.dumps(v)), v)
self.assertEqual(copy.copy(v), v)
self.assertEqual(copy.deepcopy(v), v)
if __name__ == "__main__": if __name__ == "__main__":

Loading…
Cancel
Save