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 IDs 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
PythonLoggerimplements theLoggerinterface only for printing output and printing level control. Logs are not dumped to log files. - If
setOutputis empty, log dump files are not supported. - The output of interfaces such as
info/warn/errorstarts with the corresponding prefix. - The default print level of
PythonLoggerisLogLevel.WARN. - The
PYLOG.error(msg)andlog(LogLevel.ERROR, msg)interfaces throw thePythonExceptionexception.
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
PythonExceptionis the same as that of the inheritedExceptionexcept 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
Versionversion information consists of three parts:major version,minor version, andmicro version. - The
Versionversion is initialized only by using the constructor. Once defined, it cannot be modified. - The
toStringinterface is provided for direct printing. - The
getVersioninterface 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
loadfunction 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 thePythonLoggerprint level. If the parameter is not configured, thePYLOGprint level is reset to thewarnprint 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.pathspecifies 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
loadfunction fails, thePythonExceptionexception is thrown. If the program still needs to continue, pay attention totry-catch. - The
unloadfunction 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
python3can 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-sharedduring 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 aVersionobject.
Exceptions:
- Ensure that the
loadfunction 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
Importfunction receives an input parameter of theStringtype, that is, a module name, and returns an object of thePyModuletype.
Exceptions:
- Ensure that the load function has been called for the
Importfunction. Otherwise, the returnedPyModuleobject is unavailable (isAvaliable()isfalse). - If the corresponding module cannot be found, an error is reported and the returned
PyModuleobject 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 acmdcommand of theStringtype and returns thePyObjformat of the command result.Eval()accepts a specified domain of theStringtype. The default domain is"__main__".
Exceptions:
- Ensure that the
loadfunction has been called. Otherwise, the returnedPyObjobject is unavailable (isAvaliable()isfalse). - If the command received by
Eval()fails to be executed, Python reports an error and the returnedPyObjobject 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 theStringtype, and the return type isPyObj.
Exception Handling:
- Ensure that the
loadfunction has been called. Otherwise, the returnedPyObjobject is unavailable (isAvaliable()isfalse). - If the specified function name is not found, an error is reported and the returned
PyObjobject 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
PyFFITypeis introduced. - This interface has no abstract member function and is implemented or inherited only by
PyObjandCjObj. 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
PyObjclass 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
isAvailableinterface is used to determine whether thePyObjis 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
PyObjis unavailable (isAvaliable()isfalse), an exception is thrown. - If
keydoes not exist inPyObj, Python prints the corresponding error and returns an unavailablePyObjclass 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
PyObjis unavailable (isAvaliable()isfalse), an exception is thrown. - If the
PyObjdoes not contain the correspondingkey, the Python side prints the corresponding error. - If the value of
valueis an unavailable object (isAvaliable()isfalse), the correspondingkeyis deleted from the module or class.
-
The
()parenthesis operator is overloaded to call the function of the object.- If
PyObjis unavailable (isAvaliable()isfalse), an exception is thrown. - If
PyObjis an object that cannot be called, Python reports an error and returns an unavailablePyObjclass object (isAvaliable()isfalse). ()accepts function calls without parameters.- The
([...])parameter supports at least one parameter. The parameter type can beCjObjorPyObj. Note thatCjObjandPyObjcannot 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. ThekeytypeStringis configured as the variable name, and thevaluetype PyObj is configured as the parameter value.
- If
-
Binary operator overloading:
-
+(addition of two variables):- Basic data types:
PyStringandPyBool/PyLong/PyFloatcannot be added together. Other types can be added together. - Advanced data types:
PyDict/PySetcannot be added to any type.PyTuple/PyListcan only be added to itself.
- Basic data types:
-
-(subtraction of two variables):- Basic data types:
PyStringandPyBool/PyLong/PyFloat/PyStringdo not support subtraction. Other types support subtratcion. - Advanced data types:
PyDict/PySet/PyTuple/PyListdoes not support subtraction from any type.
- Basic data types:
-
*(multiplication of two variables):- Basic data types:
PyStringandPyFloat/PyStringdo not support multiplication. Other types support multiplication. - Advanced data types:
PyDict/PySetcannot be multiplied by any type.PyTuple/PyListcan be multiplied only byPyLong/PyBool.
- Basic data types:
-
/(division of two variables):- Basic data types:
PyStringandPyBool/PyLong/PyFloat/PyStringcannot be divided by each other. Other types can be divided by each other. If the divisor is 0 (Falseis 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/PyListcannot be divided by any type.
- Basic data types:
-
**(exponent calculation):- Basic data types:
PyStringandPyBool/PyLong/PyFloat/PyStringdo not support exponential calculation. Other data types support exponential calculation. - Advanced data types:
PyDict/PySet/PyTuple/PyListdo not support exponentiation.
- Basic data types:
-
%(remainder operation):- Basic data types:
PyStringandPyBool/PyLong/PyFloat/PyStringdo not support the remainder operation. Other data types support the remainder operation. If the divisor is 0 (Falseis 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/PyListand all types do not support the modulo operation.
- Basic data types:
-
All the preceding errors are recorded at the warn level, and the returned
PyObjis unavailable (isAvaliable()isfalse).
-
-
public open func toString(): String { ... }:- The
toStringfunction can return the Python data type as a string, and the basic data type is returned in Python style. - If
PyObjis unavailable (isAvaliable()isfalse), an exception is thrown.
- The
-
The
hashCodefunction is the encapsulated Pythonhashalgorithm and returns an Int64 hash value. -
The
==operator is used to determine whether twoPyObjobjects are the same. The!=operator is used to determine whether twoPyObjobjects are the same. If the interface comparison fails, the==returnsfalseand 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
PyBoolclass is inherited from thePyObjclass. ThePyBoolclass has all the interfaces of the parent class. PyBoolcan be constructed only by using theBooltype of Cangjie.- The
toCjObjinterface convertsPyBoolto 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
PyLongclass is inherited from thePyObjclass. ThePyLongclass has all the interfaces of the parent class. -
The
PyLongsupports the construction of input parameters of the integer type from all Cangjie. -
The
toCjObjandtoInt64interfaces convertPyLongto theInt64type. -
The
toUInt64interface convertsPyLongtoUInt64. -
The
PyLongtype is converted to the Cangjie type to the 8-byte type. ThePyLongtype cannot be converted to the lower-byte type. -
Overflow/Underflow problems:
- If the original value of
toInt64(UInt64is used to assign a value and no error is reported) exceeds theInt64range, an overflow occurs. - If the original value of
toUInt64(Int64is used to assign a value and no error is reported) exceeds theUInt64range, an overflow occurs.
- If the original value of
-
Currently,
PyLongdoes 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
PyFloatclass is inherited from thePyObjclass. ThePyFloatclass has all the interfaces of the parent class. PyBoolcan be constructed using data of the CangjieFloat32/Float64type.- To ensure precision, the
toCjObjinterface convertsPyFloatto 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
PyStringclass is inherited from thePyObjclass. ThePyStringclass has all the interfaces of the parent class. PyStringcan be constructed using data of the CangjieRune/Stringtype.- The
toCjObj/toStringinterface is used to convertPyStringto theStringdata 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
PyTupletype is the same as the tuple type in Python, that is, the(...)variable is used in Python code. - The
PyTupleclass is inherited from thePyObjclass. ThePyTupleclass has all the interfaces of the parent class. PyTuplecan be constructed using CangjieArray. The element type ofArraymust bePyObj(different data types of Python can be transferred usingPyObj, that is, different data types of different elements inTupleare compatible). If a member contains an unavailable object, an exception is thrown.- Overloading of the
[]operator:- The input parameter type of
[]in the parent classPyObjisString. When this class is called, the internal member variables or functions of thePythontuple type can be accessed or set. - The subclass
PyTuplecan use[]to access elements. If the badgekeyexceeds the range of [0, size ()), an error is reported and an unavailablePyObjobject is returned. - The
set []operator is not reloaded because the Python tuple is an immutable object.
- The input parameter type of
- The
sizefunction is used to obtain the length ofPyTuple. - The
slicefunction is used to tailor the sourcePyTupleand return a newPyTuple. If the input parametersbeginandendofsliceare not in the range of [0, size ()), the sourcePyTupleis 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
PyListclass is the same as the list type in Python. That is, the Python code uses the[...]variable. -
The
PyListclass is inherited from thePyObjclass. ThePyListclass has all interfaces of the parent class. This class maps the array of the Cangjie class. Therefore, the genericTclass is introduced. TheTtype is restricted to the subclass of thePyFFITypeinterface. -
The
PyListclass can be constructed based on theArraytype of Cangjie. The member type ofArrayis also restricted to the subclass of thePyFFITypeinterface. -
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 ofPythoncan 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 supportsgetandset. Whensetis used, thevaluetype isT. If thevaluecontains an unavailable Python object, an exception is thrown.
- In the parent class
-
The
toCjObjfunction can convertPyListtoArray<PyObj>of Cangjie. Note thatPyListis not converted toArray<T>. -
The
sizefunction returns the length ofPyList. -
The
insertfunction insertsvalueat the position ofindexand moves the elements followingvaluebackward. If the index is not in the range of [0, size ()),valuecan be inserted normally. Ifvalueis an unavailable object, an exception is thrown. -
The
appendfunction appendsitemtoPyList. Ifvalueis an unavailable object, an exception is thrown. -
The
slicefunction is used to truncate data in the range of [begin, end) and return a newPyList,beginandendthat 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
PyDictis the same as the Python dictionary type, that is, the{ a: b}variable is used in the Python code. -
The
PyDictclass is inherited from thePyObjclass. ThePyDictclass has all interfaces of the parent class. This class maps the HashMap of the Cangjie class. Therefore, the generic<K, V>class is introduced. TheKtype is a subclass of thePyFFITypeinterface, can be calculated by theHashclass, and the==and!=operators are reloaded. -
The
PyDictreceives data from theHashMapof the Cangjie type for construction.Kcan only be of theCjObjorPyObjtype 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
getItemfunction is used to obtain thevalueof the key value corresponding toPyDict. If the key value cannot be found, an error is reported and an unavailablePyObjis returned. If the configured valuekeyorvalueis of thePyObjtype and is unavailable, an exception is thrown. -
The
setItemfunction is used to configurevalueof the key value corresponding toPyDict. If the key value cannot be found, it is inserted. If the configured valuekeyorvalueis of thePyObjtype and is unavailable, an exception is thrown. -
The
toCjObjfunction is used to convertPyDicttoHashMap<PyObj, PyObj>. -
The
containsfunction is used to check whether the value ofkeyis 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
copyfunction is used to copy the current dictionary and return a newPyDict<T>type. If the copy fails, the returned PyDict is unavailable. -
The
delfunction 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
sizefunction is used to return the length of the current dictionary. -
The
emptyfunction is used to clear the current dictionary content. -
The
itemsfunction is used to obtain a list of key-value pairs of the Pythonlisttype, which can be iteratively accessed. -
The
valuesfunction is used to obtain a list of values of the Pythonlisttype, which can be iteratively accessed. -
The
keysfunction is used to obtain a list of Python keys of thelisttype, 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
-
PySetcorresponds 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
PySetclass is inherited from thePyObjclass. ThePySetclass has all interfaces of the parent class. This class maps theHashSetof the Cangjie class. Therefore, the genericTis introduced. TheTtype is a subclass of thePyFFITypeinterface, can be calculated by theHashclass, and the==and!=operators are reloaded. -
The
PySetreceives data from theHashMapof the Cangjie type for construction.Kcan only be of theCjObjorPyObjtype 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
toCjObjfunction is used to convertPySet<T>toHashSet<PyObj>. Note that only the element typePyObjcan be converted. -
The
containsfunction is used to determine whetherkeyexists in the current dictionary. Thekeytype isT. -
The
addfunction can be used to insert a value. If a key value already exists inPySet, the insertion does not take effect. IfkeyisPyObjand is unavailable, an exception is thrown. -
The
popfunction is used to obtain the first element fromPySet. -
Delete the corresponding key value from
del. Ifkeyis not inPySet, an error is reported and the system exits normally. IfkeyisPyObjand is unavailable, an exception is thrown. -
sizeis used to return the length ofPySet. -
The
emptycommand is used to clear the currentPySet.
Note:
After
toCjObjis called, all elements are displayed bypop. In this case, the originalPySetis empty (sizeis 0, and the originalPySetis 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
PySlicecan be constructed using theRangetype of Cangjie and supports the syntax sugar of theRangetype. The genericThas the originalRangeconstraint and the constraint is added to the implementation of theCjObjtype. ThePyObjtype is not supported. - The
toCjObjfunction can be used to convertPySliceto CangjieRange. Note that the generic type ofRangeis an integer of theInt64type. - If you want to transfer the
PySlicetype toPyString/PyList/PyTupleor otherPyObjtypes that can be transferred byslice, you can use the member function__getitem__to transfer thePySlicetype. 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
PyObjIteratorby using the iterator method ofPyObj. -
The
PyObjIteratorcan be constructed externally. If the providedPyObjcannot be iterated or is unavailable, an exception is thrown.- The
PyString/PyTuple/PyList/PySet/PyDictobject can be iterated. - When
PyDictis iterated directly, the value ofkeyis iterated.
- The
-
The
nextfunction is used to iterate the iterator. -
The
iteratormethod 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 Typeis 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.PyCTypeis thePyCFuncinterface configuration type on the Cangjie side. For details, see Prototype ofPyCFuncand Example.Python Typeis 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
PyCCpointerandPyCWcpointerare mapped toCString. The difference is thatPyCCpointeris a character string in C, andPyCWcpointeris 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
-
PyCFuncis inherited fromPyObj. Some interfaces of the parent class can be used. (If an interface is not supported, an error is reported.) -
The
initcan be constructed by external users. The function pointer must be provided as the first parameter (theCFunctype 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/setRetTypesfunction is used to configure parameters and return value types. For details about the supported parameters, see thePyCTypeenumeration. -
The
()operator in the parent class can call the registeredCFuncfunction 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
CFuncfunction 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