diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index 9e5b3f1f337..4e8eb49bc15 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -31,6 +31,7 @@ Special Base Classes, Decorators, etc. sage/misc/method_decorator sage/misc/object_multiplexer sage/misc/fast_methods + sage/misc/call Lists and Iteration, etc. ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/sage/categories/category_with_axiom.py b/src/sage/categories/category_with_axiom.py index 524fb42c386..03bb998a7aa 100644 --- a/src/sage/categories/category_with_axiom.py +++ b/src/sage/categories/category_with_axiom.py @@ -1660,7 +1660,7 @@ class ``Sets.Finite``), or in a separate file (typically in a class from sage.misc.cachefunc import cached_method, cached_function from sage.misc.lazy_attribute import lazy_class_attribute from sage.misc.lazy_import import LazyImport -from sage.misc.misc import call_method +from sage.misc.call import call_method from sage.categories.category import Category from sage.categories.category_singleton import Category_singleton from sage.categories.category_types import Category_over_base_ring diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index 91875a4a0f6..d01853793fd 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -15,7 +15,7 @@ from sage.misc.cachefunc import cached_method, cached_in_parent_method from sage.misc.lazy_import import LazyImport from sage.misc.constant_function import ConstantFunction -from sage.misc.misc import attrcall +from sage.misc.call import attrcall from sage.categories.category_singleton import Category_singleton from sage.categories.enumerated_sets import EnumeratedSets from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets diff --git a/src/sage/categories/finite_posets.py b/src/sage/categories/finite_posets.py index b01ea7f4818..0442d33b5d1 100644 --- a/src/sage/categories/finite_posets.py +++ b/src/sage/categories/finite_posets.py @@ -1889,7 +1889,7 @@ def order_ideals_lattice(self, as_ideals=True, facade=None): if facade is None: facade = self._is_facade if as_ideals: - from sage.misc.misc import attrcall + from sage.misc.call import attrcall from sage.sets.set import Set ideals = [Set(self.order_ideal(antichain)) for antichain in self.antichains()] diff --git a/src/sage/categories/finite_semigroups.py b/src/sage/categories/finite_semigroups.py index 1dc8a494619..76c082af096 100644 --- a/src/sage/categories/finite_semigroups.py +++ b/src/sage/categories/finite_semigroups.py @@ -11,7 +11,7 @@ # ***************************************************************************** from sage.misc.cachefunc import cached_method -from sage.misc.misc import attrcall +from sage.misc.call import attrcall from sage.categories.category_with_axiom import CategoryWithAxiom diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index 4a1ec72bedc..7edb48e10fc 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -14,7 +14,7 @@ from __future__ import print_function, absolute_import from sage.misc.abstract_method import abstract_method, AbstractMethod -from sage.misc.misc import attrcall +from sage.misc.call import attrcall from sage.misc.cachefunc import cached_method, cached_in_parent_method from sage.misc.lazy_attribute import lazy_attribute from sage.misc.lazy_import import LazyImport diff --git a/src/sage/combinat/root_system/type_dual.py b/src/sage/combinat/root_system/type_dual.py index 885724a5eb1..cdcc1601c9c 100644 --- a/src/sage/combinat/root_system/type_dual.py +++ b/src/sage/combinat/root_system/type_dual.py @@ -10,7 +10,7 @@ # **************************************************************************** from __future__ import print_function, absolute_import -from sage.misc.misc import attrcall +from sage.misc.call import attrcall from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.combinat.root_system import cartan_type diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 82bc94ba73b..73da832155d 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -662,7 +662,7 @@ def corresponding_basis_over(self, R): put on a more robust and systematic footing. """ from sage.combinat.sf.sf import SymmetricFunctions - from sage.misc.misc import attrcall + from sage.misc.call import attrcall try: return attrcall(self._basis)(SymmetricFunctions(R)) except AttributeError: # or except (AttributeError, ValueError): diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py index 37b660fa834..66edc59bc82 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -27,7 +27,7 @@ from sage.rings.infinity import infinity from sage.functions.other import real, imag, sqrt from sage.misc.lazy_import import lazy_import -lazy_import('sage.misc.misc', 'attrcall') +lazy_import('sage.misc.call', 'attrcall') class HyperbolicModelCoercion(Morphism): """ diff --git a/src/sage/matrix/matrix_symbolic_dense.pyx b/src/sage/matrix/matrix_symbolic_dense.pyx index 4acdd287063..e4e009e68d2 100644 --- a/src/sage/matrix/matrix_symbolic_dense.pyx +++ b/src/sage/matrix/matrix_symbolic_dense.pyx @@ -772,7 +772,7 @@ cdef class Matrix_symbolic_dense(Matrix_generic_dense): [ x^2 + 2 -2*x + 3] [ -4*x + 6 x^2 - 6*x + 11] """ - from sage.misc.misc import attrcall + from sage.misc.call import attrcall return self.apply_map(attrcall('expand')) def variables(self): diff --git a/src/sage/misc/all.py b/src/sage/misc/all.py index 75d8295622c..1eb98169cdc 100644 --- a/src/sage/misc/all.py +++ b/src/sage/misc/all.py @@ -9,11 +9,13 @@ exists, forall, is_iterator, random_sublist, walltime, repr_lincomb, - pad_zeros, attrcall, + pad_zeros, SAGE_DB, SAGE_TMP, newton_method_sizes, compose, nest) +from .call import attrcall + from .banner import version, banner from .temporary_file import tmp_dir, tmp_filename diff --git a/src/sage/misc/call.py b/src/sage/misc/call.py new file mode 100644 index 00000000000..25c1c451864 --- /dev/null +++ b/src/sage/misc/call.py @@ -0,0 +1,182 @@ +""" +Attribute and method calling +""" + +# **************************************************************************** +# Copyright (C) 2008 Mike Hansen +# Copyright (C) 2010, 2013 Nicolas M. Thiery +# Copyright (C) 2018 Frédéric Chapoton +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +############################################# +# Operators +############################################# +class AttrCallObject(object): + def __init__(self, name, args, kwds): + """ + TESTS:: + + sage: f = attrcall('core', 3); f + *.core(3) + sage: TestSuite(f).run() + """ + self.name = name + self.args = args + self.kwds = kwds + + def __call__(self, x, *args): + """ + Gets the ``self.name`` method from ``x``, calls it with + ``self.args`` and ``args`` as positional parameters and + ``self.kwds`` as keyword parameters, and returns the result. + + EXAMPLES:: + + sage: core = attrcall('core', 3) + sage: core(Partition([4,2])) + [4, 2] + + sage: series = attrcall('series', x) + sage: series(sin(x), 4) + 1*x + (-1/6)*x^3 + Order(x^4) + """ + return getattr(x, self.name)(*(self.args + args), **self.kwds) + + def __repr__(self): + """ + Return a string representation of this object. + + The star in the output represents the object passed into ``self``. + + EXAMPLES:: + + sage: attrcall('core', 3) + *.core(3) + sage: attrcall('hooks', flatten=True) + *.hooks(flatten=True) + sage: attrcall('hooks', 3, flatten=True) + *.hooks(3, flatten=True) + """ + s = "*.%s(%s" % (self.name, ", ".join(map(repr, self.args))) + if self.kwds: + if self.args: + s += ", " + s += ", ".join("%s=%s" % keyvalue for keyvalue in self.kwds.items()) + s += ")" + return s + + def __eq__(self, other): + """ + Equality testing + + EXAMPLES:: + + sage: attrcall('core', 3, flatten = True) == attrcall('core', 3, flatten = True) + True + sage: attrcall('core', 2) == attrcall('core', 3) + False + sage: attrcall('core', 2) == 1 + False + """ + return self.__class__ == other.__class__ and self.__dict__ == other.__dict__ + + def __ne__(self, other): + """ + Equality testing + + EXAMPLES:: + + sage: attrcall('core', 3, flatten = True) != attrcall('core', 3, flatten = True) + False + sage: attrcall('core', 2) != attrcall('core', 3) + True + sage: attrcall('core', 2) != 1 + True + """ + return not self == other + + def __hash__(self): + """ + Hash value + + This method tries to ensure that, when two ``attrcall`` + objects are equal, they have the same hash value. + + .. warning:: dicts are not hashable, so we instead hash their + items; however the order of those items might differ. The + proper fix would be to use a frozen dict for ``kwds``, when + frozen dicts will be available in Python. + + EXAMPLES:: + + sage: x = attrcall('core', 3, flatten = True, blah = 1) + sage: hash(x) # random # indirect doctest + 210434060 + sage: type(hash(x)) + + sage: y = attrcall('core', 3, blah = 1, flatten = True) + sage: hash(y) == hash(x) + True + sage: y = attrcall('core', 3, flatten = True, blah = 2) + sage: hash(y) != hash(x) + True + sage: hash(attrcall('core', 2)) != hash(attrcall('core', 3)) + True + sage: hash(attrcall('core', 2)) != hash(1) + True + + Note: a missing ``__hash__`` method here used to break the + unique representation of parents taking ``attrcall`` objects + as input; see :trac:`8911`. + """ + return hash((self.args, tuple(sorted(self.kwds.items())))) + + +def attrcall(name, *args, **kwds): + """ + Returns a callable which takes in an object, gets the method named + name from that object, and calls it with the specified arguments + and keywords. + + INPUT: + + - ``name`` - a string of the name of the method you + want to call + + - ``args, kwds`` - arguments and keywords to be passed + to the method + + EXAMPLES:: + + sage: f = attrcall('core', 3); f + *.core(3) + sage: [f(p) for p in Partitions(5)] + [[2], [1, 1], [1, 1], [3, 1, 1], [2], [2], [1, 1]] + """ + return AttrCallObject(name, args, kwds) + + +def call_method(obj, name, *args, **kwds): + """ + Call the method ``name`` on ``obj``. + + This has to exist somewhere in Python!!! + + .. SEEALSO:: :func:`operator.methodcaller` :func:`attrcal` + + EXAMPLES:: + + sage: from sage.misc.call import call_method + sage: call_method(1, "__add__", 2) + 3 + """ + return getattr(obj, name)(*args, **kwds) + +from sage.misc.persist import register_unpickle_override +register_unpickle_override("sage.misc.misc", "call_method", call_method) diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index b3c538d7c88..1583f4d8626 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -49,6 +49,11 @@ from .lazy_string import lazy_string import sage.server.support +from sage.misc.lazy_import import lazy_import + +lazy_import("sage.misc.call", ["AttrCallObject", "attrcall", "call_method"], + deprecation=29869) + from sage.env import DOT_SAGE, HOSTNAME LOCAL_IDENTIFIER = '%s.%s' % (HOSTNAME, os.getpid()) @@ -1563,172 +1568,6 @@ def embedded(): """ return sage.server.support.EMBEDDED_MODE - -############################################# -# Operators -############################################# -class AttrCallObject(object): - def __init__(self, name, args, kwds): - """ - TESTS:: - - sage: f = attrcall('core', 3); f - *.core(3) - sage: TestSuite(f).run() - """ - self.name = name - self.args = args - self.kwds = kwds - - def __call__(self, x, *args): - """ - Gets the ``self.name`` method from ``x``, calls it with - ``self.args`` and ``args`` as positional parameters and - ``self.kwds`` as keyword parameters, and returns the result. - - EXAMPLES:: - - sage: core = attrcall('core', 3) - sage: core(Partition([4,2])) - [4, 2] - - sage: series = attrcall('series', x) - sage: series(sin(x), 4) - 1*x + (-1/6)*x^3 + Order(x^4) - """ - return getattr(x, self.name)(*(self.args + args), **self.kwds) - - def __repr__(self): - """ - Return a string representation of this object. - - The star in the output represents the object passed into ``self``. - - EXAMPLES:: - - sage: attrcall('core', 3) - *.core(3) - sage: attrcall('hooks', flatten=True) - *.hooks(flatten=True) - sage: attrcall('hooks', 3, flatten=True) - *.hooks(3, flatten=True) - """ - s = "*.%s(%s" % (self.name, ", ".join(map(repr, self.args))) - if self.kwds: - if self.args: - s += ", " - s += ", ".join("%s=%s" % keyvalue for keyvalue in self.kwds.items()) - s += ")" - return s - - def __eq__(self, other): - """ - Equality testing - - EXAMPLES:: - - sage: attrcall('core', 3, flatten = True) == attrcall('core', 3, flatten = True) - True - sage: attrcall('core', 2) == attrcall('core', 3) - False - sage: attrcall('core', 2) == 1 - False - """ - return self.__class__ == other.__class__ and self.__dict__ == other.__dict__ - - def __ne__(self, other): - """ - Equality testing - - EXAMPLES:: - - sage: attrcall('core', 3, flatten = True) != attrcall('core', 3, flatten = True) - False - sage: attrcall('core', 2) != attrcall('core', 3) - True - sage: attrcall('core', 2) != 1 - True - """ - return not self == other - - def __hash__(self): - """ - Hash value - - This method tries to ensure that, when two ``attrcall`` - objects are equal, they have the same hash value. - - .. warning:: dicts are not hashable, so we instead hash their - items; however the order of those items might differ. The - proper fix would be to use a frozen dict for ``kwds``, when - frozen dicts will be available in Python. - - EXAMPLES:: - - sage: x = attrcall('core', 3, flatten = True, blah = 1) - sage: hash(x) # random # indirect doctest - 210434060 - sage: type(hash(x)) - - sage: y = attrcall('core', 3, blah = 1, flatten = True) - sage: hash(y) == hash(x) - True - sage: y = attrcall('core', 3, flatten = True, blah = 2) - sage: hash(y) != hash(x) - True - sage: hash(attrcall('core', 2)) != hash(attrcall('core', 3)) - True - sage: hash(attrcall('core', 2)) != hash(1) - True - - Note: a missing ``__hash__`` method here used to break the - unique representation of parents taking ``attrcall`` objects - as input; see :trac:`8911`. - """ - return hash((self.args, tuple(sorted(self.kwds.items())))) - - -def attrcall(name, *args, **kwds): - """ - Returns a callable which takes in an object, gets the method named - name from that object, and calls it with the specified arguments - and keywords. - - INPUT: - - - ``name`` - a string of the name of the method you - want to call - - - ``args, kwds`` - arguments and keywords to be passed - to the method - - EXAMPLES:: - - sage: f = attrcall('core', 3); f - *.core(3) - sage: [f(p) for p in Partitions(5)] - [[2], [1, 1], [1, 1], [3, 1, 1], [2], [2], [1, 1]] - """ - return AttrCallObject(name, args, kwds) - - -def call_method(obj, name, *args, **kwds): - """ - Call the method ``name`` on ``obj``. - - This has to exist somewhere in Python!!! - - .. SEEALSO:: :func:`operator.methodcaller` :func:`attrcal` - - EXAMPLES:: - - sage: from sage.misc.misc import call_method - sage: call_method(1, "__add__", 2) - 3 - """ - return getattr(obj, name)(*args, **kwds) - - def is_in_string(line, pos): r""" Returns True if the character at position pos in line occurs diff --git a/src/sage/modules/with_basis/morphism.py b/src/sage/modules/with_basis/morphism.py index 2978bf1f564..435fd68ee3c 100644 --- a/src/sage/modules/with_basis/morphism.py +++ b/src/sage/modules/with_basis/morphism.py @@ -107,7 +107,7 @@ from sage.categories.fields import Fields from sage.categories.modules import Modules -from sage.misc.misc import attrcall +from sage.misc.call import attrcall # The identity function would deserve a more canonical location from sage.misc.c3_controlled import identity from sage.categories.commutative_additive_semigroups import CommutativeAdditiveSemigroups diff --git a/src/sage/sets/cartesian_product.py b/src/sage/sets/cartesian_product.py index 5ea1ac9ccc9..edd658daf4e 100644 --- a/src/sage/sets/cartesian_product.py +++ b/src/sage/sets/cartesian_product.py @@ -17,7 +17,7 @@ import numbers -from sage.misc.misc import attrcall +from sage.misc.call import attrcall from sage.misc.cachefunc import cached_method from sage.categories.sets_cat import Sets diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index ed9b550a0b9..98004ccecfe 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -45,7 +45,7 @@ from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.misc.lazy_import import lazy_import from sage.rings.integer import Integer -from sage.misc.misc import AttrCallObject +from sage.misc.call import AttrCallObject lazy_import('sage.combinat.combinat', 'CombinatorialClass') def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=False, name=None): diff --git a/src/sage/tests/finite_poset.py b/src/sage/tests/finite_poset.py index fca907d8681..edd651d501d 100644 --- a/src/sage/tests/finite_poset.py +++ b/src/sage/tests/finite_poset.py @@ -8,7 +8,7 @@ """ from sage.misc.prandom import randint -from sage.misc.misc import attrcall +from sage.misc.call import attrcall from functools import reduce implications = { @@ -156,7 +156,7 @@ def test_finite_lattice(L): from sage.misc.prandom import randint from sage.misc.flatten import flatten - from sage.misc.misc import attrcall + from sage.misc.call import attrcall from sage.misc.sageinspect import sage_getargspec @@ -460,7 +460,7 @@ def test_finite_poset(P): from sage.combinat.subset import Subsets from sage.misc.prandom import shuffle - from sage.misc.misc import attrcall + from sage.misc.call import attrcall e = P.random_element() P_one_less = P.subposet([x for x in P if x != e])