This commit is contained in:
blueloveTH 2024-08-25 01:42:08 +08:00
parent 291ee682b7
commit ac8f4a1c2d
23 changed files with 420 additions and 219 deletions

View File

@ -12,12 +12,9 @@ check_ipo_supported(RESULT result)
if(result AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
else()
message(WARNING ">> IPO disabled. You will get very poor performance!!")
message(WARNING ">> IPO disabled. You will get very poor performance!!")
message(WARNING ">> IPO disabled. You will get very poor performance!!")
message(WARNING ">> IPO disabled. You will not get the best performance.")
endif()
if(MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8 /jumptablerdata /GS-")
add_compile_options(/wd4267 /wd4244)
@ -47,11 +44,6 @@ if(PK_ENABLE_OS)
add_definitions(-DPK_ENABLE_OS=1)
endif()
option(PK_ENABLE_PROFILER "" OFF)
if(PK_ENABLE_PROFILER)
add_definitions(-DPK_ENABLE_PROFILER=1)
endif()
# PK_IS_MAIN determines whether the project is being used from root
# or if it is added as a dependency (through add_subdirectory for example).
if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")

View File

@ -79,6 +79,7 @@ To compile it with your project, these flags must be set:
+ `--std=c11` flag must be set
+ For MSVC, `/utf-8` flag must be set
+ `NDEBUG` macro should be defined for release build, or you will get poor performance
For amalgamated build, run `python amalgamate.py` to generate `pocketpy.c` and `pocketpy.h` in `amalgamated/` directory.
@ -146,9 +147,6 @@ __ERROR:
## Features
Check this [Cheatsheet](https://reference.pocketpy.dev/python.html)
for a quick overview of the supported features.
| Name | Example | Supported |
| --------------- | ------------------------------- | --------- |
| If Else | `if..else..elif` | ✅ |
@ -237,7 +235,6 @@ Your sponsorship will help us develop pkpy continuously.
[![Star History Chart](https://api.star-history.com/svg?repos=blueloveth/pocketpy&type=Date)](https://star-history.com/#blueloveth/pocketpy&Date)
## License
[MIT License](http://opensource.org/licenses/MIT)

View File

@ -8,7 +8,7 @@ from typing import List, Dict
assert os.system("python prebuild.py") == 0
ROOT = 'include/pocketpy'
PUBLIC_HEADERS = ['config.h', 'export.h', 'pocketpy.h']
PUBLIC_HEADERS = ['config.h', 'export.h', 'linalg.h', 'pocketpy.h']
COPYRIGHT = '''/*
* Copyright (c) 2024 blueloveTH

291
docs/bindings-cpp.md Normal file
View File

@ -0,0 +1,291 @@
---
icon: cpu
title: Write C++ Bindings
order: 17
---
## Quick Start
pkpy provides a [pybind11](https://pybind11.readthedocs.io/en/stable/) compatible layer which allows users to do convenient bindings.
To begin with, use `py::scoped_interpreter guard{}` to start the interpreter before using any Python objects.
Or explicitly call `py::interpreter::initialize()` and `py::interpreter::finalize()`.
### module
```cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_EMBEDDED_MODULE(example, m) {
m.def("add", [](int a, int b) {
return a + b;
});
auto math = m.def_submodule("math");
}
```
### function
```cpp
int add(int a, int b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
void register_function(py::module_& m)
{
m.def("add", py::overload_cast<int, int>(&add));
// support function overload
m.def("add", py::overload_cast<int, int, int>(&add));
// bind with default arguments
m.def("sub", [](int a, int b) {
return a - b;
}, py::arg("a") = 1, py::arg("b") = 2);
// bind *args
m.def("add", [](py::args args) {
int sum = 0;
for (auto& arg : args) {
sum += arg.cast<int>();
}
return sum;
});
// bind **kwargs
m.def("add", [](py::kwargs kwargs) {
int sum = 0;
for (auto item : kwargs) {
sum += item.second.cast<int>();
}
return sum;
});
}
```
### class
```cpp
struct Point
{
const int x;
int y;
public:
Point() : x(0), y(0) {}
Point(int x, int y) : x(x), y(y) {}
Point(const Point& p) : x(p.x), y(p.y) {}
std::string stringfy() const {
return "(" + std::to_string(x) + ", " + std::to_string(y) + ")";
}
};
struct Point3D : Point
{
private:
int z;
public:
Point3D(int x, int y, int z) : Point(x, y), z(z) {}
int get_z() const { return z; }
void set_z(int z) { this->z = z; }
};
void bind_class(py::module_& m)
{
py::class_<Point>(m, "Point")
.def(py::init<>())
.def(py::init<int, int>())
.def(py::init<const Point&>())
.def_readonly("x", &Point::x)
.def_readwrite("y", &Point::y)
.def("__str__", &Point::stringfy);
// only support single inheritance
py::class_<Point3D, Point>(m, "Point3D", py::dynamic_attr())
.def(py::init<int, int, int>())
.def_property("z", &Point3D::get_z, &Point3D::set_z);
// dynamic_attr will enable the dict of bound class
}
```
### operators
```cpp
#include <pybind11/operators.h>
namespace py = pybind11;
struct Int {
int value;
Int(int value) : value(value) {}
Int operator+(const Int& other) const {
return Int(value + other.value);
}
Int operator-(const Int& other) const {
return Int(value - other.value);
}
bool operator==(const Int& other) const {
return value == other.value;
}
bool operator!=(const Int& other) const {
return value != other.value;
}
};
void bind_operators(py::module_& m)
{
py::class_<Int>(m, "Int")
.def(py::init<int>())
.def(py::self + py::self)
.def(py::self - py::self)
.def(py::self == py::self)
.def(py::self != py::self);
// other operators are similar
}
```
### py::object
`py::object` is just simple wrapper around `PyVar`. It supports some convenient methods to interact with Python objects.
here are some common methods:
```cpp
obj.attr("x"); // access attribute
obj[1]; // access item
obj.is_none(); // same as obj is None in Python
obj.is(obj2); // same as obj is obj2 in Python
// operators
obj + obj2; // same as obj + obj2 in Python
// ...
obj == obj2; // same as obj == obj2 in Python
// ...
obj(...); // same as obj.__call__(...)
py::cast(obj); // cast to Python object
obj.cast<T>; // cast to C++ type
py::type::of(obj); // get type of obj
py::type::of<T>(); // get type of T, if T is registered
```
you can also create some builtin objects with their according wrappers:
```cpp
py::bool_ b = {true};
py::int_ i = {1};
py::float_ f = {1.0};
py::str s = {"hello"};
py::list l = {1, 2, 3};
py::tuple t = {1, 2, 3};
// ...
```
## More Examples
More examples please see the test [folder](https://github.com/pocketpy/gsoc-2024-dev/tree/main/pybind11/tests) in the GSoC repository. All tested features are supported.
## Limits and Comparison
This is a feature list of pybind11 for pocketpy. It lists all completed and pending features. It also lists the features that cannot be implemented in the current version of pocketpy.
### [Function](https://pybind11.readthedocs.io/en/stable/advanced/functions.html)
- [x] Function overloading
- [x] Return value policy
- [x] is_prepend
- [x] `*args` and `**kwargs`
- [ ] Keep-alive
- [ ] Call Guard
- [x] Default arguments
- [ ] Keyword-Only arguments
- [ ] Positional-Only arguments
- [ ] Allow/Prohibiting None arguments
### [Class](https://pybind11.readthedocs.io/en/stable/classes.html)
- [x] Creating bindings for a custom type
- [x] Binding lambda functions
- [x] Dynamic attributes
- [x] Inheritance and automatic downcasting
- [x] Enumerations and internal types
- [ ] Instance and static fields
> Binding static fields may never be implemented in pocketpy because it requires a metaclass, which is a heavy and infrequently used feature.
### [Exceptions](https://pybind11.readthedocs.io/en/stable/advanced/exceptions.html)
Need further discussion.
### [Smart pointers](https://pybind11.readthedocs.io/en/stable/advanced/smart_ptrs.html)
- [ ] std::shared_ptr
- [ ] std::unique_ptr
- [ ] Custom smart pointers
### [Type conversions](https://pybind11.readthedocs.io/en/stable/advanced/cast/index.html)
- [x] Python built-in types
- [x] STL Containers
- [ ] Functional
- [ ] Chrono
### [Python C++ interface](https://pybind11.readthedocs.io/en/stable/advanced/pycpp/object.html)
Need further discussion.
- [x] `object`
- [x] `none`
- [x] `type`
- [x] `bool_`
- [x] `int_`
- [x] `float_`
- [x] `str`
- [ ] `bytes`
- [ ] `bytearray`
- [x] `tuple`
- [x] `list`
- [ ] `set`
- [x] `dict`
- [ ] `slice`
- [x] `iterable`
- [x] `iterator`
- [ ] `function`
- [ ] `buffer`
- [ ] `memoryview`
- [x] `capsule`
### [Miscellaneous](https://pybind11.readthedocs.io/en/stable/advanced/misc.html)
- [ ] Global Interpreter Lock (GIL)
- [ ] Binding sequence data types, iterators, the slicing protocol, etc.
- [x] Convenient operators binding
### Differences between CPython and pocketpy
- only `add`, `sub` and `mul` have corresponding right versions in pocketpy. So if you bind `int() >> py::self`, it will has no effect in pocketpy.
- `__new__` and `__del__` are not supported in pocketpy.
- in-place operators, such as `+=`, `-=`, `*=`, etc., are not supported in pocketpy.
- thre return value of `globals` is immutable in pocketpy.

View File

@ -1,6 +1,6 @@
---
icon: cpu
title: Write Bindings
title: Write C Bindings
order: 18
---
@ -16,3 +16,11 @@ typedef bool (*py_CFunction)(int argc, py_Ref argv);
If successful, the function should return `true` and set the return value in `py_retval()`. In case there is no return value, you should use `py_newnone(py_retval())`.
If an error occurs, the function should raise an exception and return `false`.
See also:
+ [`py_bind`](/c-api/functions/#py_bind)
+ [`py_bindmethod`](/c-api/functions/#py_bindmethod)
+ [`py_bindfunc`](/c-api/functions/#py_bindfunc)
+ [`py_bindproperty`](/c-api/functions/#py_bindproperty)
+ [`py_newmodule`](/c-api/functions/#py_newmodule)
+ [`py_newtype`](/c-api/functions/#py_newtype)

View File

@ -4,9 +4,6 @@ title: Basic Features
order: 100
---
Check this [Cheatsheet](https://reference.pocketpy.dev/python.html)
for a quick overview of the supported features.
The following table shows the basic features of pkpy with respect to [cpython](https://github.com/python/cpython).
The features marked with `YES` are supported, and the features marked with `NO` are not supported.

View File

@ -4,7 +4,7 @@ title: Debugging
---
!!!
This feature is available in `v1.4.5` or higher. Set `PK_ENABLE_PROFILER` to `1` to enable this feature.
This feature is not available in `v2.0` yet.
!!!
You can invoke `breakpoint()` in your python code to start a PDB-like session.

View File

@ -23,20 +23,18 @@ The easiest way to test a feature is to [try it on your browser](https://pocketp
1. Descriptor protocol `__get__` and `__set__`. However, `@property` is implemented.
2. `__slots__` in class definition.
3. `else` clause in try..except.
4. Inplace methods like `__iadd__` and `__imul__`.
4. Inplace methods like `__iadd__` and `__imul__`.
5. `__del__` in class definition.
6. Multiple inheritance.
## Different behaviors
1. positional and keyword arguments are strictly evaluated.
2. `++i` and `--j` is an increment/decrement statement, not an expression.
3. `int` does not derive from `bool`.
4. `int` is 64-bit. You can use `long` type explicitly for arbitrary sized integers.
5. `__ne__` is not required. Define `__eq__` is enough.
6. Raw string cannot have boundary quotes in it, even escaped. See [#55](https://github.com/pocketpy/pocketpy/issues/55).
7. In a starred unpacked assignment, e.g. `a, b, *c = x`, the starred variable can only be presented in the last position. `a, *b, c = x` is not supported.
8. A `Tab` is equivalent to 4 spaces. You can mix `Tab` and spaces in indentation, but it is not recommended.
9. `%`, `&`, `//`, `^` and `|` for `int` behave the same as C, not python.
10. `str.split` and `str.splitlines` will remove all empty entries.
11. `__getattr__`, `__setattr__` and `__delattr__` can only be set in cpp.
2. `int` does not derive from `bool`.
3. `int` is 64-bit.
4. Raw string cannot have boundary quotes in it, even escaped. See [#55](https://github.com/pocketpy/pocketpy/issues/55).
5. In a starred unpacked assignment, e.g. `a, b, *c = x`, the starred variable can only be presented in the last position. `a, *b, c = x` is not supported.
6. A `Tab` is equivalent to 4 spaces. You can mix `Tab` and spaces in indentation, but it is not recommended.
7. `%`, `&`, `//`, `^` and `|` for `int` behave the same as C, not python.
8. `str.split` and `str.splitlines` will remove all empty entries.

View File

@ -1,145 +0,0 @@
---
icon: dot
title: Precompiling
---
pkpy allows you to precompile python code into two special forms, which can be executed later.
### In-memory precompilation
You can use `vm->compile` to compile your source code into a `CodeObject_` object.
This object can be executed later by `vm->_exec`.
```cpp
CodeObject_ code = vm->compile("print('Hello, world!')", "<string>", EXEC_MODE);
vm->_exec(code); // Hello, world!
```
This `CodeObject_` object is a very non-generic form of the compiled code,
which is an in-memory form. Very efficient, but not portable.
You are not able to save it to a file or load it from a file.
### String precompilation
In order to save the compiled code to a file, you need to use `vm->precompile`.
It does some basic preprocessing and outputs the result as a human-readable string.
```cpp
// precompile the source code into a string
Str source = vm->precompile("print('Hello, world!')", "<string>", EXEC_MODE);
CodeObject code = vm->compile(source, "<string>", EXEC_MODE);
vm->_exec(code); // Hello, world!
```
You can also use python's `compile` function to achieve the same effect.
```python
code = compile("print('Hello, world!')", "<string>", "exec")
exec(code) # Hello, world!
```
Let's take a look at the precompiled string.
```python
print(code)
```
```txt
pkpy:1.4.5
0
=1
print
=6
5,1,0,
6,0,,,
42,,1,
8,,,S48656c6c6f2c20776f726c6421
43,,0,
3,,,
```
Comparing with **In-memory precompilation**,
**String precompilation** drops most of the information of the original source code.
It has an encryption effect, which can protect your source code from being stolen.
This also means there is no source line information when an error occurs.
```python
src = """
def f(a, b):
return g(a, b)
def g(a, b):
c = f(a, b)
d = g(a, b)
return c + d
"""
code = compile(src, "<exec>", "exec")
exec(code)
f(1, 2)
```
You will get this (without source line information):
```txt
Traceback (most recent call last):
File "<exec>", line 3, in f
File "<exec>", line 6, in g
File "<exec>", line 3, in f
File "<exec>", line 6, in g
File "<exec>", line 3, in f
File "<exec>", line 6, in g
File "<exec>", line 3, in f
StackOverflowError
```
instead of this (with source line information):
```txt
Traceback (most recent call last):
File "<stdin>", line 2, in f
return g(a, b)
File "<stdin>", line 2, in g
c = f(a, b)
File "<stdin>", line 2, in f
return g(a, b)
File "<stdin>", line 2, in g
c = f(a, b)
File "<stdin>", line 2, in f
return g(a, b)
File "<stdin>", line 2, in g
c = f(a, b)
File "<stdin>", line 2, in f
return g(a, b)
StackOverflowError
```
!!!
String compilation has no guarantee of compatibility between different versions of pkpy.
!!!
You can use this snippet to convert every python file in a directory into precompiled strings.
```python
# precompile.py
import sys, os
def precompile(filepath: str):
"""Precompile a python file inplace"""
print(filepath)
with open(filepath, 'r') as f:
source = f.read()
source = compile(source, filepath, 'exec')
with open(filepath, 'w') as f:
f.write(source)
def traverse(root: str):
"""Traverse a directory and precompile every python file"""
for entry in os.listdir(root):
entrypath = os.path.join(root, entry)
if os.path.isdir(entrypath):
traverse(entrypath)
elif entrypath.endswith(".py"):
precompile(entrypath)
```

View File

@ -9,4 +9,4 @@ These are the undefined behaviours of pkpy. The behaviour of pkpy is undefined i
2. Call an unbound method with the wrong type of `self`. For example, `int.__add__('1', 2)`.
3. Type `T`'s `__new__` returns an object that is not an instance of `T`.
4. Call `__new__` with a type that is not a subclass of `type`.
5. `__eq__`, `__lt__` or `__contains__`, etc.. returns a value that is not a boolean.

View File

@ -31,6 +31,7 @@ To compile it with your project, these flags must be set:
+ `--std=c11` flag must be set
+ For MSVC, `/utf-8` flag must be set
+ `NDEBUG` macro should be defined for release build, or you will get poor performance
### Get prebuilt binaries
@ -53,12 +54,6 @@ You can download an artifact there which contains the following files.
│   └── x86_64
│   ├── libpocketpy.so
│   └── main
├── macos
│   └── pocketpy.bundle
│   └── Contents
│   ├── Info.plist
│   └── MacOS
│   └── pocketpy
└── windows
└── x86_64
├── main.exe

View File

@ -4,6 +4,8 @@
#include "pocketpy/common/utils.h"
#include "pocketpy/pocketpy.h"
#include <stdarg.h>
/* string */
typedef struct c11_string{
// int size | char[] | '\0'

View File

@ -22,15 +22,13 @@ typedef struct c11_vec3 {
float z;
} c11_vec3;
typedef struct c11_mat3x3 {
union {
struct {
float _11, _12, _13;
float _21, _22, _23;
float _31, _32, _33;
};
float m[3][3];
float data[9];
typedef union c11_mat3x3 {
struct {
float _11, _12, _13;
float _21, _22, _23;
float _31, _32, _33;
};
float m[3][3];
float data[9];
} c11_mat3x3;

View File

@ -2,7 +2,6 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stddef.h>
#include "pocketpy/config.h"

View File

@ -1,2 +0,0 @@
def loads(data: str | bytes) -> object: ...
def dumps(obj: object) -> str: ...

View File

@ -14,6 +14,8 @@ class vec2:
def __init__(self, x: float, y: float) -> None: ...
def __repr__(self) -> str: ...
def __eq__(self, other: vec2) -> bool: ...
def __ne__(self, other: vec2) -> bool: ...
def __add__(self, other: vec2) -> vec2: ...
def __sub__(self, other: vec2) -> vec2: ...
@ -51,6 +53,8 @@ class vec2:
class mat3x3:
def __init__(self, _11, _12, _13, _21, _22, _23, _31, _32, _33) -> None: ...
def __repr__(self) -> str: ...
def __eq__(self, other: mat3x3) -> bool: ...
def __ne__(self, other: mat3x3) -> bool: ...
def __getitem__(self, index: tuple[int, int]) -> float: ...
def __setitem__(self, index: tuple[int, int], value: float) -> None: ...
@ -101,6 +105,8 @@ class vec2i:
def __init__(self, x: int, y: int) -> None: ...
def __repr__(self) -> str: ...
def __eq__(self, other: vec2i) -> bool: ...
def __ne__(self, other: vec2i) -> bool: ...
class vec3i:
@ -117,6 +123,8 @@ class vec3i:
def __init__(self, x: int, y: int, z: int) -> None: ...
def __repr__(self) -> str: ...
def __eq__(self, other: vec3i) -> bool: ...
def __ne__(self, other: vec3i) -> bool: ...
class vec3:
@ -133,3 +141,5 @@ class vec3:
def __init__(self, x: float, y: float, z: float) -> None: ...
def __repr__(self) -> str: ...
def __eq__(self, other: vec3) -> bool: ...
def __ne__(self, other: vec3) -> bool: ...

View File

@ -283,12 +283,13 @@ FrameResult VM__run_top_frame(VM* self) {
if(magic->type == tp_nativefunc) {
if(!py_callcfunc(magic->_cfunc, 2, SECOND())) goto __ERROR;
POP();
py_assign(TOP(), py_retval());
} else {
INSERT_THIRD(); // [?, a, b]
*THIRD() = *magic; // [__getitem__, a, b]
if(!py_vectorcall(1, 0)) goto __ERROR;
PUSH(py_retval());
}
*TOP() = self->last_retval;
DISPATCH();
}
TypeError("'%t' object is not subscriptable", SECOND()->type);
@ -349,7 +350,6 @@ FrameResult VM__run_top_frame(VM* self) {
} else {
*FOURTH() = *magic; // [__selitem__, a, b, val]
if(!py_vectorcall(2, 0)) goto __ERROR;
POP(); // discard retval
}
DISPATCH();
}
@ -423,7 +423,6 @@ FrameResult VM__run_top_frame(VM* self) {
INSERT_THIRD(); // [?, a, b]
*THIRD() = *magic; // [__delitem__, a, b]
if(!py_vectorcall(1, 0)) goto __ERROR;
POP(); // discard retval
}
DISPATCH();
}

View File

@ -63,11 +63,44 @@ static bool slice_step(int argc, py_Ref argv) {
return true;
}
static bool slice__eq__(int argc, py_Ref argv) {
py_Ref self = py_arg(0);
py_Ref other = py_arg(1);
if(!py_istype(other, tp_slice)) {
py_newnotimplemented(py_retval());
return true;
}
for(int i = 0; i < 3; i++) {
py_Ref lhs = py_getslot(self, i);
py_Ref rhs = py_getslot(other, i);
int res = py_equal(lhs, rhs);
if(res == -1) return false;
if(res == 0) {
py_newbool(py_retval(), false);
return true;
}
}
py_newbool(py_retval(), true);
return true;
}
static bool slice__ne__(int argc, py_Ref argv) {
bool ok = slice__eq__(argc, argv);
if(!ok) return false;
py_Ref res = py_retval();
if(py_isbool(res)) py_newbool(py_retval(), !py_tobool(res));
return true;
}
py_Type pk_slice__register() {
py_Type type = pk_newtype("slice", tp_object, NULL, NULL, false, true);
py_bindmagic(type, __new__, slice__new__);
py_bindmagic(type, __repr__, slice__repr__);
py_bindmagic(type, __eq__, slice__eq__);
py_bindmagic(type, __ne__, slice__ne__);
py_setdict(py_tpobject(type), __hash__, py_None);
py_bindproperty(type, "start", slice_start, NULL);
py_bindproperty(type, "stop", slice_stop, NULL);

View File

@ -167,26 +167,6 @@ assert b == [(5, 1), (1, 2), (3,3)]
# assert repr(a) == "[0, [1, 2, [...]]]"
# try:
# a.index(1, 1)
# exit(1)
# except ValueError:
# pass
# slice extras
# class A:
# def __getitem__(self, index):
# return index
# assert A()[1:2, 3] == (slice(1, 2, None), 3)
# assert A()[1:2, 3:4] == (slice(1, 2, None), slice(3, 4, None))
# assert A()[1:2, 3:4, 5] == (slice(1, 2, None), slice(3, 4, None), 5)
# assert A()[:, :] == (slice(None, None, None), slice(None, None, None))
# assert A()[::, :] == (slice(None, None, None), slice(None, None, None))
# assert A()[::, :2] == (slice(None, None, None), slice(None, 2, None))
# assert A()['b':'c':1, :] == (slice('b', 'c', 1), slice(None, None, None))
# assert A()[1:2, :A()[3:4, ::-1]] == (slice(1, 2, None), slice(None, (slice(3, 4, None), slice(None, None, -1)), None))
# unpacking builder (not supported)
# a = [1, 2, 3]
# b = [*a, 4, 5]

View File

@ -47,7 +47,7 @@ try:
a = A()
b = a[1]
exit(1)
except:
except KeyError:
pass
try:

49
tests/99_extras.py Normal file
View File

@ -0,0 +1,49 @@
try:
a = [1, 2, 3]
a.index(999)
exit(1)
except ValueError:
pass
# test some python magics
class A:
def __init__(self):
self.d = {}
def __getitem__(self, index):
return self.d[index]
def __setitem__(self, index, value):
self.d[index] = value
def __contains__(self, index):
return index in self.d
def __delitem__(self, index):
del self.d[index]
a = A()
a['1'] = 3
assert '1' in a
assert '2' not in a
assert a['1'] == 3
del a['1']
assert '1' not in a
# slice extras
class A:
def __getitem__(self, index):
return index
assert slice(1, 2, None) == slice(1, 2, None)
assert slice(1, 3, None) != slice(1, 2, None)
assert A()[1] == 1
assert A()[1:2, 3] == (slice(1, 2, None), 3)
assert A()[1:2, 3:4] == (slice(1, 2, None), slice(3, 4, None))
assert A()[1:2, 3:4, 5] == (slice(1, 2, None), slice(3, 4, None), 5)
assert A()[:, :] == (slice(None, None, None), slice(None, None, None))
assert A()[::, :] == (slice(None, None, None), slice(None, None, None))
assert A()[::, :2] == (slice(None, None, None), slice(None, 2, None))
assert A()['b':'c':1, :] == (slice('b', 'c', 1), slice(None, None, None))
assert A()[1:2, :A()[3:4, ::-1]] == (slice(1, 2, None), slice(None, (slice(3, 4, None), slice(None, None, -1)), None))