soegaard / pyffi

Use Python from Racket
27 stars 2 forks source link

Issues with Attributes on `provide`d Python Objects #2

Open abhillman opened 10 months ago

abhillman commented 10 months ago

Observed behavior: If a python object is exported, there isn't a way to access its attributes.

Expected behavior: I'm not sure. Ideally attributes could be accessed. Short of that, some documentation about best-practice for being able to access the attribute of a python object from a different module.

Repro:

Store this in py-point.rkt:

#lang racket

(require pyffi)

;; initialize the python interpreter
(initialize)
(post-initialize)

;; define `Point` via `collections.namedtuple`
(import collections)
(run* "Point = collections.namedtuple('Point', 'x y')")

;; create a `Point` object and bind it 
(define py-point (run "Point(1, 2)"))

(provide py-point)

Store this in main.rkt:

#lang racket

(require "py-point.rkt")

;; Print out the point; this works.
(print py-point)

;; Attempt to access an attr on `py-point`; this fails.
(print py-point.x)

Run main.rkt:

$ racket main.rkt
main.rkt:9:7: py-point.x: unbound identifier
  in: py-point.x
  location...:
   main.rkt:9:7

Other stuff

Note that within py-point.rkt, attributes can be accessed:

$ cat py-point.rkt
#lang racket

(require pyffi)

;; initialize the python interpreter
(initialize)
(post-initialize)

;; define `Point` via `collections.namedtuple`
(import collections)
(run* "Point = collections.namedtuple('Point', 'x y')")

;; create a `Point` object and bind it
(define py-point (run "Point(1, 2)"))

(provide py-point)

(print py-point.x)

$ racket py-point.rkt
1
abhillman commented 10 months ago

Workaround appears to be to add a (require pyffi) statement in a module that wishes to access attributes on a given object. Ideally there's something better (I'm guessing there is, but I have not discovered it yet) that might be something like:

(require (prefix-in pyffi/ pyffi))
(require "py-point.rkt")

;; this doesn't exist, but I'm guessing something like it might
(pyffi/read-attr py-point 'x)

Related: https://github.com/soegaard/pyffi/issues/3

abhillman commented 10 months ago

Oh cool! These seem to work as workarounds:

(require (prefix-in pyffi/ pyffi))
(require "py-point.rkt")

;; allows for attribute fetching with `pyffi` via `prefix-in`
(pyffi/getattr py-point "x")
(require pyffi)
(require "py-point.rkt")

;; allows for attribute fetching with `pyffi` via `prefix-in`
(getattr py-point "x")
soegaard commented 10 months ago

In this examle:

#lang racket
(require "py-point.rkt")
(print py-point.x)

The (require "py-point.rkt") imports the identifier py-point. In #lang racket the syntax py-point.x is read as a the name of a single identifier py-point.x with no relation to py-point, therefore you get an "unbound identifier" error.

Now in #lang racket a reference to an unbound identifier py-point.x expands into (#%top . py-point.x). The standard binding of #%top simply generates the "unbound identifier" error. However, we can use our own #%top to turn py-point.x into something that references the attribute x of py-point.

One fix:

#lang racket
(require "py-point.rkt" (only-in pyffi #%top))
(print py-point.x)

which will display

(obj "Point" : Point(x=1, y=2))

Another way:

#lang racket
(require "py-point.rkt" pyffi)
(print py-point.x)
abhillman commented 10 months ago

Wow! Very nice explanation and thank you!

soegaard commented 10 months ago

Note that you can do this:

#lang racket
(require "py-point.rkt" 
               (only-in pyffi #%top)
               (prefix-in pyffi/ pyffi))
(print py-point.x)

If you prefer to have the pyffi/ prefix and still want dot notation to work.