-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
github-actions
committed
Dec 24, 2023
1 parent
0b83a2c
commit eee5cd2
Showing
19 changed files
with
3,452 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Sphinx build info version 1 | ||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. | ||
config: 50c9068fa39613d2f3c4573889fa877b | ||
tags: 645f666f9bcd5a90fca523b33c5a78b7 |
186 changes: 186 additions & 0 deletions
186
docs/version/update-dep-and-compat-versions/html/_sources/index.rst.txt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
R's R6 classes in rpy2/Python | ||
============================= | ||
|
||
Repository: https://github.com/rpy2/rpy2-R6 | ||
|
||
.. contents:: Table of Contents | ||
|
||
|
||
Introduction | ||
------------ | ||
|
||
[R6](https://r6.r-lib.org/) is one of the OOP systems available | ||
to R programmers, and is relatively similar to other OOP systems | ||
non-R programmers might be familiar with. In the R6 authors' | ||
own words "this style of programming is also sometimes referred | ||
to as classical object-oriented programming". | ||
|
||
This Python package is using :mod:`rpy2` to provide Pythonic | ||
wrappers for R6 classes and instances, and make the integration | ||
of R6-based class models into Python code as natural and as | ||
readable as possible. | ||
|
||
OOP in R's R6 is relatively different from the way Python classes | ||
are defined and are working. | ||
In R6 a class definition is an instance of class | ||
`ClassFactoryGenerator`, that has at one method (`new()`) acting | ||
as a factory that can create instances inheriting from class `R6`. | ||
One can see this a some form of metaprogramming, with instances class | ||
`ClassFactorGenerator` acting like metaclasses by defining the | ||
corresponding child classes dynamically. | ||
|
||
Despite the challenge, we are trying to have a wrapper | ||
that is both faithful to the R code, since this can make | ||
debugging or using the R documentation easier, while also | ||
allowing a rather Pythonic feel. Currently there are two alternative wrappers: | ||
:mod:`rpy2_r6.r6a` and :mod:`rpy2_r6.r6b`. | ||
|
||
|
||
A first example | ||
--------------- | ||
|
||
To demonstrate how it is working, we use the R package | ||
`scales` (a dependency of `ggplot2`). | ||
|
||
.. doctest:: first_example | ||
|
||
import rpy2.rinterface | ||
import rpy2.robjects | ||
from rpy2.robjects.packages import importr | ||
scales = importr('scales') | ||
|
||
In this short tutorial we start with `Range`, an instance of class | ||
`ClassFactoryGenerator` in the package `scales`, and we wrap | ||
it to be an instance of our matching Python class. | ||
|
||
In R, creating a new instance of `Range` would be done with: | ||
|
||
.. code-block:: r | ||
library(scales) | ||
obj <- Range$new() | ||
That instance `obj` is of R class `("Range", "R6")`, meaning that | ||
this is an instance of class "Range" inheriting from class "R6" (on the | ||
R side). | ||
|
||
.. code-block:: rconsole | ||
> class(Range$new()) | ||
c("Range", "R6") | ||
r6a | ||
--- | ||
|
||
The wrapper was kindly contributed in a PR for rpy2 by Matthew Wardrop (@matthewwardrop), | ||
and was only slightly adapted to use the recent `SupportsSEXP` interface in rpy2. | ||
|
||
In this approach the class `ClassFactoryGenerator` in R is mapped to :class:`rpy2_r6.r6a.R6Class`. | ||
|
||
.. testcode:: first_example | ||
|
||
range_factory_r6a = r6a.R6Class(scales.Range) | ||
|
||
|
||
.. testcode:: first_example | ||
|
||
range_r6a = range_factory_r6a.new() | ||
|
||
The instance is a generic :class:`rpy2_r6.r6a.R6` in Python, with the R class name available | ||
through a property of that object: | ||
|
||
.. doctest:: | ||
|
||
>>> type(range_r6a) | ||
rpy2_r6.r6a.R6 | ||
>>> range_r6b.class_name | ||
'Scale' | ||
|
||
The properties and methods available for the object in R are dynamically resolved from the Python side, | ||
and private attributes in the R6 definitions have an underscore prefixed to their attribute name in Python. | ||
For example, the private method R6 `clone` for the object is accessed with `_clone` in Python: | ||
|
||
.. testcode:: first_example | ||
|
||
range_r6a._clone() | ||
|
||
|
||
r6b | ||
--- | ||
|
||
.. testcode:: first_example | ||
|
||
range_factory_r6b = r6b.R6DynamicClassGenerator(scales.Range) | ||
|
||
.. note:: | ||
|
||
Automatic wrapping could be achieved through rpy2's own conversion | ||
system. It is planned to offer the option to facilitate this in this package. | ||
|
||
|
||
We are able to write essentially the same code in Python: | ||
|
||
.. testcode:: first_example | ||
|
||
range_r6b = range_factory_r6b.new() | ||
|
||
The type of the resulting object is a Python class `Range`: | ||
|
||
.. doctest:: | ||
|
||
>>> type(range_r6b) | ||
rpy2_r6.r6b.Range | ||
|
||
|
||
Dynamic class generation | ||
^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
You'll note that we never explicitly defined the class `Range`; it | ||
was dynamically created by our package | ||
to reflect the R class definition from | ||
the `ClassFactoryGenerator` instance. | ||
|
||
The method `new` is a factory: | ||
|
||
.. testcode:: first_example | ||
|
||
myrange = range_factory_r6b.new | ||
|
||
The underlying class is: | ||
|
||
.. testcode:: first_example | ||
|
||
Range = range_factory_r6b.__R6CLASS__ | ||
|
||
The lineage (inheritance tree) for the Python class `Range` is | ||
dynamically generated to match the one for the R6 class | ||
definition in R. | ||
|
||
.. doctest:: first_example | ||
|
||
>>> import inspect | ||
>>> inspect.getmro(Range) | ||
(rpy2_r6.r6b.Range, | ||
rpy2_r6.r6b.R6, | ||
rpy2.rinterface_lib.sexp.SupportsSEXP, | ||
object) | ||
|
||
An other example with a longer lineage: | ||
|
||
.. doctest:: first_example | ||
|
||
>>> DiscreteRange = r6b.R6DynamicClassGenerator(scales.DiscreteRange).new | ||
>>> inspect.getmro(DiscreteRange) | ||
(rpy2_r6.r6b.DiscreteRange, | ||
rpy2_r6.r6b.Range, | ||
rpy2_r6.r6b.R6, | ||
rpy2.rinterface_lib.sexp.SupportsSEXP, | ||
object) | ||
|
||
|
||
Indices and tables | ||
================== | ||
|
||
* :ref:`genindex` | ||
* :ref:`search` |
Oops, something went wrong.