Commit e0edfcea authored by Vitja Makarov's avatar Vitja Makarov

Merge remote branch 'upstream/master'

parents 61a825b7 76723265
......@@ -86,6 +86,7 @@ class AnnotationCCodeWriter(CCodeWriter):
html_filename = os.path.splitext(target_filename)[0] + ".html"
f = codecs.open(html_filename, "w", encoding="UTF-8")
f.write(u'<!-- Generated by Cython %s on %s -->\n' % (Version.version, time.asctime()))
f.write(u'<html>\n')
f.write(u"""
<head>
......
......@@ -76,6 +76,25 @@ bad:
}
""")
hasattr_utility_code = UtilityCode(
proto = """
static CYTHON_INLINE int __Pyx_HasAttr(PyObject *, PyObject *); /*proto*/
""",
impl = """
static CYTHON_INLINE int __Pyx_HasAttr(PyObject *o, PyObject *n) {
PyObject *v = PyObject_GetAttr(o, n);
if (v) {
Py_DECREF(v);
return 1;
}
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
return 0;
}
return -1;
}
""")
pyexec_utility_code = UtilityCode(
proto = """
#if PY_VERSION_HEX < 0x02040000
......@@ -365,7 +384,8 @@ builtin_function_table = [
utility_code = getattr3_utility_code),
BuiltinFunction('getattr3', "OOO", "O", "__Pyx_GetAttr3", "getattr",
utility_code = getattr3_utility_code), # Pyrex compatibility
BuiltinFunction('hasattr', "OO", "b", "PyObject_HasAttr"),
BuiltinFunction('hasattr', "OO", "b", "__Pyx_HasAttr",
utility_code = hasattr_utility_code),
BuiltinFunction('hash', "O", "h", "PyObject_Hash"),
#('hex', "", "", ""),
#('id', "", "", ""),
......
......@@ -4,7 +4,7 @@
import sys
from Cython.Utils import open_new_file
from DebugFlags import debug_exception_on_error
import DebugFlags
import Options
......@@ -156,7 +156,7 @@ def error(position, message):
if position is None:
raise InternalError(message)
err = CompileError(position, message)
if debug_exception_on_error: raise Exception(err) # debug
if DebugFlags.debug_exception_on_error: raise Exception(err) # debug
report_error(err)
return err
......
......@@ -371,7 +371,7 @@ class ExprNode(Node):
def nonlocally_immutable(self):
# Returns whether this variable is a safe reference, i.e.
# can't be modified as part of globals or closures.
return self.is_temp
return self.is_temp or self.type.is_array or self.type.is_cfunction
# --------------- Type Analysis ------------------
......@@ -1483,6 +1483,8 @@ class NameNode(AtomicExprNode):
return 1
def nonlocally_immutable(self):
if ExprNode.nonlocally_immutable(self):
return True
entry = self.entry
return entry and (entry.is_local or entry.is_arg) and not entry.in_closure
......@@ -3045,10 +3047,11 @@ class SimpleCallNode(CallNode):
if i == 0 and self.self is not None:
continue # self is ok
arg = self.args[i]
if arg.is_name and arg.entry and (
(arg.entry.is_local and not arg.entry.in_closure)
or arg.entry.type.is_cfunction):
# local variables and C functions are safe
if arg.nonlocally_immutable():
# locals, C functions, unassignable types are safe.
pass
elif arg.type.is_cpp_class:
# Assignment has side effects, avoid.
pass
elif env.nogil and arg.type.is_pyobject:
# can't copy a Python reference into a temp in nogil
......
......@@ -2,7 +2,7 @@
# Cython Top Level
#
import os, sys, re
import os, sys, re, codecs
if sys.version_info[:2] < (2, 3):
sys.stderr.write("Sorry, Cython requires Python 2.3 or later\n")
sys.exit(1)
......@@ -607,6 +607,14 @@ def run_pipeline(source, options, full_module_name = None):
# Set up result object
result = create_default_resultobj(source, options)
if options.annotate is None:
# By default, decide based on whether an html file already exists.
html_filename = os.path.splitext(result.c_file)[0] + ".html"
if os.path.exists(html_filename):
line = codecs.open(html_filename, "r", encoding="UTF-8").readline()
if line.startswith(u'<!-- Generated by Cython'):
options.annotate = True
# Get pipeline
if source_ext.lower() == '.py':
pipeline = context.create_py_pipeline(options, result)
......@@ -826,7 +834,7 @@ default_options = dict(
errors_to_stderr = 1,
cplus = 0,
output_file = None,
annotate = False,
annotate = None,
generate_pxi = 0,
working_path = "",
recursive = 0,
......
......@@ -1745,9 +1745,6 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.putln("{")
tempdecl_code = code.insertion_point()
env.use_utility_code(check_binary_version_utility_code)
code.putln("__Pyx_check_binary_version();")
code.putln("#if CYTHON_REFNANNY")
code.putln("void* __pyx_refnanny = NULL;")
code.putln("__Pyx_RefNanny = __Pyx_RefNannyImportAPI(\"refnanny\");")
......@@ -1760,6 +1757,9 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.putln("__pyx_refnanny = __Pyx_RefNanny->SetupContext(\"%s\", __LINE__, __FILE__);"% header3)
code.putln("#endif")
env.use_utility_code(check_binary_version_utility_code)
code.putln("if ( __Pyx_check_binary_version() < 0) %s" % code.error_goto(self.pos))
code.putln("%s = PyTuple_New(0); %s" % (Naming.empty_tuple, code.error_goto_if_null(Naming.empty_tuple, self.pos)));
code.putln("%s = PyBytes_FromStringAndSize(\"\", 0); %s" % (Naming.empty_bytes, code.error_goto_if_null(Naming.empty_bytes, self.pos)));
......@@ -2666,8 +2666,6 @@ int %(wmain_method)s(int argc, wchar_t **argv) {
#else
static int __Pyx_main(int argc, wchar_t **argv) {
#endif
int r = 0;
PyObject* m = NULL;
/* 754 requires that FP exceptions run in "no stop" mode by default,
* and until C vendors implement C99's ways to control FP exceptions,
* Python requires non-stop mode. Alas, some platforms enable FP
......@@ -2679,29 +2677,30 @@ static int __Pyx_main(int argc, wchar_t **argv) {
m = fpgetmask();
fpsetmask(m & ~FP_X_OFL);
#endif
if (argc) {
if (argc && argv)
Py_SetProgramName(argv[0]);
}
Py_Initialize();
if (argc) {
if (argc && argv)
PySys_SetArgv(argc, argv);
}
{ /* init module '%(module_name)s' as '__main__' */
PyObject* m = NULL;
%(module_is_main)s = 1;
#if PY_MAJOR_VERSION < 3
#if PY_MAJOR_VERSION < 3
init%(module_name)s();
#else
#else
m = PyInit_%(module_name)s();
#endif
if (PyErr_Occurred() != NULL) {
r = 1;
#endif
if (PyErr_Occurred()) {
PyErr_Print(); /* This exits with the right code if SystemExit. */
#if PY_MAJOR_VERSION < 3
#if PY_MAJOR_VERSION < 3
if (Py_FlushLine()) PyErr_Clear();
#endif
#endif
return 1;
}
Py_XDECREF(m);
}
Py_Finalize();
return r;
return 0;
}
......@@ -2860,35 +2859,21 @@ check_binary_version_utility_code = UtilityCode(proto="""
static int __Pyx_check_binary_version(void);
""", impl="""
static int __Pyx_check_binary_version(void) {
int res = -1;
PyObject *major_info = NULL;
PyObject *minor_info = NULL;
PyObject *version_info = PySys_GetObject("version_info");
if (version_info == NULL) goto bad;
major_info = PySequence_GetItem(version_info, 0);
if (major_info == NULL || !PyInt_CheckExact(major_info)) goto bad;
minor_info = PySequence_GetItem(version_info, 1);
if (minor_info == NULL || !PyInt_CheckExact(minor_info)) goto bad;
if (PyInt_AsLong(major_info) == PY_MAJOR_VERSION && PyInt_AsLong(minor_info) == PY_MINOR_VERSION) {
res = 0;
} else {
char warning[200];
PyOS_snprintf(warning, sizeof(warning),
"compiletime version (%d, %d) of module %s does not match runtime version (%ld, %ld)",
PY_MAJOR_VERSION, PY_MINOR_VERSION,
__Pyx_MODULE_NAME,
PyInt_AsLong(major_info), PyInt_AsLong(minor_info));
char ctversion[4], rtversion[4];
PyOS_snprintf(ctversion, 4, "%d.%d", PY_MAJOR_VERSION, PY_MINOR_VERSION);
PyOS_snprintf(rtversion, 4, "%s", Py_GetVersion());
if (ctversion[0] != rtversion[0] || ctversion[2] != rtversion[2]) {
char message[200];
PyOS_snprintf(message, sizeof(message),
"compiletime version %s of module '%.100s' "
"does not match runtime version %s",
ctversion, __Pyx_MODULE_NAME, rtversion);
#if PY_VERSION_HEX < 0x02050000
PyErr_Warn(NULL, warning);
return PyErr_Warn(NULL, message);
#else
PyErr_WarnEx(NULL, warning, 0);
return PyErr_WarnEx(NULL, message, 1);
#endif
res = 1;
}
bad:
Py_XDECREF(version_info);
Py_XDECREF(major_info);
Py_XDECREF(minor_info);
return res;
return 0;
}
""")
......@@ -648,6 +648,9 @@ class CArgDeclNode(Node):
is_self_arg = 0
is_type_arg = 0
is_generic = 1
kw_only = 0
not_none = 0
or_none = 0
type = None
name_declarator = None
default_value = None
......@@ -733,6 +736,9 @@ class CSimpleBaseTypeNode(CBaseTypeNode):
child_attrs = []
arg_name = None # in case the argument name was interpreted as a type
module_path = []
is_basic_c_type = False
complex = False
def analyse(self, env, could_be_name = False):
# Return type descriptor.
......@@ -1901,6 +1907,9 @@ class DefNode(FuncDefNode):
entry = None
acquire_gil = 0
self_in_stararg = 0
star_arg = None
starstar_arg = None
doc = None
def __init__(self, pos, **kwds):
FuncDefNode.__init__(self, pos, **kwds)
......@@ -2283,6 +2292,8 @@ class DefNode(FuncDefNode):
arg.hdr_type.declaration_code(arg.hdr_cname))
if not self.entry.is_special and sig.method_flags() == [TypeSlots.method_noargs]:
arg_code_list.append("CYTHON_UNUSED PyObject *unused")
if (self.entry.scope.is_c_class_scope and self.entry.name == "__ipow__"):
arg_code_list.append("CYTHON_UNUSED PyObject *unused")
if sig.has_generic_args:
arg_code_list.append(
"PyObject *%s, PyObject *%s"
......@@ -3308,6 +3319,7 @@ class CClassDefNode(ClassDefNode):
objstruct_name = None
typeobj_name = None
decorators = None
shadow = False
def analyse_declarations(self, env):
#print "CClassDefNode.analyse_declarations:", self.class_name
......@@ -3398,7 +3410,10 @@ class CClassDefNode(ClassDefNode):
visibility = self.visibility,
typedef_flag = self.typedef_flag,
api = self.api,
buffer_defaults = buffer_defaults)
buffer_defaults = buffer_defaults,
shadow = self.shadow)
if self.shadow:
home_scope.lookup(self.class_name).as_variable = self.entry
if home_scope is not env and self.visibility == 'extern':
env.add_imported_entry(self.class_name, self.entry, self.pos)
self.scope = scope = self.entry.type.scope
......
......@@ -2,10 +2,10 @@
# Cython - Compilation-wide options and pragma declarations
#
cache_builtins = 1 # Perform lookups on builtin names only once
cache_builtins = True # Perform lookups on builtin names only once
embed_pos_in_docstring = 0
gcc_branch_hints = 1
embed_pos_in_docstring = False
gcc_branch_hints = True
pre_import = None
docstrings = True
......@@ -14,9 +14,9 @@ docstrings = True
# 0: None, 1+: interned objects, 2+: cdef globals, 3+: types objects
# Mostly for reducing noise for Valgrind, only executes at process exit
# (when all memory will be reclaimed anyways).
generate_cleanup_code = 0
generate_cleanup_code = False
annotate = 0
annotate = False
# This will abort the compilation on the first error occured rather than trying
# to keep going and printing further error messages.
......@@ -29,19 +29,19 @@ fast_fail = False
# i to overflow. Specifically, if this option is set, an error will be
# raised before the loop is entered, wheras without this option the loop
# will execute until an overflowing value is encountered.
convert_range = 1
convert_range = True
# Enable this to allow one to write your_module.foo = ... to overwrite the
# definition if the cpdef function foo, at the cost of an extra dictionary
# lookup on every call.
# If this is 0 it simply creates a wrapper.
lookup_module_cpdef = 0
lookup_module_cpdef = False
# This will set local variables to None rather than NULL which may cause
# surpress what would be an UnboundLocalError in pure Python but eliminates
# checking for NULL on every use, and can decref rather than xdecref at the end.
# WARNING: This is a work in progress, may currently segfault.
init_local_none = 1
init_local_none = True
# Whether or not to embed the Python interpreter, for use in making a
# standalone executable or calling from external libraries.
......@@ -50,7 +50,7 @@ init_local_none = 1
embed = None
# Disables function redefinition, allowing all functions to be declared at
# module creation time. For legacy code only.
# module creation time. For legacy code only, needed for some circular imports.
disable_function_redefinition = False
......
......@@ -1039,6 +1039,27 @@ property NAME:
return ATTR
""", level='c_class')
struct_or_union_wrapper = TreeFragment(u"""
cdef class NAME:
cdef TYPE value
def __init__(self, MEMBER=None):
cdef int count
count = 0
INIT_ASSIGNMENTS
if IS_UNION and count > 1:
raise ValueError, "At most one union member should be specified."
def __str__(self):
return STR_FORMAT % MEMBER_TUPLE
def __repr__(self):
return REPR_FORMAT % MEMBER_TUPLE
""")
init_assignment = TreeFragment(u"""
if VALUE is not None:
ATTR = VALUE
count += 1
""")
def __call__(self, root):
self.env_stack = [root.scope]
# needed to determine if a cdef var is declared after it's used.
......@@ -1121,6 +1142,80 @@ property NAME:
node.analyse_declarations(self.env_stack[-1])
return node
def visit_CStructOrUnionDefNode(self, node):
# Create a wrapper node if needed.
# We want to use the struct type information (so it can't happen
# before this phase) but also create new objects to be declared
# (so it can't happen later).
# Note that we don't return the original node, as it is
# never used after this phase.
if True: # private (default)
return None
self_value = ExprNodes.AttributeNode(
pos = node.pos,
obj = ExprNodes.NameNode(pos=node.pos, name=u"self"),
attribute = EncodedString(u"value"))
var_entries = node.entry.type.scope.var_entries
attributes = []
for entry in var_entries:
attributes.append(ExprNodes.AttributeNode(pos = entry.pos,
obj = self_value,
attribute = entry.name))
# __init__ assignments
init_assignments = []
for entry, attr in zip(var_entries, attributes):
# TODO: branch on visibility
init_assignments.append(self.init_assignment.substitute({
u"VALUE": ExprNodes.NameNode(entry.pos, name = entry.name),
u"ATTR": attr,
}, pos = entry.pos))
# create the class
str_format = u"%s(%s)" % (node.entry.type.name, ("%s, " * len(attributes))[:-2])
wrapper_class = self.struct_or_union_wrapper.substitute({
u"INIT_ASSIGNMENTS": Nodes.StatListNode(node.pos, stats = init_assignments),
u"IS_UNION": ExprNodes.BoolNode(node.pos, value = not node.entry.type.is_struct),
u"MEMBER_TUPLE": ExprNodes.TupleNode(node.pos, args=attributes),
u"STR_FORMAT": ExprNodes.StringNode(node.pos, value = EncodedString(str_format)),
u"REPR_FORMAT": ExprNodes.StringNode(node.pos, value = EncodedString(str_format.replace("%s", "%r"))),
}, pos = node.pos).stats[0]
wrapper_class.class_name = node.name
wrapper_class.shadow = True
class_body = wrapper_class.body.stats
# fix value type
assert isinstance(class_body[0].base_type, Nodes.CSimpleBaseTypeNode)
class_body[0].base_type.name = node.name
# fix __init__ arguments
init_method = class_body[1]
assert isinstance(init_method, Nodes.DefNode) and init_method.name == '__init__'
arg_template = init_method.args[1]
if not node.entry.type.is_struct:
arg_template.kw_only = True
del init_method.args[1]
for entry, attr in zip(var_entries, attributes):
arg = copy.deepcopy(arg_template)
arg.declarator.name = entry.name
init_method.args.append(arg)
# setters/getters
for entry, attr in zip(var_entries, attributes):
# TODO: branch on visibility
if entry.type.is_pyobject:
template = self.basic_pyobject_property
else:
template = self.basic_property
property = template.substitute({
u"ATTR": attr,
}, pos = entry.pos).stats[0]
property.name = entry.name
wrapper_class.body.stats.append(property)
wrapper_class.analyse_declarations(self.env_stack[-1])
return self.visit_CClassDefNode(wrapper_class)
# Some nodes are no longer needed after declaration
# analysis and can be dropped. The analysis was performed
# on these nodes in a seperate recursive process from the
......@@ -1142,9 +1237,6 @@ property NAME:
else:
return None
def visit_CStructOrUnionDefNode(self, node):
return None
def visit_CNameDeclaratorNode(self, node):
if node.name in self.seen_vars_stack[-1]:
entry = self.env_stack[-1].lookup(node.name)
......
......@@ -329,7 +329,7 @@ class Scope(object):
# Return the module-level scope containing this scope.
return self.outer_scope.builtin_scope()
def declare(self, name, cname, type, pos, visibility):
def declare(self, name, cname, type, pos, visibility, shadow = 0):
# Create new entry, and add to dictionary if
# name is not None. Reports a warning if already
# declared.
......@@ -339,7 +339,7 @@ class Scope(object):
# See http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html#Reserved-Names
warning(pos, "'%s' is a reserved name in C." % cname, -1)
entries = self.entries
if name and name in entries:
if name and name in entries and not shadow:
if visibility == 'extern':
warning(pos, "'%s' redeclared " % name, 0)
elif visibility != 'ignore':
......@@ -352,6 +352,7 @@ class Scope(object):
# entries[name].overloaded_alternatives.append(entry)
# else:
# entries[name] = entry
if not shadow:
entries[name] = entry
entry.scope = self
entry.visibility = visibility
......@@ -373,11 +374,11 @@ class Scope(object):
return entry
def declare_type(self, name, type, pos,
cname = None, visibility = 'private', defining = 1):
cname = None, visibility = 'private', defining = 1, shadow = 0):
# Add an entry for a type definition.
if not cname:
cname = name
entry = self.declare(name, cname, type, pos, visibility)
entry = self.declare(name, cname, type, pos, visibility, shadow)
entry.is_type = 1
if defining:
self.type_entries.append(entry)
......@@ -1029,7 +1030,7 @@ class ModuleScope(Scope):
def declare_c_class(self, name, pos, defining = 0, implementing = 0,
module_name = None, base_type = None, objstruct_cname = None,
typeobj_cname = None, visibility = 'private', typedef_flag = 0, api = 0,
buffer_defaults = None):
buffer_defaults = None, shadow = 0):
# If this is a non-extern typedef class, expose the typedef, but use
# the non-typedef struct internally to avoid needing forward
# declarations for anonymous structs.
......@@ -1044,7 +1045,7 @@ class ModuleScope(Scope):
# Look for previous declaration as a type
#
entry = self.lookup_here(name)
if entry:
if entry and not shadow:
type = entry.type
if not (entry.is_type and type.is_extension_type):
entry = None # Will cause redeclaration and produce an error
......@@ -1060,7 +1061,7 @@ class ModuleScope(Scope):
#
# Make a new entry if needed
#
if not entry:
if not entry or shadow:
type = PyrexTypes.PyExtensionType(name, typedef_flag, base_type, visibility == 'extern')
type.pos = pos
type.buffer_defaults = buffer_defaults
......@@ -1072,7 +1073,7 @@ class ModuleScope(Scope):
type.module_name = self.qualified_name
type.typeptr_cname = self.mangle(Naming.typeptr_prefix, name)
entry = self.declare_type(name, type, pos, visibility = visibility,
defining = 0)
defining = 0, shadow = shadow)
entry.is_cclass = True
if objstruct_cname:
type.objstruct_cname = objstruct_cname
......
......@@ -603,7 +603,7 @@ PyNumberMethods = (
MethodSlot(ibinaryfunc, "nb_inplace_multiply", "__imul__"),
MethodSlot(ibinaryfunc, "nb_inplace_divide", "__idiv__", py3 = False),
MethodSlot(ibinaryfunc, "nb_inplace_remainder", "__imod__"),
MethodSlot(ternaryfunc, "nb_inplace_power", "__ipow__"), # NOT iternaryfunc!!!
MethodSlot(ibinaryfunc, "nb_inplace_power", "__ipow__"), # actually ternaryfunc!!!
MethodSlot(ibinaryfunc, "nb_inplace_lshift", "__ilshift__"),
MethodSlot(ibinaryfunc, "nb_inplace_rshift", "__irshift__"),
MethodSlot(ibinaryfunc, "nb_inplace_and", "__iand__"),
......
......@@ -3,7 +3,7 @@
cdef extern from *:
ctypedef char const_char "const char"
cdef extern from "locale.h":
cdef extern from "locale.h" nogil:
struct lconv:
char *decimal_point
......
cdef extern from "math.h":
cdef extern from "math.h" nogil:
enum: M_E
enum: M_LOG2E
......
......@@ -9,9 +9,12 @@ local:
.git: REV := $(shell cat .gitrev)
.git: TMPDIR := $(shell mktemp -d tmprepo.XXXXXX)
.git:
rm -rf $(TMPDIR)
git clone $(REPO) $(TMPDIR)
cd $(TMPDIR); git checkout -b working $(REV)
mv $(TMPDIR)/{.git,.hgtags,.hgignore} .
mv $(TMPDIR)/.hgtags .
mv $(TMPDIR)/.hgignore .
mv $(TMPDIR)/.git .
mv $(TMPDIR)/Doc/s5 Doc/s5
rm -rf $(TMPDIR)
......
......@@ -26,16 +26,16 @@ See LICENSE.txt.
--------------------------
Note that Cython used to ship the Mercurial (hg) repository in its source
Note that Cython used to ship the full version control repository in its source
distribution, but no longer does so due to space constraints. To get the
full source history, make sure you have hg installed, then step into the
full source history, make sure you have git installed, then step into the
base directory of the Cython source distribution and type
make repo
Alternatively, check out the latest developer repository from
http://hg.cython.org/cython-devel
https://github.com/cython/cython
......
......@@ -3,9 +3,6 @@ from distutils.sysconfig import get_python_lib
import os, os.path
import sys
if 'sdist' in sys.argv and sys.platform != "win32":
assert os.system("git show-ref -s HEAD > .gitrev") == 0
if sys.platform == "darwin":
# Don't create resource files on OS X tar.
os.environ['COPY_EXTENDED_ATTRIBUTES_DISABLE'] = 'true'
......@@ -18,6 +15,15 @@ def add_command_class(name, cls):
cmdclasses[name] = cls
setup_args['cmdclass'] = cmdclasses
from distutils.command.sdist import sdist as sdist_orig
class sdist(sdist_orig):
def run(self):
if (sys.platform != "win32" and
os.path.isdir('.git')):
assert os.system("git show-ref -s HEAD > .gitrev") == 0
sdist_orig.run(self)
add_command_class('sdist', sdist)
if sys.version_info[0] >= 3:
import lib2to3.refactor
from distutils.command.build_py \
......
......@@ -13,7 +13,6 @@ cpp_structs
with_statement_module_level_T536
function_as_method_T494
closure_inside_cdef_T554
ipow_crash_T562
pure_mode_cmethod_inheritance_T583
genexpr_iterable_lookup_T600
for_from_pyvar_loop_T601
......
class Foo:
@property
def foo(self):
return None
@property
def bar(self):
raise AttributeError
@property
def baz(self):
return int(1)/int(0)
def wrap_hasattr(obj, name):
"""
>>> wrap_hasattr(None, "abc")
False
>>> wrap_hasattr(list, "append")
True
>>> wrap_hasattr(Foo(), "foo")
True
>>> wrap_hasattr(Foo(), "spam")
False
>>> wrap_hasattr(Foo(), "bar")
False
>>> wrap_hasattr(Foo(), "baz") #doctest: +ELLIPSIS
Traceback (most recent call last):
...
ZeroDivisionError: ...
"""
return hasattr(obj, name)
class IPOW:
"""
>>> IPOW().__ipow__('a')
a
>>> x = IPOW()
>>> x **= 'z'
z
"""
def __ipow__(self, other):
print ("%s" % other)
cdef class CrashIPOW:
"""
>>> CrashIPOW().__ipow__('a')
a
>>> x = CrashIPOW()
>>> x **= 'z'
z
"""
def __ipow__(self, other, mod):
print "%s, %s" % (other, mod)
def __ipow__(self, other):
print ("%s" % other)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment