diff --git a/IPython/Extensions/clearcmd.py b/IPython/Extensions/clearcmd.py index 58eb193..dc6de6d 100644 --- a/IPython/Extensions/clearcmd.py +++ b/IPython/Extensions/clearcmd.py @@ -16,7 +16,7 @@ def clear_f(self,arg): api = self.getapi() for target in arg.split(): if target == 'out': - print "Flushing output cache (%d entries)" % len(api.user_ns()['_oh']) + print "Flushing output cache (%d entries)" % len(api.user_ns['_oh']) self.outputcache.flush() elif target == 'in': print "Flushing input history" diff --git a/IPython/Extensions/ext_rescapture.py b/IPython/Extensions/ext_rescapture.py index c73b1c2..5ab0437 100644 --- a/IPython/Extensions/ext_rescapture.py +++ b/IPython/Extensions/ext_rescapture.py @@ -39,11 +39,11 @@ def hnd_syscmd(line,mo): return itpl('$var = _ip.magic($expr)') def install_re_handler(pat, hnd): - ip.meta().re_prefilters.append((re.compile(pat), hnd)) + ip.meta.re_prefilters.append((re.compile(pat), hnd)) def init_handlers(): - ip.meta().re_prefilters = [] + ip.meta.re_prefilters = [] install_re_handler('(?P[\w\.]+)\s*=\s*%(?P.*)', hnd_magic @@ -56,11 +56,11 @@ def init_handlers(): init_handlers() def regex_prefilter_f(self,line): - for pat, handler in ip.meta().re_prefilters: + for pat, handler in ip.meta.re_prefilters: mo = pat.match(line) if mo: return handler(line,mo) raise IPython.ipapi.TryNext -ip.set_hook('input_prefilter', regex_prefilter_f) \ No newline at end of file +ip.set_hook('input_prefilter', regex_prefilter_f) diff --git a/IPython/Extensions/ipipe.py b/IPython/Extensions/ipipe.py index ef73ed7..7f119db 100644 --- a/IPython/Extensions/ipipe.py +++ b/IPython/Extensions/ipipe.py @@ -259,7 +259,7 @@ def item(iterator, index, default=noitem): def getglobals(g): if g is None: if ipapi is not None: - return ipapi.get().user_ns() + return ipapi.get().user_ns else: return globals() return g diff --git a/IPython/Extensions/ipy_sane_defaults.py b/IPython/Extensions/ipy_defaults.py similarity index 100% rename from IPython/Extensions/ipy_sane_defaults.py rename to IPython/Extensions/ipy_defaults.py diff --git a/IPython/Extensions/ipy_system_conf.py b/IPython/Extensions/ipy_system_conf.py index 1f4ae90..b6f5d60 100644 --- a/IPython/Extensions/ipy_system_conf.py +++ b/IPython/Extensions/ipy_system_conf.py @@ -21,4 +21,4 @@ import pspersistence # %store magic import clearcmd # %clear # Basic readline config -o = ip.options() \ No newline at end of file +o = ip.options diff --git a/IPython/Extensions/pspersistence.py b/IPython/Extensions/pspersistence.py index 6592002..199fbe8 100644 --- a/IPython/Extensions/pspersistence.py +++ b/IPython/Extensions/pspersistence.py @@ -17,14 +17,14 @@ from IPython.FakeModule import FakeModule def restore_aliases(self): ip = self.getapi() - staliases = ip.getdb().get('stored_aliases', {}) + staliases = ip.db.get('stored_aliases', {}) for k,v in staliases.items(): #print "restore alias",k,v # dbg self.alias_table[k] = v def refresh_variables(ip): - db = ip.getdb() + db = ip.db for key in db.keys('autorestore/*'): # strip autorestore justkey = os.path.basename(key) @@ -35,7 +35,7 @@ def refresh_variables(ip): print "The error was:",sys.exc_info()[0] else: #print "restored",justkey,"=",obj #dbg - ip.user_ns()[justkey] = obj + ip.user_ns[justkey] = obj @@ -83,7 +83,7 @@ def magic_store(self, parameter_s=''): opts,argsl = self.parse_options(parameter_s,'drz',mode='string') args = argsl.split(None,1) ip = self.getapi() - db = ip.getdb() + db = ip.db # delete if opts.has_key('d'): try: @@ -148,7 +148,7 @@ def magic_store(self, parameter_s=''): # %store foo try: - obj = ip.user_ns()[args[0]] + obj = ip.user_ns[args[0]] except KeyError: # it might be an alias if args[0] in self.alias_table: diff --git a/IPython/Magic.py b/IPython/Magic.py index cf736be..dfe271d 100644 --- a/IPython/Magic.py +++ b/IPython/Magic.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Magic functions for InteractiveShell. -$Id: Magic.py 1209 2006-03-12 20:34:28Z vivainio $""" +$Id: Magic.py 1314 2006-05-19 18:24:14Z fperez $""" #***************************************************************************** # Copyright (C) 2001 Janko Hauser and @@ -26,6 +26,7 @@ import inspect import os import pdb import pydoc +import shlex import sys import re import tempfile @@ -317,7 +318,7 @@ license. To use profiling, please install"python2.3-profiler" from non-free.""") if len(args) >= 1: # If the list of inputs only has 0 or 1 thing in it, there's no # need to look for options - argv = shlex_split(arg_str) + argv = shlex.split(arg_str) # Do regular option processing try: opts,args = getopt(argv,opt_str,*long_opts) @@ -1654,12 +1655,10 @@ Currently the magic system has the following functions:\n""" order = min(-int(math.floor(math.log10(best)) // 3), 3) else: order = 3 - print "%d loops, best of %d: %.*g %s per loop" % (number, repeat, precision, + print "%d loops, best of %d: %.*g %s per loop" % (number, repeat, + precision, best * scaling[order], units[order]) - - - def magic_time(self,parameter_s = ''): """Time execution of a Python statement or expression. @@ -1669,9 +1668,8 @@ Currently the magic system has the following functions:\n""" is always reported as 0, since it can not be measured. This function provides very basic timing functionality. In Python - 2.3, the timeit module offers more control and sophistication, but for - now IPython supports Python 2.2, so we can not rely on timeit being - present. + 2.3, the timeit module offers more control and sophistication, so this + could be rewritten to use it (patches welcome). Some examples: @@ -2005,7 +2003,10 @@ Currently the magic system has the following functions:\n""" # custom exceptions class DataIsObject(Exception): pass - opts,args = self.parse_options(parameter_s,'prx') + opts,args = self.parse_options(parameter_s,'prxn=i') + + print 'opt.n: <%r>' % opts.n # dbg + # Set a few locals from the options for convenience: opts_p = opts.has_key('p') opts_r = opts.has_key('r') @@ -2421,7 +2422,7 @@ Defaulting color scheme to 'NoColor'""" # Call again init_auto_alias() so we get 'rm -i' and other # modified aliases since %rehashx will probably clobber them self.shell.init_auto_alias() - db = self.getapi().getdb() + db = self.getapi().db db['syscmdlist'] = syscmdlist finally: os.chdir(savedir) @@ -2936,7 +2937,7 @@ Defaulting color scheme to 'NoColor'""" ipinstallation = path(IPython.__file__).dirname() upgrade_script = sys.executable + " " + ipinstallation / 'upgrade_dir.py' src_config = ipinstallation / 'UserConfig' - userdir = path(ip.options().ipythondir) + userdir = path(ip.options.ipythondir) cmd = upgrade_script + " " + src_config + " " + userdir print ">",cmd shell(cmd) diff --git a/IPython/UserConfig/ipy_profile_sh.py b/IPython/UserConfig/ipy_profile_sh.py index 610063a..2ad936c 100644 --- a/IPython/UserConfig/ipy_profile_sh.py +++ b/IPython/UserConfig/ipy_profile_sh.py @@ -1,26 +1,23 @@ -""" Shell mode for ipython +"""Shell mode for IPython. Start ipython in shell mode by invoking "ipython -p sh" (the old version, "ipython -p pysh" still works but this is the more "modern" shell mode and is recommended for users who don't care about pysh-mode compatibility) - - """ - from IPython import ipapi import os,textwrap # The import below effectively obsoletes your old-style ipythonrc[.ini], # so consider yourself warned! -import ipy_sane_defaults +import ipy_defaults def main(): ip = ipapi.get() - o = ip.options() + o = ip.options # autocall to "full" mode (smart mode is default, I like full mode) o.autocall = 2 @@ -66,7 +63,7 @@ def main(): # now alias all syscommands - db = ip.getdb() + db = ip.db syscmds = db.get("syscmdlist",[] ) if not syscmds: diff --git a/IPython/UserConfig/ipy_user_conf.py b/IPython/UserConfig/ipy_user_conf.py index fd93cf1..936cc30 100644 --- a/IPython/UserConfig/ipy_user_conf.py +++ b/IPython/UserConfig/ipy_user_conf.py @@ -20,10 +20,10 @@ import IPython.ipapi ip = IPython.ipapi.get() # You probably want to uncomment this if you did %upgrade -nolegacy -# import ipy_sane_defaults +# import ipy_defaults def main(): - o = ip.options() + o = ip.options # An example on how to set options #o.autocall = 1 diff --git a/IPython/__init__.py b/IPython/__init__.py index 10b64bc..9d6e266 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -25,9 +25,9 @@ IPython tries to: iii - serve as an embeddable, ready to go interpreter for your own programs. -IPython requires Python 2.2 or newer. +IPython requires Python 2.3 or newer. -$Id: __init__.py 1110 2006-01-30 20:43:30Z vivainio $""" +$Id: __init__.py 1314 2006-05-19 18:24:14Z fperez $""" #***************************************************************************** # Copyright (C) 2001-2004 Fernando Perez. diff --git a/IPython/completer.py b/IPython/completer.py index 362260f..211b42d 100644 --- a/IPython/completer.py +++ b/IPython/completer.py @@ -69,6 +69,7 @@ import glob import keyword import os import re +import shlex import sys import IPython.rlineimpl as readline @@ -80,8 +81,7 @@ try: except NameError: from sets import Set as set - -from IPython.genutils import shlex_split,debugx +from IPython.genutils import debugx __all__ = ['Completer','IPCompleter'] @@ -346,7 +346,7 @@ class IPCompleter(Completer): lbuf = self.lbuf open_quotes = 0 # track strings with open quotes try: - lsplit = shlex_split(lbuf)[-1] + lsplit = shlex.split(lbuf)[-1] except ValueError: # typically an unmatched ", or backslash without escaped char. if lbuf.count('"')==1: diff --git a/IPython/demo.py b/IPython/demo.py index 454c13a..35c65eb 100644 --- a/IPython/demo.py +++ b/IPython/demo.py @@ -122,10 +122,11 @@ print 'bye!' import exceptions import os import re +import shlex import sys from IPython.PyColorize import Parser -from IPython.genutils import marquee, shlex_split, file_read, file_readlines +from IPython.genutils import marquee, file_read, file_readlines __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError'] @@ -165,7 +166,7 @@ class Demo: """ self.fname = fname - self.sys_argv = [fname] + shlex_split(arg_str) + self.sys_argv = [fname] + shlex.split(arg_str) self.auto_all = auto_all # get a few things from ipython. While it's a bit ugly design-wise, diff --git a/IPython/genutils.py b/IPython/genutils.py index 6fa285f..2aaaec2 100644 --- a/IPython/genutils.py +++ b/IPython/genutils.py @@ -5,7 +5,7 @@ General purpose utilities. This is a grab-bag of stuff I find useful in most programs I write. Some of these things are also convenient when working at the command line. -$Id: genutils.py 1217 2006-03-16 21:49:01Z fperez $""" +$Id: genutils.py 1314 2006-05-19 18:24:14Z fperez $""" #***************************************************************************** # Copyright (C) 2001-2006 Fernando Perez. @@ -14,8 +14,6 @@ $Id: genutils.py 1217 2006-03-16 21:49:01Z fperez $""" # the file COPYING, distributed as part of this software. #***************************************************************************** -from __future__ import generators # 2.2 compatibility - from IPython import Release __author__ = '%s <%s>' % Release.authors['Fernando'] __license__ = Release.license @@ -26,7 +24,6 @@ import __main__ import commands import os import re -import shlex import shutil import sys import tempfile @@ -40,66 +37,6 @@ from path import path if os.name == "nt": from IPython.winconsole import get_console_size -# Build objects which appeared in Python 2.3 for 2.2, to make ipython -# 2.2-friendly -try: - basestring -except NameError: - import types - basestring = (types.StringType, types.UnicodeType) - True = 1==1 - False = 1==0 - - def enumerate(obj): - i = -1 - for item in obj: - i += 1 - yield i, item - - # add these to the builtin namespace, so that all modules find them - import __builtin__ - __builtin__.basestring = basestring - __builtin__.True = True - __builtin__.False = False - __builtin__.enumerate = enumerate - -# Try to use shlex.split for converting an input string into a sys.argv-type -# list. This appeared in Python 2.3, so here's a quick backport for 2.2. -try: - shlex_split = shlex.split -except AttributeError: - _quotesre = re.compile(r'[\'"](.*)[\'"]') - _wordchars = ('abcdfeghijklmnopqrstuvwxyz' - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.~*?' - 'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' - '�����ÅÆÇ�ÉÊ�Ì�Î��ÑÒÓÔÕÖ�ÙÚ�Ü�Þ%s' - % os.sep) - - def shlex_split(s): - """Simplified backport to Python 2.2 of shlex.split(). - - This is a quick and dirty hack, since the shlex module under 2.2 lacks - several of the features needed to really match the functionality of - shlex.split() in 2.3.""" - - lex = shlex.shlex(StringIO(s)) - # Try to get options, extensions and path separators as characters - lex.wordchars = _wordchars - lex.commenters = '' - # Make a list out of the lexer by hand, since in 2.2 it's not an - # iterator. - lout = [] - while 1: - token = lex.get_token() - if token == '': - break - # Try to handle quoted tokens correctly - quotes = _quotesre.match(token) - if quotes: - token = quotes.group(1) - lout.append(token) - return lout - #**************************************************************************** # Exceptions class Error(Exception): @@ -1622,16 +1559,6 @@ def list2dict2(lst,default=''): def flatten(seq): """Flatten a list of lists (NOT recursive, only works for 2d lists).""" - # bug in python??? (YES. Fixed in 2.2, let's leave the kludgy fix in). - - # if the x=0 isn't made, a *global* variable x is left over after calling - # this function, with the value of the last element in the return - # list. This does seem like a bug big time to me. - - # the problem is fixed with the x=0, which seems to force the creation of - # a local name - - x = 0 return [x for subseq in seq for x in subseq] #---------------------------------------------------------------------------- diff --git a/IPython/ipapi.py b/IPython/ipapi.py index 0acea7e..7cbcce4 100644 --- a/IPython/ipapi.py +++ b/IPython/ipapi.py @@ -25,7 +25,8 @@ personal configuration (as opposed to boilerplate ipythonrc-PROFILENAME stuff) in there. ----------------------------------------------- -import IPython.ipapi as ip +import IPython.ipapi +ip = IPython.ipapi.get() def ankka_f(self, arg): print "Ankka",self,"says uppercase:",arg.upper() @@ -53,55 +54,82 @@ def jed_editor(self,filename, linenum=None): ip.set_hook('editor',jed_editor) -o = ip.options() +o = ip.options o.autocall = 2 # FULL autocall mode print "done!" - ''' - + +# stdlib imports +import sys + +# our own +from IPython.genutils import warn,error class TryNext(Exception): - """ Try next hook exception. + """Try next hook exception. - Raise this in your hook function to indicate that the next - hook handler should be used to handle the operation. - If you pass arguments to the constructor those arguments will be - used by the next hook instead of the original ones. + Raise this in your hook function to indicate that the next hook handler + should be used to handle the operation. If you pass arguments to the + constructor those arguments will be used by the next hook instead of the + original ones. """ def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs - # contains the most recently instantiated IPApi -_recent = None + +class IPythonNotRunning: + """Dummy do-nothing class. + + Instances of this class return a dummy attribute on all accesses, which + can be called and warns. This makes it easier to write scripts which use + the ipapi.get() object for informational purposes to operate both with and + without ipython. Obviously code which uses the ipython object for + computations will not work, but this allows a wider range of code to + transparently work whether ipython is being used or not.""" + + def __str__(self): + return "" + + __repr__ = __str__ + + def __getattr__(self,name): + return self.dummy + + def dummy(self,*args,**kw): + """Dummy function, which doesn't do anything but warn.""" + warn("IPython is not running, this is a dummy no-op function") + +_recent = IPythonNotRunning() def get(): - """ Get an IPApi object, or None if not running under ipython + """Get an IPApi object. - Running this should be the first thing you do when writing - extensions that can be imported as normal modules. You can then - direct all the configuration operations against the returned - object. + Returns an instance of IPythonNotRunning if not running under IPython. + Running this should be the first thing you do when writing extensions that + can be imported as normal modules. You can then direct all the + configuration operations against the returned object. """ return _recent - - class IPApi: """ The actual API class for configuring IPython - You should do all of the IPython configuration by getting - an IPApi object with IPython.ipapi.get() and using the provided - methods. + You should do all of the IPython configuration by getting an IPApi object + with IPython.ipapi.get() and using the attributes and methods of the + returned object.""" - """ def __init__(self,ip): + # All attributes exposed here are considered to be the public API of + # IPython. As needs dictate, some of these may be wrapped as + # properties. + self.magic = ip.ipmagic self.system = ip.ipsystem @@ -109,19 +137,34 @@ class IPApi: self.set_hook = ip.set_hook self.set_custom_exc = ip.set_custom_exc - + + self.user_ns = ip.user_ns + + # Session-specific data store, which can be used to store + # data that should persist through the ipython session. + self.meta = ip.meta + + # The ipython instance provided self.IP = ip + global _recent _recent = self - - - def options(self): - """ All configurable variables """ + # Use a property for some things which are added to the instance very + # late. I don't have time right now to disentangle the initialization + # order issues, so a property lets us delay item extraction while + # providing a normal attribute API. + def get_db(self): + """A handle to persistent dict-like database (a PickleShareDB object)""" + return self.IP.db + + db = property(get_db,None,None,get_db.__doc__) + + def get_options(self): + """All configurable variables.""" return self.IP.rc - - def user_ns(self): - return self.IP.user_ns + + options = property(get_options,None,None,get_options.__doc__) def expose_magic(self,magicname, func): ''' Expose own function as magic function for ipython @@ -138,31 +181,16 @@ class IPApi: im = new.instancemethod(func,self.IP, self.IP.__class__) setattr(self.IP, "magic_" + magicname, im) - def ex(self,cmd): """ Execute a normal python statement in user namespace """ - exec cmd in self.user_ns() + exec cmd in self.user_ns def ev(self,expr): """ Evaluate python expression expr in user namespace Returns the result of evaluation""" - return eval(expr,self.user_ns()) + return eval(expr,self.user_ns) - def meta(self): - """ Get a session-specific data store - - Object returned by this method can be used to store - data that should persist through the ipython session. - """ - return self.IP.meta - - def getdb(self): - """ Return a handle to persistent dict-like database - - Return a PickleShareDB object. - """ - return self.IP.db def runlines(self,lines): """ Run the specified lines in interpreter, honoring ipython directives. @@ -175,6 +203,96 @@ class IPApi: else: self.IP.runlines('\n'.join(lines)) + def to_user_ns(self,*vars): + """Inject a group of variables into the IPython user namespace. + + Inputs: + + - *vars: one or more variables from the caller's namespace to be put + into the interactive IPython namespace. The arguments can be given + in one of two forms, but ALL arguments must follow the same + convention (the first is checked and the rest are assumed to follow + it): + + a) All strings, naming variables in the caller. These names are + evaluated in the caller's frame and put in, with the same name, in + the IPython namespace. + + b) Pairs of (name, value), where the name is a string (a valid + python identifier). In this case, the value is put into the + IPython namespace labeled by the given name. This allows you to + rename your local variables so they don't collide with other names + you may already be using globally, or elsewhere and which you also + want to propagate. + + + This utility routine is meant to ease interactive debugging work, + where you want to easily propagate some internal variable in your code + up to the interactive namespace for further exploration. + + When you run code via %run, globals in your script become visible at + the interactive prompt, but this doesn't happen for locals inside your + own functions and methods. Yet when debugging, it is common to want + to explore some internal variables further at the interactive propmt. + + Examples: + + To use this, you first must obtain a handle on the ipython object as + indicated above, via: + + import IPython.ipapi + ip = IPython.ipapi.get() + + Once this is done, inside a routine foo() where you want to expose + variables x and y, you do the following: + + def foo(): + ... + x = your_computation() + y = something_else() + + # This pushes x and y to the interactive prompt immediately, even + # if this routine crashes on the next line after: + ip.to_user_ns('x','y') + ... + # return + + The following example shows you how to rename variables to avoid + clashes: + + def bar(): + ... + x,y,z,w = foo() + + # Push these variables with different names, so they don't + # overwrite x and y from before + ip.to_user_ns(('x1',x),('y1',y),('z1',z),('w1',w)) + # which is more conveniently written as: + ip.to_user_ns(*zip(('x1','y1','z1','w1'),(x,y,z,w))) + + ... + # return """ + + # print 'vars given:',vars # dbg + # Get the caller's frame to evaluate the given names in + cf = sys._getframe(1) + + # XXX fix this after Ville replies... + user_ns = self.user_ns + + if isinstance(vars[0],basestring): + # assume that all variables are given as strings + try: + for name in vars: + user_ns[name] = eval(name,cf.f_globals,cf.f_locals) + except: + error('could not get var. %s from %s' % + (name,cf.f_code.co_name)) + + else: + # assume they are all given as pairs of name,object + user_ns.update(dict(vars)) + def launch_new_instance(user_ns = None): """ Create and start a new ipython instance. @@ -200,4 +318,4 @@ def create_session(user_ns = None): if user_ns is not None: user_ns["__name__"] = user_ns.get("__name__",'ipy_session') import IPython - return IPython.Shell.start(user_ns = user_ns) \ No newline at end of file + return IPython.Shell.start(user_ns = user_ns) diff --git a/IPython/iplib.py b/IPython/iplib.py index 240c2bc..bfc1893 100644 --- a/IPython/iplib.py +++ b/IPython/iplib.py @@ -6,7 +6,7 @@ Requires Python 2.3 or newer. This file contains all the classes and helper functions specific to IPython. -$Id: iplib.py 1277 2006-05-02 10:31:55Z walter.doerwald $ +$Id: iplib.py 1314 2006-05-19 18:24:14Z fperez $ """ #***************************************************************************** @@ -28,8 +28,6 @@ $Id: iplib.py 1277 2006-05-02 10:31:55Z walter.doerwald $ #**************************************************************************** # Modules and globals -from __future__ import generators # for 2.2 backwards-compatibility - from IPython import Release __author__ = '%s <%s>\n%s <%s>' % \ ( Release.authors['Janko'] + Release.authors['Fernando'] ) @@ -196,10 +194,6 @@ class InteractiveShell(object,Magic): # log system self.logger = Logger(self,logfname='ipython_log.py',logmode='rotate') - # Produce a public API instance - - self.api = IPython.ipapi.IPApi(self) - # some minimal strict typechecks. For some core data structures, I # want actual basic python types, not just anything that looks like # one. This is especially true for namespaces. @@ -210,12 +204,6 @@ class InteractiveShell(object,Magic): # Job manager (for jobs run as background threads) self.jobs = BackgroundJobManager() - # track which builtins we add, so we can clean up later - self.builtins_added = {} - # This method will add the necessary builtins for operation, but - # tracking what it did via the builtins_added dict. - self.add_builtins() - # Do the intuitively correct thing for quit/exit: we remove the # builtins if they exist, and our own magics will deal with this try: @@ -599,6 +587,16 @@ class InteractiveShell(object,Magic): self.auto_alias = map(lambda s:s.split(None,1),auto_alias) # Call the actual (public) initializer self.init_auto_alias() + + # Produce a public API instance + self.api = IPython.ipapi.IPApi(self) + + # track which builtins we add, so we can clean up later + self.builtins_added = {} + # This method will add the necessary builtins for operation, but + # tracking what it did via the builtins_added dict. + self.add_builtins() + # end __init__ def pre_config_initialization(self): @@ -612,7 +610,6 @@ class InteractiveShell(object,Magic): rc = self.rc self.db = pickleshare.PickleShareDB(rc.ipythondir + "/db") - def post_config_initialization(self): """Post configuration init method