Cangjie-Python Interoperability
To be compatible with powerful computing and AI ecosystems, Cangjie supports interoperability with Python. The Python interoperability provides capabilities for users through the ffi.python library in the std module.
Currently, Python interoperability can be used only on the Linux platform and supports only the CJNative backend of the Cangjie compiler.
Global Resources and Usage of Python
Providing Built-in Function Classes and Global Resources
Code prototype:
public class PythonBuiltins {
...
}
public let Python = PythonBuiltins()
The interfaces provided by the Python library cannot ensure concurrency security. When Python is called asynchronously (the ID
s of system threads are inconsistent), the PythonException
exception is thrown.
During Python initialization, the GIL global interpreter lock is locked based on the current OS thread. If the Cangjie thread (including the main
thread) where the executed code is located is locked,
The Cangjie thread is scheduled on the OS thread (the ID
of the OS thread changes). When Python attempts to check the GIL again, the thread status is verified and the GIL is found.
The ID
of the OS thread saved in the status is inconsistent with that of the current OS thread. As a result, an internal error is triggered and the program breaks down.
Python interoperability uses a large amount of native code of the Python library. Stack protection cannot be performed on the code on the Cangjie side. The default size of the Cangjie stack protection is 64 KB. When the Python C API is called, the native code may exceed the default stack size. As a result, overflow occurs and unexpected results are triggered. Before executing Python interoperability code, you are advised to
set the default stack size of Cangjie to at least 1 MB: export cjStackSize=1MB
.
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
Python.unload()
return 0
}
Providing the Python Library Log Class PythonLogger
Code prototype:
public class PythonLogger <: Logger {
mut prop level: LogLevel {...}
public func setOutput(output: io.File): Unit {} // do nothing
public func trace(msg: String): Unit {...}
public func debug(msg: String): Unit {...}
public func info(msg: String): Unit {...}
public func warn(msg: String): Unit {...}
public func error(msg: String): Unit {...}
public func log(level: LogLevel, msg: String): Unit {...}
}
public let PYLOG = PythonLogger()
The Logger class is declared as follows:
- The
PythonLogger
implements theLogger
interface only for printing output and printing level control. Logs are not dumped to log files. - If
setOutput
is empty, log dump files are not supported. - The output of interfaces such as
info/warn/error
starts with the corresponding prefix. - The default print level of
PythonLogger
isLogLevel.WARN
. - The
PYLOG.error(msg)
andlog(LogLevel.ERROR, msg)
interfaces throw thePythonException
exception.
Example:
import std.ffi.python.*
import std.log.*
main(): Int64 {
PYLOG.level = LogLevel.WARN // Only logs of the warn level and above are printed.
PYLOG.info("log info")
PYLOG.warn("log warn")
try {
PYLOG.error("log error")
} catch(e: PythonException) {}
PYLOG.log(LogLevel.INFO, "loglevel info")
PYLOG.log(LogLevel.WARN, "loglevel warn")
try {
PYLOG.log(LogLevel.ERROR, "loglevel error")
} catch(e: PythonException) {}
return 0
}
Execution result:
WARN: log warn
ERROR: log error
WARN: loglevel warn
ERROR: loglevel error
Providing the Python Library Exception Class PythonException
Code prototype:
public class PythonException <: Exception {
public init() {...}
public init(message: String) {...}
}
The PythonException
is described as follows:
- The usage of
PythonException
is the same as that of the inheritedException
except the exception prefix. - When an internal exception occurs in Python, the external system can capture the exception through
try-catch
. If the exception is not captured, the exception stack is printed and the program exits. The return value is 1.
Example:
import std.ffi.python.*
import std.log.*
main(): Int64 {
try {
Python.load("/usr/lib/", loglevel: LogLevel.INFO)
} catch(e: PythonException) {
print("${e}") // PythonException: "/usr/lib/" does not exist or the file path is invalid.
}
return 0
}
Providing the Python Library Version Information Class Version
Code prototype:
public struct Version <: ToString {
public init(major: Int64, minor: Int64, micro: Int64)
public func getMajor(): Int64
public func getMinor(): Int64
public func getMicro(): Int64
public func getVersion(): (Int64, Int64, Int64)
public func toString(): String
}
The declaration of the Version
class is as follows:
- The
Version
version information consists of three parts:major version
,minor version
, andmicro version
. - The
Version
version is initialized only by using the constructor. Once defined, it cannot be modified. - The
toString
interface is provided for direct printing. - The
getVersion
interface is provided to obtain the tuple format of the version.
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
var version = Python.getVersion()
print("${version}")
var tuple_version = version.getVersion()
Python.unload()
return 0
}
PythonBuiltins
Built-in Function Class
Importing and Loading the Python Library
Code prototype:
public class PythonBuiltins {
public func load(loglevel!: LogLevel = LogLevel.WARN): Unit
public func load(path: String, loglevel!: LogLevel = LogLevel.WARN): Unit
public func isLoad(): Bool
public func unload(): Unit
}
public let Python = PythonBuiltins()
The following statements about loading and unloading are as follows:
- The
load
function is implemented in overloading mode. It can be loaded without participation or loaded from a specified dynamic library path. An optional parameter is provided to configure thePythonLogger
print level. If the parameter is not configured, thePYLOG
print level is reset to thewarn
print level. - The
load()
function is used for Python-related preparations and must be called before Python interoperability. For details about how to query the dynamic library, see the dynamic library loading policy. - For the
load(path: String)
function, you need to configure the dynamic library pathpath
.path
specifies the dynamic library file (for example,/usr/lib/libpython3.9.so
) and cannot be set to a directory or a non-dynamic library file. - When the
load
function fails, thePythonException
exception is thrown. If the program still needs to continue, pay attention totry-catch
. - The
unload
function is called after Python interoperability is complete. Otherwise, related resources may be leaked. - The loading and unloading operations need to be called only once. If they are called for multiple times, only the first invoking takes effect.
- The
isload()
function is used to determine whether the Python library is loaded.
Example:
load
and unload
:
import std.ffi.python.*
main(): Int64 {
Python.load()
Python.unload()
Python.load("/usr/lib/libpython3.9.so")
Python.unload()
return 0
}
isLoad
Function:
import std.ffi.python.*
main(): Int64 {
print("${Python.isLoad()}\n") // false
Python.load()
print("${Python.isLoad()}\n") // true
Python.unload()
return 0
}
Loading Policy of Dynamic Library
The Python library depends on the official dynamic link library libpython3.x.so
of Python. The recommended version is 3.9.2. Python3.0 and later versions are supported.
Obtaining the dynamic library from the Python source code:
# In the Python source code path:
./configure --enable-shared --with-system-ffi --prefix=/usr
make
make install
The dynamic library of Python is automatically searched as follows:
- Use the specified environment variable:
export PYTHON_DYNLIB=".../libpython3.9.so"
- If the environment variable is not specified, search for the dependency of the executable file.
- Ensure that the executable file
python3
can be executed properly (the path has been added to the PATH environment variable). Query the dynamic library dependency of the Python3 executable file. - Python executable files that are not dependent on dynamic libraries cannot be used. (Python executable files that are not compiled using
--enable-shared
during source code compilation do not depend on dynamic libraries.)
$ ldd $(which python3)
...
libpython3.9d.so.1.0 => /usr/local/lib/libpython3.9d.so.1.0 (0x00007f499102f000)
...
- If the executable file dependency cannot be found, search for the dependency from the default dynamic library query path.
["/lib", "/usr/lib", "/usr/local/lib"]
The name of the dynamic library queried in the path must comply with the naming format of libpythonX.Y.so
. X
Y
indicates the major version number and minor version number. The following suffixes are supported: d.so
, m.so
, dm.so
, and .so
. The supported version is later than Python 3.0 and earlier than or equal to Python 3.10. Example:
libpython3.9.so
libpython3.9d.so
libpython3.9m.so
libpython3.9dm.so
Example:
import std.ffi.python.*
import std.log.*
main(): Int64 {
Python.load(loglevel: LogLevel.INFO)
print("${Python.getVersion()}\n")
Python.unload()
return 0
}
You can enable the INFO
level printing of Python and view the search process of the Python library path.
# Specifying .so by Using Environment Variables
$ export PYTHON_DYNLIB=/root/code/python_source_code/Python-3.9.2/libpython3.9d.so
$ cjc ./main.cj -o ./main && ./main
INFO: Try to get libpython path.
INFO: Found PYTHON_DYNLIB value: /root/code/python_source_code/Python-3.9.2/libpython3.9d.so
...
# Find dynamic libraries by executable file dependency.
INFO: Try to get libpython path.
INFO: Can't get path from environment PYTHON_DYNLIB, try to find it from executable file path.
INFO: Exec cmd: "ldd $(which python3)":
INFO: ...
libpython3.9d.so.1.0 => /usr/local/lib/libpython3.9d.so.1.0 (0x00007fbbb5014000)
...
INFO: Found lib: /usr/local/lib/libpython3.9d.so.1.0.
INFO: Found exec dependency: /usr/local/lib/libpython3.9d.so.1.0
...
# Search for the dynamic library in the system path.
$ unset PYTHON_DYNLIB
$ cjc ./main.cj -o ./main && ./main
INFO: Can't get path from environment PYTHON_DYNLIB, try to find it from executable file path.
INFO: Can't get path from executable file path, try to find it from system lib path.
INFO: Find in /lib.
INFO: Found lib: /lib/libpython3.9.so.
...
# Failed to find the dynamic library.
$ cjc ./main.cj -o ./main && ./main
INFO: Can't get path from environment PYTHON_DYNLIB, try to find it from executable file path.
INFO: Can't get path from executable file path, try to find it from system lib path.
INFO: Find in /lib.
INFO: Can't find lib in /lib.
INFO: Find in /usr/lib.
INFO: Can't find lib in /usr/lib.
INFO: Find in /usr/local/lib.
INFO: Can't find lib in /usr/local/lib.
An exception has occurred:
PythonException: Can't get path from system lib path, load exit.
at std/ffi/python.std/ffi/python::(PythonException::)init(std/core::String)(stdlib/std/ffi/python/Python.cj:82)
at std/ffi/python.std/ffi/python::(PythonBuiltins::)load(std/log::LogLevel)(stdlib/std/ffi/python/Python.cj:127)
at default.default::main()(/root/code/debug/src/main.cj:5)
getVersion()
Function
Function prototype:
public func getVersion(): Version
Interface description:
- The
getVersion()
function is used to obtain the current Python version.
Return values of input parameters:
- The
getVersion()
function has no parameter and returns aVersion
object.
Exceptions:
- Ensure that the
load
function has been called. Otherwise, the returned version number is0.0.0
.
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
var version = Python.getVersion()
print("${version}")
var tuple_version = version.getVersion()
Python.unload()
return 0
}
Import()
Function
Function prototype:
public func Import(module: String): PyModule
Return values of input parameters:
- The
Import
function receives an input parameter of theString
type, that is, a module name, and returns an object of thePyModule
type.
Exceptions:
- Ensure that the load function has been called for the
Import
function. Otherwise, the returnedPyModule
object is unavailable (isAvaliable()
isfalse
). - If the corresponding module cannot be found, an error is reported and the returned
PyModule
object is unavailable (isAvaliable()
isfalse
).
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
var sys = Python.Import("sys")
if (sys.isAvailable()) {
print("Import sys success\n")
}
// Import the test.py file in the current folder.
var test = Python.Import("test")
if (test.isAvailable()) {
print("Import test success\n")
}
var xxxx = Python.Import("xxxx")
if (!xxxx.isAvailable()) {
print("Import test failed\n")
}
Python.unload()
return 0
}
Execution result:
Import sys success
Import test success
Import test failed
Eval()
Function
Function prototype:
public func Eval(cmd: String, module!: String = "__main__"): PyObj
Interface description:
- The
Eval()
function is used to create a Python data type.
Return values of input parameters:
Eval()
receives acmd
command of theString
type and returns thePyObj
format of the command result.Eval()
accepts a specified domain of theString
type. The default domain is"__main__"
.
Exceptions:
- Ensure that the
load
function has been called. Otherwise, the returnedPyObj
object is unavailable (isAvaliable()
isfalse
). - If the command received by
Eval()
fails to be executed, Python reports an error and the returnedPyObj
object is unavailable (isAvaliable()
isfalse
).
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
var a = Python.Eval("123")
if (a.isAvailable()) {
Python["print"]([a])
}
var b = Python.Eval("x = 123") // The expression in `Eval` needs have a return value.
if (!b.isAvailable()) {
print("b is unavailable.\n")
}
Python.unload()
return 0
}
Execution result:
123
b is unavailable.
index []
Operator Overloading
Interface description:
- The
[]
function provides the capability of calling other built-in functions of Python.
Return values of input parameters:
- The input parameter of the
[]
function accepts the name of a built-in function of theString
type, and the return type isPyObj
.
Exception Handling:
- Ensure that the
load
function has been called. Otherwise, the returnedPyObj
object is unavailable (isAvaliable()
isfalse
). - If the specified function name is not found, an error is reported and the returned
PyObj
object is unavailable (isAvaliable()
isfalse
).
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
if (Python["type"].isAvailable()) {
print("find type\n")
}
if (!Python["type1"].isAvailable()) {
print("cant find type1\n")
}
Python.unload()
return 0
}
Execution result:
find type
WARN: Dict key "type1" not found!
cant find type1
Type Mapping
The interoperability between Python and Cangjie is developed based on C APIs. The data type mapping between Python and C is implemented through the PyObject
structure pointer, and a series of interfaces for different data types are provided. Compared with the C language, Cangjie has the advantage of object-oriented programming. Therefore, the PyObject
structure pointer is encapsulated as a parent class and inherited by different data types.
Type Mapping Table
Mapping from the Cangjie type to the Python type:
Cangjie Type | Python Type |
---|---|
Bool | PyBool |
UInt8/Int8/Int16/UInt16/Int32/UInt32/Int64/UInt64 | PyLong |
Float32/Float64 | PyFloat |
Rune/String | PyString |
Array< PyObj > | PyTuple |
Array | PyList |
HashMap | PyDict |
HashSet | PySet |
Mapping from the Python type to the Cangjie type:
Python Type | Cangjie Type |
---|---|
PyBool | Bool |
PyLong | Int64/UInt64 |
PyFloat | Float64 |
PyString | String |
PyTuple | - |
PyList | Array |
PyDict | HashMap |
PySet | HashSet |
PyFFIType
Interface of Python FFI Library Generic Constraint
public interface PyFFIType { }
- Some classes introduce generics. To restrict the use of generics, the abstract interface
PyFFIType
is introduced. - This interface has no abstract member function and is implemented or inherited only by
PyObj
andCjObj
. This interface cannot be implemented outside the package. If you customize a class and implement this interface, undefined behavior may occur.
PyObj
Class
It corresponds to the PyObject
structure in the Python library and provides common interfaces for subdivided data types, such as member variable access, function access, and character string conversion to Cangjie.
Class prototype:
public open class PyObj <: ToString & PyFFIType {
public func isAvailable(): Bool { ... }
public open operator func [](key: String): PyObj { ... }
public open operator func [](key: String, value!: PyObj): Unit { ... }
public operator func ()(): PyObj { ... }
public operator func ()(kargs: HashMap<String, PyObj>): PyObj { ... }
public operator func ()(args: Array<PyObj>): PyObj { ... }
public operator func ()(args: Array<PyObj>, kargs: HashMap<String, PyObj>): PyObj { ... }
public operator func ()(args: Array<CjObj>): PyObj { ... }
public operator func ()(args: Array<CjObj>, kargs: HashMap<String, PyObj>): PyObj { ... }
public operator func +(b: PyObj): PyObj { ... }
public operator func -(b: PyObj): PyObj { ... }
public operator func *(b: PyObj): PyObj { ... }
public operator func /(b: PyObj): PyObj { ... }
public operator func **(b: PyObj): PyObj { ... }
public operator func %(b: PyObj): PyObj { ... }
public open func toString(): String { ... }
public func hashCode(): Int64 { ... }
public operator func ==(right: PyObj): Bool { ... }
public operator func !=(right: PyObj): Bool { ... }
}
Description of the PyObj
Class
-
The
PyObj
class does not provide constructors for external systems. This class cannot be inherited outside the package. If a user customizes a class and implements the interface, undefined behavior may occur. -
public func isAvailable(): Bool { ... }
:- The
isAvailable
interface is used to determine whether thePyObj
is available (that is, whether the encapsulated C pointer isNULL
).
- The
-
public open operator func [](key: String): PyObj { ... }
:[](key)
is used to access members of a Python class or a module.- If
PyObj
is unavailable (isAvaliable()
isfalse
), an exception is thrown. - If
key
does not exist inPyObj
, Python prints the corresponding error and returns an unavailablePyObj
class object (isAvaliable()
isfalse
).
-
public open operator func [](key: String, value!: PyObj): Unit { ... }
:[](key, value)
: Set the member variables of the Python class and module tovalue
.- If
PyObj
is unavailable (isAvaliable()
isfalse
), an exception is thrown. - If the
PyObj
does not contain the correspondingkey
, the Python side prints the corresponding error. - If the value of
value
is an unavailable object (isAvaliable()
isfalse
), the correspondingkey
is deleted from the module or class.
-
The
()
parenthesis operator is overloaded to call the function of the object.- If
PyObj
is unavailable (isAvaliable()
isfalse
), an exception is thrown. - If
PyObj
is an object that cannot be called, Python reports an error and returns an unavailablePyObj
class object (isAvaliable()
isfalse
). ()
accepts function calls without parameters.- The
([...])
parameter supports at least one parameter. The parameter type can beCjObj
orPyObj
. Note thatCjObj
andPyObj
cannot be used together when multiple parameters are transferred. - If the parameter contains an unavailable object (
isAvaliable()
isfalse
), an exception is thrown to prevent unpredictable program breakdown on the Python side. - The
()
operator supportskargs
, which corresponds to the variable naming parameter design of Python. It is transferred through aHashMap
. Thekey
typeString
is configured as the variable name, and thevalue
type PyObj is configured as the parameter value.
- If
-
Binary operator overloading:
-
+
(addition of two variables):- Basic data types:
PyString
andPyBool/PyLong/PyFloat
cannot be added together. Other types can be added together. - Advanced data types:
PyDict/PySet
cannot be added to any type.PyTuple/PyList
can only be added to itself.
- Basic data types:
-
-
(subtraction of two variables):- Basic data types:
PyString
andPyBool/PyLong/PyFloat/PyString
do not support subtraction. Other types support subtratcion. - Advanced data types:
PyDict/PySet/PyTuple/PyList
does not support subtraction from any type.
- Basic data types:
-
*
(multiplication of two variables):- Basic data types:
PyString
andPyFloat/PyString
do not support multiplication. Other types support multiplication. - Advanced data types:
PyDict/PySet
cannot be multiplied by any type.PyTuple/PyList
can be multiplied only byPyLong/PyBool
.
- Basic data types:
-
/
(division of two variables):- Basic data types:
PyString
andPyBool/PyLong/PyFloat/PyString
cannot be divided by each other. Other types can be divided by each other. If the divisor is 0 (False
is interpreted as 0 on the Python side and cannot be used as a divisor), an error is printed on the Python side. - Advanced data types:
PyDict/PySet/PyTuple/PyList
cannot be divided by any type.
- Basic data types:
-
**
(exponent calculation):- Basic data types:
PyString
andPyBool/PyLong/PyFloat/PyString
do not support exponential calculation. Other data types support exponential calculation. - Advanced data types:
PyDict/PySet/PyTuple/PyList
do not support exponentiation.
- Basic data types:
-
%
(remainder operation):- Basic data types:
PyString
andPyBool/PyLong/PyFloat/PyString
do not support the remainder operation. Other data types support the remainder operation. If the divisor is 0 (False
is interpreted as 0 in Python and cannot be used as a divisor), an error message is displayed in Python. - Advanced data types:
PyDict/PySet/PyTuple/PyList
and all types do not support the modulo operation.
- Basic data types:
-
All the preceding errors are recorded at the warn level, and the returned
PyObj
is unavailable (isAvaliable()
isfalse
).
-
-
public open func toString(): String { ... }
:- The
toString
function can return the Python data type as a string, and the basic data type is returned in Python style. - If
PyObj
is unavailable (isAvaliable()
isfalse
), an exception is thrown.
- The
-
The
hashCode
function is the encapsulated Pythonhash
algorithm and returns an Int64 hash value. -
The
==
operator is used to determine whether twoPyObj
objects are the same. The!=
operator is used to determine whether twoPyObj
objects are the same. If the interface comparison fails, the==
returnsfalse
and captures the Python error. If the two compared objects are unavailable, an exception is thrown.
Example:
test01.py file:
a = 10
def function():
print("a is", a)
def function02(b, c = 1):
print("function02 call.")
print("b is", b)
print("c is", c)
Cangjie file main.cj in the same directory:
import std.ffi.python.*
import std.collection.*
main(): Int64 {
Python.load()
// Create an unavailable value.
var a = Python.Eval("a = 10") // SyntaxError: invalid syntax
print("${a.isAvailable()}\n") // false
// Uncallable value `b` be invoked
var b = Python.Eval("10")
b() // TypeError: 'int' object is not callable
// Import .py file.
var test = Python.Import("test01")
// `get []` get value of `a`.
var p_a = test["a"]
print("${p_a}\n") // 10
// `set []` set the value of a to 20.
test["a"] = Python.Eval("20")
test["function"]() // a is 20
// Call function02 with a named argument.
test["function02"]([1], HashMap<String, PyObj>([("c", 2.toPyObj())]))
// Set `a` in test01 to an unavailable value, and `a` will be deleted.
test["a"] = a
test["function"]() // NameError: name 'a' is not defined
Python.unload()
0
}
CjObj
Interface
Interface prototype and type extension:
public interface CjObj <: PyFFIType {
func toPyObj(): PyObj
}
extend Bool <: CjObj {
public func toPyObj(): PyBool { ... }
}
extend Rune <: CjObj {
public func toPyObj(): PyString { ... }
}
extend Int8 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend UInt8 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend Int16 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend UInt16 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend Int32 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend UInt32 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend Int64 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend UInt64 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend Float32 <: CjObj {
public func toPyObj(): PyFloat { ... }
}
extend Float64 <: CjObj {
public func toPyObj(): PyFloat { ... }
}
extend String <: CjObj {
public func toPyObj(): PyString { ... }
}
extend<T> Array<T> <: CjObj where T <: PyFFIType {
public func toPyObj(): PyList<T> { ... }
}
extend<K, V> HashMap<K, V> <: CjObj where K <: Hashable & Equatable<K> & PyFFIType {
public func toPyObj(): PyDict<K, V> { ... }
}
extend<T> HashSet<T> <: CjObj where T <: Hashable, T <: Equatable<T> & PyFFIType {
public func toPyObj(): PySet<T> { ... }
}
Description of the CjObj
Class
The CjObj
interface is implemented by all basic data types and toPyObj
is extended. The CjObj
interface can be converted to the corresponding Python data type.
Mapping between PyBool
and Bool
Class prototype:
public class PyBool <: PyObj {
public init(bool: Bool) { ... }
public func toCjObj(): Bool { ... }
}
Description of the PyBool
Class
- The
PyBool
class is inherited from thePyObj
class. ThePyBool
class has all the interfaces of the parent class. PyBool
can be constructed only by using theBool
type of Cangjie.- The
toCjObj
interface convertsPyBool
to the Cangjie data typeBool
.
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyBool`.
var a = PyBool(true) // The type of `a` is `PyBool`.
var b = Python.Eval("True") // The type of `b` is `PyObj` and needs to be matched to `PyBool`.
var c = true.toPyObj() // The type of `c` is `PyBool`, which is the same as `a`.
print("${a}\n")
if (a.toCjObj()) {
print("success\n")
}
if (b is PyBool) {
print("b is PyBool\n")
}
Python.unload()
0
}
Execution result:
True
success
b is PyBool
Mapping between PyLong
and Integers
Class prototype:
public class PyLong <: PyObj {
public init(value: Int64) { ... }
public init(value: UInt64) { ... }
public init(value: Int32) { ... }
public init(value: UInt32) { ... }
public init(value: Int16) { ... }
public init(value: UInt16) { ... }
public init(value: Int8) { ... }
public init(value: UInt8) { ... }
public func toCjObj(): Int64 { ... }
public func toInt64(): Int64 { ... }
public func toUInt64(): UInt64 { ... }
}
Description of the PyLong
Class
-
The
PyLong
class is inherited from thePyObj
class. ThePyLong
class has all the interfaces of the parent class. -
The
PyLong
supports the construction of input parameters of the integer type from all Cangjie. -
The
toCjObj
andtoInt64
interfaces convertPyLong
to theInt64
type. -
The
toUInt64
interface convertsPyLong
toUInt64
. -
The
PyLong
type is converted to the Cangjie type to the 8-byte type. ThePyLong
type cannot be converted to the lower-byte type. -
Overflow/Underflow problems:
- If the original value of
toInt64
(UInt64
is used to assign a value and no error is reported) exceeds theInt64
range, an overflow occurs. - If the original value of
toUInt64
(Int64
is used to assign a value and no error is reported) exceeds theUInt64
range, an overflow occurs.
- If the original value of
-
Currently,
PyLong
does not support large number processing.
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyLong`.
var a = PyLong(10) // The type of `a` is `PyLong`.
var b = Python.Eval("10") // The type of `b` is `PyObj` and needs to be matched to `PyLong`.
var c = 10.toPyObj() // The type of `c` is `PyLong`, which is the same as `a`.
print("${a}\n")
if (a.toCjObj() == 10 && a.toUInt64() == 10) {
print("success\n")
}
if (b is PyLong) {
print("b is PyLong\n")
}
Python.unload()
0
}
Execution result:
10
success
b is PyLong
Mapping between PyFloat
and Floating Points
Class prototype:
public class PyFloat <: PyObj {
public init(value: Float32) { ... }
public init(value: Float64) { ... }
public func toCjObj(): Float64 { ... }
}
Description of the PyFloat
Class
- The
PyFloat
class is inherited from thePyObj
class. ThePyFloat
class has all the interfaces of the parent class. PyBool
can be constructed using data of the CangjieFloat32
/Float64
type.- To ensure precision, the
toCjObj
interface convertsPyFloat
to the Cangjie data typeFloat64
.
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyLong`.
var a = PyFloat(3.14) // The type of `a` is `PyFloat`.
var b = Python.Eval("3.14") // The type of `b` is `PyObj` and needs to be matched to `PyFloat`.
var c = 3.14.toPyObj() // The type of `c` is `PyFloat`, which is the same as `a`.
print("${a}\n")
if (a.toCjObj() == 3.14) {
print("success\n")
}
if (b is PyFloat) {
print("b is PyFloat\n")
}
Python.unload()
0
}
Execution result:
3.14
success
b is PyFloat
Mapping between PyString
and Characters and Character Strings
Class prototype:
public class PyString <: PyObj {
public init(value: String) { ... }
public init(value: Rune) { ... }
public func toCjObj(): String { ... }
public override func toString(): String { ... }
}
Description of the PyString
Class
- The
PyString
class is inherited from thePyObj
class. ThePyString
class has all the interfaces of the parent class. PyString
can be constructed using data of the CangjieRune
/String
type.- The
toCjObj
/toString
interface is used to convertPyString
to theString
data type.
Example
import std.ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyString`.
var a = PyString("hello python") // The type of `a` is `PyString`.
var b = Python.Eval("\"hello python\"") // The type of `b` is `PyObj` and needs to be matched to `PyString`.
var c = "hello python".toPyObj() // The type of `c` is `PyString`, which is the same as `a`.
print("${a}\n")
if (a.toCjObj() == "hello python") {
print("success\n")
}
if (b is PyString) {
print("b is PyString\n")
}
Python.unload()
0
}
Execution result:
hello python
success
b is PyString
PyTuple
Type
Class prototype:
public class PyTuple <: PyObj {
public init(args: Array<PyObj>) { ... }
public operator func [](key: Int64): PyObj { ... }
public func size(): Int64 { ... }
public func slice(begin: Int64, end: Int64): PyTuple { ... }
}
Description of the PyTuple
Class
- The
PyTuple
type is the same as the tuple type in Python, that is, the(...)
variable is used in Python code. - The
PyTuple
class is inherited from thePyObj
class. ThePyTuple
class has all the interfaces of the parent class. PyTuple
can be constructed using CangjieArray
. The element type ofArray
must bePyObj
(different data types of Python can be transferred usingPyObj
, that is, different data types of different elements inTuple
are compatible). If a member contains an unavailable object, an exception is thrown.- Overloading of the
[]
operator:- The input parameter type of
[]
in the parent classPyObj
isString
. When this class is called, the internal member variables or functions of thePython
tuple type can be accessed or set. - The subclass
PyTuple
can use[]
to access elements. If the badgekey
exceeds the range of [0, size ()), an error is reported and an unavailablePyObj
object is returned. - The
set []
operator is not reloaded because the Python tuple is an immutable object.
- The input parameter type of
- The
size
function is used to obtain the length ofPyTuple
. - The
slice
function is used to tailor the sourcePyTuple
and return a newPyTuple
. If the input parametersbegin
andend
ofslice
are not in the range of [0, size ()), the sourcePyTuple
is still tailored.
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyTuple`.
var a = PyTuple(["Array".toPyObj(), 'a'.toPyObj(), 1.toPyObj(), 1.1.toPyObj()])
var b = match (Python.Eval("('Array', 'a', 1, 1.1)")) {
case val: PyTuple => val
case _ => throw PythonException()
}
// Usage of size
println(a.size()) // 4
// Usage of slice
println(a.slice(1, 2)) // ('a',). This print is same as Python code `a[1: 2]`.
println(a.slice(-1, 20)) // ('Array', 'a', 'set index 3 to String', 1.1)
Python.unload()
return 0
}
Execution result:
4
('a',)
('Array', 'a', 1, 1.1)
Mapping between PyList
and Array
Class prototype:
public class PyList<T> <: PyObj where T <: PyFFIType {
public init(args: Array<T>) { ... }
public operator func [](key: Int64): PyObj { ... }
public operator func [](key: Int64, value!: T): Unit { ... }
public func toCjObj(): Array<PyObj> { ... }
public func size(): Int64 { ... }
public func insert(index: Int64, value: T): Unit { ... }
public func append(item: T): Unit { ... }
public func slice(begin: Int64, end: Int64): PyList<T> { ... }
}
Description of the PyList
Class
-
The
PyList
class is the same as the list type in Python. That is, the Python code uses the[...]
variable. -
The
PyList
class is inherited from thePyObj
class. ThePyList
class has all interfaces of the parent class. This class maps the array of the Cangjie class. Therefore, the genericT
class is introduced. TheT
type is restricted to the subclass of thePyFFIType
interface. -
The
PyList
class can be constructed based on theArray
type of Cangjie. The member type ofArray
is also restricted to the subclass of thePyFFIType
interface. -
Overloading of the
[]
operator:- In the parent class
PyObj
, the input parameter type of[]
isString
. When this class object is called, only the internal member variables or functions ofPython
can be accessed or set. - The input parameter type of
[]
in this class isInt64
, that is, the corner mark value ofArray
. The value range is [0, size ()). If the input parameter is not within the range, an error is reported and the returned object is unavailable. []
also supportsget
andset
. Whenset
is used, thevalue
type isT
. If thevalue
contains an unavailable Python object, an exception is thrown.
- In the parent class
-
The
toCjObj
function can convertPyList
toArray<PyObj>
of Cangjie. Note thatPyList
is not converted toArray<T>
. -
The
size
function returns the length ofPyList
. -
The
insert
function insertsvalue
at the position ofindex
and moves the elements followingvalue
backward. If the index is not in the range of [0, size ()),value
can be inserted normally. Ifvalue
is an unavailable object, an exception is thrown. -
The
append
function appendsitem
toPyList
. Ifvalue
is an unavailable object, an exception is thrown. -
The
slice
function is used to truncate data in the range of [begin, end) and return a newPyList
,begin
andend
that are not in the range of [0, size ()).
Example:
import std.ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyList`.
var a = PyList<Int64>([1, 2, 3])
var b = match (Python.Eval("[1, 2, 3]")) {
case val: PyList<PyObj> => val
case _ => throw PythonException()
}
var c = [1, 2, 3].toPyObj()
// Usage of `[]`
println(a["__add__"]([b])) // [1, 2, 3, 1, 2, 3]
a[1]
b[1]
a[2] = 13
b[2] = 15.toPyObj()
// Usage of `toCjObj`
var cjArr = a.toCjObj()
for (v in cjArr) {
print("${v} ") // 1 2 13
}
print("\n")
// Usage of `size`
println(a.size()) // 3
// Usage of `insert`
a.insert(1, 4) // [1, 4, 2, 13]
a.insert(-100, 5) // [5, 1, 4, 2, 13]
a.insert(100, 6) // [5, 1, 4, 2, 13, 6]
b.insert(1, 4.toPyObj()) // [1, 4, 2, 15]
// Usage of `append`
a.append(7) // [5, 1, 4, 2, 13, 6, 7]
b.append(5.toPyObj()) // [1, 4, 2, 15, 5]
// Usage of `slice`
a.slice(1, 2) // [1]
a.slice(-100, 100) // [5, 1, 4, 2, 13, 6, 7]
b.slice(-100, 100) // [1, 4, 2, 15, 5]
return 0
}
Execution result:
[1, 2, 3, 1, 2, 3]
1 2 13
3
Mapping between PyDict
and HashMap
Class prototype:
public class PyDict<K, V> <: PyObj where K <: Hashable & Equatable<K> & PyFFIType {
public init(args: HashMap<K, V>) { ... }
public func getItem(key: K): PyObj { ... }
public func setItem(key: K, value: V): Unit { ... }
public func toCjObj(): HashMap<PyObj, PyObj> { ... }
public func contains(key: K): Bool { ... }
public func copy(): PyDict<K, V> { ... }
public func del(key: K): Unit { ... }
public func size(): Int64 { ... }
public func empty(): Unit { ... }
public func items(): PyList<PyObj> { ... }
public func values(): PyList<PyObj> { ... }
public func keys(): PyList<PyObj> { ... }
}
Description of the PyDict
Class
-
The
PyDict
is the same as the Python dictionary type, that is, the{ a: b}
variable is used in the Python code. -
The
PyDict
class is inherited from thePyObj
class. ThePyDict
class has all interfaces of the parent class. This class maps the HashMap of the Cangjie class. Therefore, the generic<K, V>
class is introduced. TheK
type is a subclass of thePyFFIType
interface, can be calculated by theHash
class, and the==
and!=
operators are reloaded. -
The
PyDict
receives data from theHashMap
of the Cangjie type for construction.K
can only be of theCjObj
orPyObj
type or its subclasses.- The values of the same Python data are the same. For example,
Python.Eval("1")
and1.toPyObj()
are in the==
relationship.
-
The
getItem
function is used to obtain thevalue
of the key value corresponding toPyDict
. If the key value cannot be found, an error is reported and an unavailablePyObj
is returned. If the configured valuekey
orvalue
is of thePyObj
type and is unavailable, an exception is thrown. -
The
setItem
function is used to configurevalue
of the key value corresponding toPyDict
. If the key value cannot be found, it is inserted. If the configured valuekey
orvalue
is of thePyObj
type and is unavailable, an exception is thrown. -
The
toCjObj
function is used to convertPyDict
toHashMap<PyObj, PyObj>
. -
The
contains
function is used to check whether the value ofkey
is contained in the current dictionary. The return value is of the Boolean type. If the interface fails to be invoked, an error is reported and false is returned. -
The
copy
function is used to copy the current dictionary and return a newPyDict<T>
type. If the copy fails, the returned PyDict is unavailable. -
The
del
function is used to delete the value of the correspondingkey
. If the key value is of the PyObj type and is unavailable, an exception is thrown. -
The
size
function is used to return the length of the current dictionary. -
The
empty
function is used to clear the current dictionary content. -
The
items
function is used to obtain a list of key-value pairs of the Pythonlist
type, which can be iteratively accessed. -
The
values
function is used to obtain a list of values of the Pythonlist
type, which can be iteratively accessed. -
The
keys
function is used to obtain a list of Python keys of thelist
type, which can be iteratively accessed.
Example:
import std.ffi.python.*
import std.collection.*
main() {
Python.load()
// Creation of `PyDict`
var a = PyDict(HashMap<Int64, Int64>([(1, 1), (2, 2)])) // The key type is `CjObj`.
var b = PyDict(HashMap<PyObj, Int64>([(Python.Eval("1"), 1), (Python.Eval("2"), 2)])) // The key type is `PyObj`.
var c = match (Python.Eval("{'pydict': 1, 'hashmap': 2, 3: 3, 3.1: 4}")) {
case val: PyDict<PyObj, PyObj> => val // Python side return `PyDict<PyObj, PyObj>`
case _ => throw PythonException()
}
var d = HashMap<Int64, Int64>([(1, 1), (2, 2)]).toPyObj()
// Usage of `getItem`
println(a.getItem(1)) // 1
println(b.getItem(1.toPyObj())) // 1
// Usage of `setItem`
a.setItem(1, 10)
b.setItem(1.toPyObj(), 10)
println(a.getItem(1)) // 10
println(b.getItem(1.toPyObj())) // 10
// Usage of `toCjObj`
var hashA = a.toCjObj()
for ((k, v) in hashA) {
print("${k}: ${v}, ") // 1: 10, 2: 2,
}
print("\n")
var hashB = b.toCjObj()
for ((k, v) in hashB) {
print("${k}: ${v}, ") // 1: 10, 2: 2,
}
print("\n")
// Usage of `contains`
println(a.contains(1)) // true
println(a.contains(3)) // false
println(b.contains(1.toPyObj())) // true
// Usage of `copy`
println(a.copy()) // {1: 10, 2: 2}
// Usage of `del`
a.del(1) // Delete the key-value pair (1: 1).
// Usage of `size`
println(a.size()) // 1
// Usage of `empty`
a.empty() // Clear all elements in dict.
// Usage of `items`
for (i in b.items()) {
print("${i} ") // (1, 10) (2, 2)
}
print("\n")
// Usage of `values`
for (i in b.values()) {
print("${i} ") // 10 2
}
print("\n")
// Usage of `keys`
for (i in b.keys()) {
print("${i} ") // 1, 2
}
print("\n")
Python.unload()
}
Mapping between PySet
and HashSet
Class prototype:
public class PySet<T> <: PyObj where T <: Hashable, T <: Equatable<T> & PyFFIType {
public init(args: HashSet<T>) { ... }
public func toCjObj(): HashSet<PyObj> { ... }
public func contains(key: T): Bool { ... }
public func add(key: T): Unit { ... }
public func pop(): PyObj { ... }
public func del(key: T): Unit { ... }
public func size(): Int64 { ... }
public func empty(): Unit { ... }
}
Description of the PySet
Class
-
PySet
corresponds to the data type of the collection in Python. When an element is inserted, the hash algorithm in Python is used to sort the elements in the collection. (The elements may not be sorted in strict ascending order. Some methods may cause different running results.) -
The
PySet
class is inherited from thePyObj
class. ThePySet
class has all interfaces of the parent class. This class maps theHashSet
of the Cangjie class. Therefore, the genericT
is introduced. TheT
type is a subclass of thePyFFIType
interface, can be calculated by theHash
class, and the==
and!=
operators are reloaded. -
The
PySet
receives data from theHashMap
of the Cangjie type for construction.K
can only be of theCjObj
orPyObj
type or its subclasses.- The values of the same Python data are the same. For example,
Python.Eval("1")
and1.toPyObj()
are in the==
relationship.
-
The
toCjObj
function is used to convertPySet<T>
toHashSet<PyObj>
. Note that only the element typePyObj
can be converted. -
The
contains
function is used to determine whetherkey
exists in the current dictionary. Thekey
type isT
. -
The
add
function can be used to insert a value. If a key value already exists inPySet
, the insertion does not take effect. Ifkey
isPyObj
and is unavailable, an exception is thrown. -
The
pop
function is used to obtain the first element fromPySet
. -
Delete the corresponding key value from
del
. Ifkey
is not inPySet
, an error is reported and the system exits normally. Ifkey
isPyObj
and is unavailable, an exception is thrown. -
size
is used to return the length ofPySet
. -
The
empty
command is used to clear the currentPySet
.
Note:
After
toCjObj
is called, all elements are displayed bypop
. In this case, the originalPySet
is empty (size
is 0, and the originalPySet
is still available).
Example:
import std.ffi.python.*
import std.collection.*
main() {
Python.load()
// Creation of `PySet`
var a = PySet<Int64>(HashSet<Int64>([1, 2, 3]))
var b = match (Python.Eval("{'PySet', 'HashSet', 1, 1.1, True}")) {
case val: PySet<PyObj> => val
case _ => throw PythonException()
}
var c = HashSet<Int64>([1, 2, 3]).toPyObj()
// Usage of `toCjObj`
var cja = a.toCjObj()
println(a.size()) // 0
// Usage of `contains`
println(b.contains("PySet".toPyObj())) // true
// Usage of `add`
a.add(2)
println(a.size()) // 1
a.add(2) // Insert same value, do nothing.
println(a.size()) // 1
a.add(1) // Insert `1`.
// Usage of `pop`
println(a.pop()) // 1. Pop the first element.
println(a.size()) // 1
// Usage of `del`
c.del(2)
println(c.contains(2)) // false
// Usage of `empty`
println(c.size()) // 2
c.empty()
println(c.size()) // 0
Python.unload()
}
PySlice
Type
The usage of the PySlice
type is the same as that of the return value of the Python built-in function slice()
. The PySlice
type can be used to identify a range and step, and can be used as the index value of the type that can be sliced to tailor and obtain the substring. To facilitate construction on the Cangjie side, the PySlice
class and the Range
range type of Cangjie can be converted to each other. For details, see the following description.
Class prototype:
public class PySlice<T> <: PyObj where T <: Countable<T> & Comparable<T> & Equatable<T> & CjObj {
public init(args: Range<T>) { ... }
public func toCjObj(): Range<Int64> { ... }
}
Description of PySlice
- The
PySlice
can be constructed using theRange
type of Cangjie and supports the syntax sugar of theRange
type. The genericT
has the originalRange
constraint and the constraint is added to the implementation of theCjObj
type. ThePyObj
type is not supported. - The
toCjObj
function can be used to convertPySlice
to CangjieRange
. Note that the generic type ofRange
is an integer of theInt64
type. - If you want to transfer the
PySlice
type toPyString/PyList/PyTuple
or otherPyObj
types that can be transferred byslice
, you can use the member function__getitem__
to transfer thePySlice
type. For details, see the example.
Example:
import std.ffi.python.*
main() {
Python.load()
var range = 1..6:2
// Create a PySlice.
var slice1 = PySlice(range)
var slice2 = match (Python["slice"]([0, 6, 2])) {
case val: PySlice<Int64> => val
case _ => throw PythonException()
}
var slice3 = range.toPyObj()
// Use PySlice in PyString.
var str = PyString("1234567")
println(str["__getitem__"]([range])) // 246
println(str["__getitem__"]([slice1])) // 246
// Use PySlice in PyList.
var list = PyList(["a", "b", "c", "d", "e", "f", "g", "h"])
println(list["__getitem__"]([range])) // ['b', 'd', 'f']
println(list["__getitem__"]([slice1])) // ['b', 'd', 'f']
// Use PySlice in PyTuple.
var tup = PyTuple(list.toCjObj())
println(tup["__getitem__"]([range])) // ('b', 'd', 'f')
println(tup["__getitem__"]([slice1])) // ('b', 'd', 'f')
Python.unload()
0
}
Execution result:
246
246
['b', 'd', 'f']
['b', 'd', 'f']
('b', 'd', 'f')
('b', 'd', 'f')
PyObjIterator
of PyObj
Code prototype:
Extension of PyObj
:
extend PyObj <: Iterable<PyObj> {
public func iterator(): Iterator<PyObj> { ... }
}
PyObjIterator
type:
public class PyObjIterator <: Iterator<PyObj> {
public init(obj: PyObj) { ... }
public func next(): Option<PyObj> { ... }
public func iterator(): Iterator<PyObj> { ... }
}
Description of PyObjIterator
-
You can obtain
PyObjIterator
by using the iterator method ofPyObj
. -
The
PyObjIterator
can be constructed externally. If the providedPyObj
cannot be iterated or is unavailable, an exception is thrown.- The
PyString/PyTuple/PyList/PySet/PyDict
object can be iterated. - When
PyDict
is iterated directly, the value ofkey
is iterated.
- The
-
The
next
function is used to iterate the iterator. -
The
iterator
method is used to return itself.
Example
import std.ffi.python.*
import std.collection.*
main() {
Python.load()
// iter of PyString
var S = PyString("Str")
for (s in S) {
print("${s} ") // S t r
}
print("\n")
// iter of PyTuple
var T = PyTuple(["T".toPyObj(), "u".toPyObj(), "p".toPyObj()])
for (t in T) {
print("${t} ") // T u p
}
print("\n")
// iter of PyList
var L = PyList(["L", "i", "s", "t"])
for (l in L) {
print("${l} ") // L i s t
}
print("\n")
// iter of PyDict
var D = PyDict(HashMap<Int64, String>([(1, "D"), (2, "i"), (3, "c"), (4, "t")]))
for (d in D) {
print("${d} ") // 1 2 3 4, dict print keys.
}
print("\n")
// iter of PySet
var Se = PySet(HashSet<Int64>([1, 2, 3]))
for (s in Se) {
print("${s} ") // 1 2 3
}
print("\n")
0
}
Execution result:
S t r
T u p
L i s t
1 2 3 4
1 2 3
Registration and Callback between Cangjie and Python
The Python interoperability library supports simple function registration and Python's calling of Cangjie functions.
The Python callback code needs to be called using C as the medium, and the third-party libraries of Python, ctypes
and _ctypes
, are used.
Mapping between Cangjie, C, and Python Types
The following table lists the basic data.
Cangjie Type | CType | Python Type |
---|---|---|
Bool | PyCBool | PyBool |
Rune | PyCWchar | PyString |
Int8 | PyCByte | PyLong |
UInt8 | PyCUbyte/PyCChar | PyLong |
Int16 | PyCShort | PyLong |
UInt16 | PyCUshort | PyLong |
Int32 | PyCInt | PyLong |
UInt32 | PyCUint | PyLong |
Int64 | PyCLonglong | PyLong |
UInt64 | PyCUlonglong | PyLong |
Float32 | PyCFloat | PyFloat |
Float64 | PyCDouble | PyFloat |
[unsupport CPointer as param] CPointer<T> | PyCPointer | ctypes.pointer |
[unsupport CString as param] CString | PyCCpointer | ctypes.c_char_p |
[unsupport CString as param] CString | PyCWcpointer | ctypes.c_wchar_p |
Unit | PyCVoid | - |
Cangjie Type
is a variable type modified on the Cangjie side. Unless otherwise specified, parameters of this type can be transferred to Python code and can be transferred from Python to Cangjie.PyCType
is thePyCFunc
interface configuration type on the Cangjie side. For details, see Prototype ofPyCFunc
and Example.Python Type
is a type mapping on the Cangjie side. There is no pointer type mapping. Python functions with pointers cannot be called from the Cangjie side.- Both
PyCCpointer
andPyCWcpointer
are mapped toCString
. The difference is thatPyCCpointer
is a character string in C, andPyCWcpointer
is only a character pointer. Even if multiple characters are transferred, only the first character is used. - Type mismatch will result in unpredictable results.
Prototype of PyCFunc
Class
PyCFunc
is a PyObj subtype based on the Python interoperability library and Python third-party library ctype/_ctype
. This type can be directly transferred to Python
for use. PyCFunc
provides the CFunc
function for Python to register with Cangjie and allows Python to call back the CFunc
function.
Code prototype:
public enum PyCType {
PyCBool |
PyCChar |
PyCWchar |
PyCByte |
PyCUbyte |
PyCShort |
PyCUshort |
PyCInt |
PyCUint |
PyCLonglong |
PyCUlonglong |
PyCFloat |
PyCDouble |
PyCPointer |
PyCCpointer |
PyCWcpointer |
PyCVoid
}
public class PyCFunc <: PyObj {
public init(f: CPointer<Unit>, argsTy!: Array<PyCType> = [], retTy!: PyCType = PyCType.PyCVoid) { ... }
public func setArgTypes(args: Array<PyCType>): PyCFunc { ... }
public func setRetTypes(ret: PyCType): PyCFunc { ... }
}
Description of Class
-
PyCFunc
is inherited fromPyObj
. Some interfaces of the parent class can be used. (If an interface is not supported, an error is reported.) -
The
init
can be constructed by external users. The function pointer must be provided as the first parameter (theCFunc
type needs to be converted to theCPointer<Unit>
type on the Cangjie side). The last two optional parameters are the array of the input parameter type and the return value type.If the input pointer is not a function pointer, the program will crash when the function is called (the library layer cannot intercept the function).
-
The
setArgTypes/setRetTypes
function is used to configure parameters and return value types. For details about the supported parameters, see thePyCType
enumeration. -
The
()
operator in the parent class can call the registeredCFunc
function on the Cangjie side. -
This class can be directly transferred to the Python side or directly called on the Cangjie side. (If a non-function pointer is used during class construction, the system will crash when this class is called.)
-
This class supports chain calling similar to Java Script.
Example
- Prepare the
CFunc
function of Cangjie.
@C
func cfoo(a: Bool, b: Int32, c: Int64): CPointer<Unit> {
print("cfoo called.\n")
print("${a}, ${b}, ${c}\n")
return CPointer<Unit>()
}
- Construct a PyCFunc class object.
import std.ffi.python.*
// Define the @C function.
@C
func cfoo(a: Bool, b: Int32, c: Int64): CPointer<Unit> {
print("cfoo called.\n")
print("${a}, ${b}, ${c}\n")
return CPointer<Unit>()
}
main() {
Python.load()
/*
Construct PyCFunc class.
Set args type: Bool -> PyCBool
Int32 -> PyCInt
Int64 -> PyCLonglong
CPointer<Unit> -> PyCPointer
*/
var f1 = PyCFunc(unsafe {CPointer<Unit>(cfoo)},
argsTy: [PyCBool, PyCInt, PyCLonglong],
retTy: PyCPointer)
// You also can use it by chain-call.
var f2 = PyCFunc(unsafe {CPointer<Unit>(cfoo)})
.setArgTypes([PyCBool, PyCInt, PyCLonglong])
.setRetTypes(PyCPointer)([true, 1, 2])
// Call f1
f1([true, 1, 2])
f1([PyBool(true), PyLong(1), PyLong(2)])
Python.unload()
0
}
Compile and run the following file:
$ cjc ./main.cj -o ./main && ./main
cfoo called.
true, 1, 2
cfoo called.
true, 1, 2
cfoo called.
true, 1, 2
- Register the function with Python and enable Python to call the function.
The Python code is as follows:
# File test.py
# `foo` get a function pointer and call it.
def foo(func):
func(True, 10, 40)
Modify the preceding main
.
import std.ffi.python.*
// Define the @C function.
@C
func cfoo(a: Bool, b: Int32, c: Int64): CPointer<Unit> {
print("cfoo called.\n")
print("${a}, ${b}, ${c}\n")
return CPointer<Unit>()
}
main() {
Python.load()
var f1 = PyCFunc(unsafe {CPointer<Unit>(cfoo)},
argsTy: [PyCBool, PyCInt, PyCLonglong],
retTy: PyCPointer)
// Import test.py
var cfunc01 = Python.Import("test")
// Call `foo` and transfer `f1`
cfunc01["foo"]([f1])
Python.unload()
0
}
- The Python side transfers the pointer to the Cangjie side.
Add a function to the Python file.
# File test.py
# If you want transfer pointer type to Cangjie CFunc, you need import ctypes.
import ctypes.*
# `foo` get a function pointer and call it.
def foo(func):
func(True, 10, 40)
# `fooptr` get a function pointer and call it with pointer type args.
def fooptr(func):
a = c_int(10)
# c_char_p will get whole symbols, but c_wchar_p only get first one symbol 'd'.
func(pointer(a), c_char_p(b'abc'), c_wchar_p('def'))
Modify the Cangjie code.
import std.ffi.python.*
var x = Python.load()
// Modify the `foo` param type to pointer.
@C
func foo(a: CPointer<Int64>, b: CString, c: CString): Unit {
print("${unsafe {a.read(0)}}, ${b.toString()}, ${c.toString()}\n")
}
main(): Int64 {
var f1 = PyCFunc(unsafe {CPointer<Unit>(foo)},
argsTy: [PyCPointer, PyCCpointer, PyCWcpointer],
retTy: PyCVoid)
// Import test.py
var test = Python.Import("test")
// Call `fooptr` and transfer `f1`
test["fooptr"]([f1])
return 0
}
The pointer type cannot be transferred to the Python library when the function is called on the Cangjie side. Therefore, the function can be called only on the Python side.
Compile and run the following command:
$ cjc ./main.cj -o ./main && ./main
10, abc, d