Mailing List Archive

How to replace an instance method?
I would like to replace a method of an instance, but don't know how to
do it properly.

My first naive idea was

inst = SomeClass()
def new_method(self, param):
# do something
return whatever
inst.method = new_method

however that doesn't work: self isn't passed as first parameter to
the new inst.method, instead inst.method behaves like a static method.

I had a closer look at the decorators classmethod and staticmethod.
Unfortunetely I couldn't find a decorator / function "instancemethod"
that turns a normal function into an instancemethod.

The classmethod documentation contains a reference to the standard
type hierarchie, and there is an explanation that an instancemethod
is sort of a dynamically created wrapper around a function, which
is accessable as __func__.
So I modified the last line of the example above to

inst.method.__func__ = new_method

but got told that __func__ is read only.

I found some information about methods in the Descriptor HowTo Guide,
but it's about how it works internally and doesn't tell how to solve
my problem (at least it doesn't tell me).

Now I'm running out of ideas what to try next or what sections of the
documentation to read next.

Any ideas / pointers?

Ralf M.
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On Sat, 17 Sept 2022 at 07:07, Ralf M. <Ralf_M@t-online.de> wrote:
>
> I would like to replace a method of an instance, but don't know how to
> do it properly.
>
> My first naive idea was
>
> inst = SomeClass()
> def new_method(self, param):
> # do something
> return whatever
> inst.method = new_method
>
> however that doesn't work: self isn't passed as first parameter to
> the new inst.method, instead inst.method behaves like a static method.
>
> I had a closer look at the decorators classmethod and staticmethod.
> Unfortunetely I couldn't find a decorator / function "instancemethod"
> that turns a normal function into an instancemethod.
>
> The classmethod documentation contains a reference to the standard
> type hierarchie, and there is an explanation that an instancemethod
> is sort of a dynamically created wrapper around a function, which
> is accessable as __func__.
> So I modified the last line of the example above to
>
> inst.method.__func__ = new_method
>
> but got told that __func__ is read only.
>
> I found some information about methods in the Descriptor HowTo Guide,
> but it's about how it works internally and doesn't tell how to solve
> my problem (at least it doesn't tell me).
>
> Now I'm running out of ideas what to try next or what sections of the
> documentation to read next.
>
> Any ideas / pointers?
>

You don't actually want a descriptor, since the purpose of descriptor
protocol is to give you information about the instance when the
attribute (the method, in this case) was attached to the class. In
this case, you can handle it with something far far simpler:

inst.method = functools.partial(new_method, inst)

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On 9/16/22, Ralf M. <Ralf_M@t-online.de> wrote:
> I would like to replace a method of an instance, but don't know how to
> do it properly.

A function is a descriptor that binds to any object as a method. For example:

>>> f = lambda self, x: self + x
>>> o = 42
>>> m = f.__get__(o)
>>> type(m)
<class 'method'>
>>> m.__self__ is o
True
>>> m(10)
52
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On Fri, Sep 16, 2022 at 2:06 PM Ralf M. <Ralf_M@t-online.de> wrote:

> I would like to replace a method of an instance, but don't know how to
> do it properly.
>

You appear to have a good answer, but... are you sure this is a good idea?

It'll probably be confusing to future maintainers of this code, and I doubt
static analyzers will like it either.

I'm not the biggest fan of inheritance you'll ever meet, but maybe this is
a good place for it?
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
Here is an example of probably the easiest way to add an instance method:

class Demo:
def sqr(self, x):
return x*x

# Function to turn into a instance method
def cube(self, x):
return x*x*x

d = Demo()
print(d.sqr(2))

d.cube = cube.__get__(d)
print(d.cube(3))

As for when someone might want to do this kind of thing, one place is
when you are running scripts in an existing framework, and you can't
change the class definition but you can access an instance. Or you
might want to add the method to an existing class to debug and tune it
up before you go through the formalities of actually changing an
existing project.

I once added a class to an running text editor so that it would
highlight the current line. That was adding a class instead of a method,
but the need was basically the same.

On 9/16/2022 6:35 PM, Dan Stromberg wrote:
> On Fri, Sep 16, 2022 at 2:06 PM Ralf M. <Ralf_M@t-online.de> wrote:
>
>> I would like to replace a method of an instance, but don't know how to
>> do it properly.
>>
>
> You appear to have a good answer, but... are you sure this is a good idea?
>
> It'll probably be confusing to future maintainers of this code, and I doubt
> static analyzers will like it either.
>
> I'm not the biggest fan of inheritance you'll ever meet, but maybe this is
> a good place for it?

--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
Am 16.09.2022 um 23:34 schrieb Eryk Sun:
> On 9/16/22, Ralf M. <Ralf_M@t-online.de> wrote:
>> I would like to replace a method of an instance, but don't know how to
>> do it properly.
>
> A function is a descriptor that binds to any object as a method. For example:
>
> >>> f = lambda self, x: self + x
> >>> o = 42
> >>> m = f.__get__(o)
> >>> type(m)
> <class 'method'>
> >>> m.__self__ is o
> True
> >>> m(10)
> 52

Thank you and Chris A. for the two suggestions how to replace a method.

I tried both
inst.method = functools.partial(new_method, inst)
and
inst.method = new_method.__get__(inst)
and both work in my toy example.
I will try it on the real code next week.

Even though the functools.partial solution is easier to understand (at
least for me), I will probably use the __get__ solution as it avoids
the import of an extra library.

Ralf M.
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
Am 17.09.2022 um 00:35 schrieb Dan Stromberg:
>
>
> On Fri, Sep 16, 2022 at 2:06 PM Ralf M. <Ralf_M@t-online.de
> <mailto:Ralf_M@t-online.de>> wrote:
>
> I would like to replace a method of an instance, but don't know how to
> do it properly.
>
>
> You appear to have a good answer, but...  are you sure this is a good idea?

It's definitely a dirty hack.

> It'll probably be confusing to future maintainers of this code, and I
> doubt static analyzers will like it either.

I agree that I will have to add sufficient comments for the future
maintainer, should there ever be one (and even for me to still
understand it next year). I don't use static analyzers.

> I'm not the biggest fan of inheritance you'll ever meet, but maybe this
> is a good place for it?

Using a derived version of the class in question to overwrite the
method was my first idea, however I don't instantiate the class in
question myself, it is instantiated during the initialisation of
another class, so I would at least have to derive a modified version of
that as well. And that code is rather complex, with metaclasses and
custom decorators, and I feel uncomfortable messing with that, while
the method I intend to change is quite simple and straightforward.

In case anybody is interested what I'm trying to achieve:

It's simple in pandas to read an excel file into a dataframe, but only
the cell value is read. Sometimes I need more / other information, e.g.
some formatting or the hyperlink in a cell. Reopening the file with
openpyxl and getting the info is possible, but cumbersome.
Looking into the pandas code for reading excel files (which uses
openpyxl internally) I noticed a method (of an internal pandas class)
that extracts the value from an openpyxl cell. This method is rather
simple and seems the ideal spot to change to get what I want.

My idea is to instantiate pandas.ExcelFile (official pandas API), get
the reader instance (an attribute of the ExcelFile object) and modify
the method of the reader instance.

The fact that the method I change and the ExcelFile attribute containing
the reader are both private (start with _) doesn't make it any better,
but I'm desperate enough to be willing to adapt my code to every major
pandas release, if necessary.

Ralf M.
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On Sun, 18 Sept 2022 at 07:20, Ralf M. <Ralf_M@t-online.de> wrote:
>
> Am 16.09.2022 um 23:34 schrieb Eryk Sun:
> > On 9/16/22, Ralf M. <Ralf_M@t-online.de> wrote:
> >> I would like to replace a method of an instance, but don't know how to
> >> do it properly.
> >
> > A function is a descriptor that binds to any object as a method. For example:
> >
> > >>> f = lambda self, x: self + x
> > >>> o = 42
> > >>> m = f.__get__(o)
> > >>> type(m)
> > <class 'method'>
> > >>> m.__self__ is o
> > True
> > >>> m(10)
> > 52
>
> Thank you and Chris A. for the two suggestions how to replace a method.
>
> I tried both
> inst.method = functools.partial(new_method, inst)
> and
> inst.method = new_method.__get__(inst)
> and both work in my toy example.
> I will try it on the real code next week.
>
> Even though the functools.partial solution is easier to understand (at
> least for me), I will probably use the __get__ solution as it avoids
> the import of an extra library.
>

The two are basically equivalent. Using functools.partial emphasizes
the fact that all you're doing is "locking in" the first parameter;
using the __get__ method emphasizes the fact that functions are,
fundamentally, the same thing as methods. Choose whichever one makes
sense to you!

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On 9/17/22, Chris Angelico <rosuav@gmail.com> wrote:
>
> The two are basically equivalent. Using functools.partial emphasizes
> the fact that all you're doing is "locking in" the first parameter;
> using the __get__ method emphasizes the fact that functions are,
> fundamentally, the same thing as methods. Choose whichever one makes
> sense to you!

Functions are really not "fundamentally, the same thing as methods".
They're only the same in that they're both callable. Also, a method's
__getattribute__() falls back on looking up attributes on the
underlying function (i.e. the method's __func__), such as inspecting
the __name__ and __code__. A fundamental difference is that, unlike a
function, a method is not a descriptor. Thus if a method object is set
as an attribute of a type, the method does not rebind as a new method
when accessed as an attribute of an instance of the type.
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On Sun, 18 Sept 2022 at 09:37, Eryk Sun <eryksun@gmail.com> wrote:
>
> On 9/17/22, Chris Angelico <rosuav@gmail.com> wrote:
> >
> > The two are basically equivalent. Using functools.partial emphasizes
> > the fact that all you're doing is "locking in" the first parameter;
> > using the __get__ method emphasizes the fact that functions are,
> > fundamentally, the same thing as methods. Choose whichever one makes
> > sense to you!
>
> Functions are really not "fundamentally, the same thing as methods".
> They're only the same in that they're both callable. Also, a method's
> __getattribute__() falls back on looking up attributes on the
> underlying function (i.e. the method's __func__), such as inspecting
> the __name__ and __code__. A fundamental difference is that, unlike a
> function, a method is not a descriptor. Thus if a method object is set
> as an attribute of a type, the method does not rebind as a new method
> when accessed as an attribute of an instance of the type.

An unbound method in Python 2 was distinctly different from a
function, but in Python 3, they really truly are the same thing. A
bound method object is a small wrapper around a function which binds
its 'self' parameter; that's a distinction, but not a fundamental one.
Yes, a bound method isn't a descriptor; that's not really a huge
difference either, though.

A method IS a function. A bound method is a function with one argument
locked in, but still a function.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
RE: How to replace an instance method? [ In reply to ]
From your description, Chris, it sounds like the functional programming
technique often called currying. A factory function is created where one (or
more) parameters are sort of frozen in so the user never sees or cares about
them, and a modified or wrapped function is returned. In this case, the
function now knows what object it is attached to as this.

-----Original Message-----
From: Python-list <python-list-bounces+avi.e.gross=gmail.com@python.org> On
Behalf Of Chris Angelico
Sent: Saturday, September 17, 2022 8:21 PM
To: python-list@python.org
Subject: Re: How to replace an instance method?

On Sun, 18 Sept 2022 at 09:37, Eryk Sun <eryksun@gmail.com> wrote:
>
> On 9/17/22, Chris Angelico <rosuav@gmail.com> wrote:
> >
> > The two are basically equivalent. Using functools.partial emphasizes
> > the fact that all you're doing is "locking in" the first parameter;
> > using the __get__ method emphasizes the fact that functions are,
> > fundamentally, the same thing as methods. Choose whichever one makes
> > sense to you!
>
> Functions are really not "fundamentally, the same thing as methods".
> They're only the same in that they're both callable. Also, a method's
> __getattribute__() falls back on looking up attributes on the
> underlying function (i.e. the method's __func__), such as inspecting
> the __name__ and __code__. A fundamental difference is that, unlike a
> function, a method is not a descriptor. Thus if a method object is set
> as an attribute of a type, the method does not rebind as a new method
> when accessed as an attribute of an instance of the type.

An unbound method in Python 2 was distinctly different from a function, but
in Python 3, they really truly are the same thing. A bound method object is
a small wrapper around a function which binds its 'self' parameter; that's a
distinction, but not a fundamental one.
Yes, a bound method isn't a descriptor; that's not really a huge difference
either, though.

A method IS a function. A bound method is a function with one argument
locked in, but still a function.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list

--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On Sun, 18 Sept 2022 at 10:29, <avi.e.gross@gmail.com> wrote:
>
>
> From your description, Chris, it sounds like the functional programming
> technique often called currying. A factory function is created where one (or
> more) parameters are sort of frozen in so the user never sees or cares about
> them, and a modified or wrapped function is returned. In this case, the
> function now knows what object it is attached to as this.
>

Correct. I avoided the technical term but that is exactly what's happening.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On 9/17/22, Chris Angelico <rosuav@gmail.com> wrote:
>
> A method IS a function. A bound method is a function with one argument
> locked in, but still a function.

We were talking past each other. A method object is not a function
object. You're talking about a function defined in a class that's
accessed as a method of an instance of the class. In the class, that's
just a function object; it's exactly a function, not merely
fundamentally the same thing as one. It's only when accessed as an
attribute of an instance of the type that the function's descriptor
__get__() method is called to bind it and the instance to a method
object that sets the instance as its __self__ and the function as its
__func__.
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
Hey,

What has not been mentioned yet is simple delegation.

Often you want to rewrite a method, maybe have different (more or less) parameters and additionally keep the old methods for backwards compatibility. Or mark it as deprecated at a later point. So you could write the new method and change the old method to call the new method but with the parameters the new method expects. If you explain this in the docstrings as well. Then you do not need to actually replace the method.

Or you had a completely different use-case in mind, that I missed.

Cheers

Lars


Lars Liedtke
Software Entwickler

[Tel.]
[Fax] +49 721 98993-
[E-Mail] lal@solute.de<mailto:lal@solute.de>


solute GmbH
Zeppelinstraße 15
76185 Karlsruhe
Germany


[Logo Solute]


Marken der solute GmbH | brands of solute GmbH
[Marken]
[Advertising Partner]

Geschäftsführer | Managing Director: Dr. Thilo Gans, Bernd Vermaaten
Webseite | www.solute.de <http://www.solute.de/>
Sitz | Registered Office: Karlsruhe
Registergericht | Register Court: Amtsgericht Mannheim
Registernummer | Register No.: HRB 110579
USt-ID | VAT ID: DE234663798



Informationen zum Datenschutz | Information about privacy policy
https://www.solute.de/ger/datenschutz/grundsaetze-der-datenverarbeitung.php




Am 16.09.22 um 22:55 schrieb Ralf M.:
I would like to replace a method of an instance, but don't know how to do it properly.

My first naive idea was

inst = SomeClass()
def new_method(self, param):
# do something
return whatever
inst.method = new_method

however that doesn't work: self isn't passed as first parameter to
the new inst.method, instead inst.method behaves like a static method.

I had a closer look at the decorators classmethod and staticmethod.
Unfortunetely I couldn't find a decorator / function "instancemethod"
that turns a normal function into an instancemethod.

The classmethod documentation contains a reference to the standard
type hierarchie, and there is an explanation that an instancemethod
is sort of a dynamically created wrapper around a function, which
is accessable as __func__.
So I modified the last line of the example above to

inst.method.__func__ = new_method

but got told that __func__ is read only.

I found some information about methods in the Descriptor HowTo Guide,
but it's about how it works internally and doesn't tell how to solve
my problem (at least it doesn't tell me).

Now I'm running out of ideas what to try next or what sections of the
documentation to read next.

Any ideas / pointers?

Ralf M.
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
Just subclass and override whatever method you wish to modify
“Private” is conceptual. Mostly it means when the next version of a module comes out, code that you wrote that accesses *._ parts of the module might break.
___


import pandas


class MyClass(pandas.ExcelFile.OpenpyxlReader):

def _convert_cell(self, cell, convert_float: bool) -> 'Scalar':
"""override"""
# do whatever you want, or call the base class version
return super()._convert_cell(cell, convert_float)


Gerard Weatherby | Application Architect NMRbox | NAN | Department of Molecular Biology and Biophysics
UConn Health 263 Farmington Avenue, Farmington, CT 06030-6406 uchc.edu
On Sep 17, 2022, 5:29 PM -0400, Ralf M. <Ralf_M@t-online.de>, wrote:
*** Attention: This is an external email. Use caution responding, opening attachments or clicking on links. ***

Am 17.09.2022 um 00:35 schrieb Dan Stromberg:


On Fri, Sep 16, 2022 at 2:06 PM Ralf M. <Ralf_M@t-online.de
<mailto:Ralf_M@t-online.de>> wrote:

I would like to replace a method of an instance, but don't know how to
do it properly.


You appear to have a good answer, but... are you sure this is a good idea?

It's definitely a dirty hack.

It'll probably be confusing to future maintainers of this code, and I
doubt static analyzers will like it either.

I agree that I will have to add sufficient comments for the future
maintainer, should there ever be one (and even for me to still
understand it next year). I don't use static analyzers.

I'm not the biggest fan of inheritance you'll ever meet, but maybe this
is a good place for it?

Using a derived version of the class in question to overwrite the
method was my first idea, however I don't instantiate the class in
question myself, it is instantiated during the initialisation of
another class, so I would at least have to derive a modified version of
that as well. And that code is rather complex, with metaclasses and
custom decorators, and I feel uncomfortable messing with that, while
the method I intend to change is quite simple and straightforward.

In case anybody is interested what I'm trying to achieve:

It's simple in pandas to read an excel file into a dataframe, but only
the cell value is read. Sometimes I need more / other information, e.g.
some formatting or the hyperlink in a cell. Reopening the file with
openpyxl and getting the info is possible, but cumbersome.
Looking into the pandas code for reading excel files (which uses
openpyxl internally) I noticed a method (of an internal pandas class)
that extracts the value from an openpyxl cell. This method is rather
simple and seems the ideal spot to change to get what I want.

My idea is to instantiate pandas.ExcelFile (official pandas API), get
the reader instance (an attribute of the ExcelFile object) and modify
the method of the reader instance.

The fact that the method I change and the ExcelFile attribute containing
the reader are both private (start with _) doesn't make it any better,
but I'm desperate enough to be willing to adapt my code to every major
pandas release, if necessary.

Ralf M.
--
https://urldefense.com/v3/__https://mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!mYWFkAugwhU4HgCv9nRg1vSJhyJCA8RApcnyGTRNGQYTTmvVigqANAagTbBwo96YFdHmzfCYU8gN3KpVmcrmOg$
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On 2022-09-18 at 09:11:28 +0000,
Stefan Ram <ram@zedat.fu-berlin.de> wrote:

> ram@zedat.fu-berlin.de (Stefan Ram) writes (abbreviated):
> >types.MethodType( function, instance )
> >functools.partial( function, instance )
> >new_method.__get__( instance )
>
> I wonder which of these three possibilities expresses
> the idea of creating a new method from a function and
> an instance most clearly.

The first one. And only the first one.

The second one requires too much inside knowledge of Python to make the
leap from currying to instance method.

The third one doesn't even mention the function. Also, in Python, if
I'm applying dunder methods directly (it's okay to write them), then I'm
doing something wrong.
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On 9/18/22, Stefan Ram <ram@zedat.fu-berlin.de> wrote:
> ram@zedat.fu-berlin.de (Stefan Ram) writes (abbreviated):
>>types.MethodType( function, instance )
>>functools.partial( function, instance )
>>new_method.__get__( instance )
>
> I wonder which of these three possibilities expresses
> the idea of creating a new method from a function and
> an instance most clearly.

Using types.MethodType(function, instance) is the most clear and
correct of the three. Using the function's descriptor __get__() method
is equivalent in terms of the result. That said, the descriptor
protocol is an intermediate-level concept, so manually calling
__get__() isn't friendly to novices or casual users of the language.

Using a functools.partial isn't the expected method type, with
__func__ and __self__ attributes, and, unlike a method, it doesn't
expose the wrapped function's __code__, __name__, __module__, __doc__,
__annotations__, __defaults__, __kwdefaults__, __closure__,
__globals__, or __builtins__. If dynamic inspection matters, using a
functools.partial won't work directly with dis.dis(),
inspect.getfile(), inspect.getsource(), inspect.getdoc(),
inspect.get_annotations(), inspect.getcallargs(), or
inspect.getclosurevars().
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
On 9/19/22, 2QdxY4RzWzUUiLuE@potatochowder.com
<2QdxY4RzWzUUiLuE@potatochowder.com> wrote:
> On 2022-09-18 at 09:11:28 +0000,
> Stefan Ram <ram@zedat.fu-berlin.de> wrote:
>
>> ram@zedat.fu-berlin.de (Stefan Ram) writes (abbreviated):
>> >types.MethodType( function, instance )
>> >functools.partial( function, instance )
>> >new_method.__get__( instance )
>>
>> I wonder which of these three possibilities expresses
>> the idea of creating a new method from a function and
>> an instance most clearly.
>
> The first one. And only the first one.
>
> The second one requires too much inside knowledge of Python to make the
> leap from currying to instance method.
>
> The third one doesn't even mention the function.

The OP's example named the function "new_method". In general the third
case would be func.__get__(instance). It's how the interpreter binds a
new method when a function from the class hierarchy is accessed as an
instance attribute.

When a function from the class hierarchy is accessed as an attribute
of the class, it's equivalent to calling func.__get__(None, cls),
which just returns a reference to the function. To use the descriptor
protocol to bind a function as a method of the class requires wrapping
it with the classmethod descriptor type. For example,
classmethod(func).__get__(None, cls) returns a method object with
__self__ that references the cls type. Of course, calling
types.MethodType(func, cls) is easier to understand and the preferred
way.
--
https://mail.python.org/mailman/listinfo/python-list
Re: How to replace an instance method? [ In reply to ]
Another possibility would be to use a lambda function or a callable
object. This adds an overhead but would also allow you to inject new
parameters that go into the function call. It also does not require
any extra import.

obj.old_method_name = lambda *a, **kw: new_method_name(obj, *a, **kw)

A full example goes like this:

class C:

def __init__(self):
self.value = 21

def get(self):
return self.value

def new_get(self):
return self.value * 2

obj = C()
print(obj.get())
obj.get = lambda *a, **kw: new_get(obj, *a, **kw)
print(obj.get())

This would first output 21 and then 42.

--

What you are trying to do requires more than just replacing the
function _convert_cell. By default, OpenpyxlReader loads the workbook
in read_only mode, discarding all links. This means that the cell
object present in _convert_cell has no hyperlink attribute. There is
no option to make it load the links. To force it to be loaded, we need
to replace load_workbook as well. This method asks openpyxl to load
the workbook, deciding whether it will discard the links or not.

The second problem is that as soon as you instantiate an ExcelFile
object it will instantiate an OpenpyxlReader and load the file.
Leaving you with no time to replace the functions. Happily, ExcelFile
gets the engine class from a static dictionary called _engines. This
means that we can extend OpenpyxlReader, overwrite those two methods
and replace the reference in ExcelFile._engines. The full source is:

import pandas as pd

class MyOpenpyxlReader(pd.ExcelFile.OpenpyxlReader):

def load_workbook(self, filepath_or_buffer):
from openpyxl import load_workbook
return load_workbook(
filepath_or_buffer,
read_only=False,
data_only=False,
keep_links=True
)

def _convert_cell(self, cell, convert_float: bool):
value = super()._convert_cell(cell, convert_float)
if cell.hyperlink is None:
return value
else:
return (value, cell.hyperlink.target)


pd.ExcelFile._engines["openpyxl"] = MyOpenpyxlReader
df = pd.read_excel("links.xlsx")
print(df)

The source above worked on python 3.8.10, pandas 1.5.0, and openpyxl
3.0.10. The output for a sample xlsx file with the columns id, a page
name (with links), and the last access is shown next. The first
element in the second column's output tuple is the cell's text and the
second element is the cell's link:

id
page last access
0 1 (google, https://www.google.com/) 2022-04-12
1 2 (gmail, https://gmail.com/) 2022-02-06
2 3 (maps, https://www.google.com/maps) 2022-02-17
3 4 (bbc, https://bbc.co.uk/) 2022-08-30
4 5 (reddit, https://www.reddit.com/) 2022-12-02
5 6 (stackoverflow, https://stackoverflow.com/) 2022-05-25

--

Should you do any of this? No.

1. What makes a good developer is his ability to create clear and
maintainable code. Any of these options are clearly not clear,
increase cognitive complexity, and reduce reliability.
2. We are manipulating internal class attributes and internal methods
(those starting with _). Internal elements are not guaranteed to stay
there over different versions, even minor updates. You should not
manipulate them unless you are working on a fixed library version,
like implementing tests and checking if the internal state has
changed, hacking it, or debugging. Python assumes you will access
these attributes wisely.
3. If you are working with other developers and you commit this code
there is a huge chance another developer is using a slightly different
pandas version that misses one of these elements. You will break the
build, your team will complain and start thinking you are a naive
developer.
4. Even if you adapt your code for multiple pandas versions you will
end up with multiple ifs and different implementations. You don't want
to maintain this over time.
5. It clearly takes more time to understand pandas' internals than
writing your reader using openpyxl. It is not cumbersome, and if it
changes the execution time from 20ms to 40ms but is much more reliable
and maintainable we surely prefer the latter.

The only scenario I see in which this would be acceptable is when you
or your boss have an important presentation in the next hour, and you
need a quick fix to make it work in order to demonstrate it. After the
presentation is over and people have validated the functionality you
should properly implement it.

Keep It Simple and Stupid (KISS)

--
Diego Souza
Wespa Intelligent Systems
Rio de Janeiro - Brasil

On Mon, Sep 19, 2022 at 1:00 PM <python-list-request@python.org> wrote:
>
>
> From: "Weatherby,Gerard" <gweatherby@uchc.edu>
> Date: Mon, 19 Sep 2022 13:06:42 +0000
> Subject: Re: How to replace an instance method?
> Just subclass and override whatever method you wish to modify
> “Private” is conceptual. Mostly it means when the next version of a module comes out, code that you wrote that accesses *._ parts of the module might break.
> ___
>
>
> import pandas
>
>
> class MyClass(pandas.ExcelFile.OpenpyxlReader):
>
> def _convert_cell(self, cell, convert_float: bool) -> 'Scalar':
> """override"""
> # do whatever you want, or call the base class version
> return super()._convert_cell(cell, convert_float)
>
> —
> Gerard Weatherby | Application Architect NMRbox | NAN | Department of Molecular Biology and Biophysics
> UConn Health 263 Farmington Avenue, Farmington, CT 06030-6406 uchc.edu
> On Sep 17, 2022, 5:29 PM -0400, Ralf M. <Ralf_M@t-online.de>, wrote:
> *** Attention: This is an external email. Use caution responding, opening attachments or clicking on links. ***
>
> Am 17.09.2022 um 00:35 schrieb Dan Stromberg:
>
>
> On Fri, Sep 16, 2022 at 2:06 PM Ralf M. <Ralf_M@t-online.de
> <mailto:Ralf_M@t-online.de>> wrote:
>
> I would like to replace a method of an instance, but don't know how to
> do it properly.
>
>
> You appear to have a good answer, but... are you sure this is a good idea?
>
> It's definitely a dirty hack.
>
> It'll probably be confusing to future maintainers of this code, and I
> doubt static analyzers will like it either.
>
> I agree that I will have to add sufficient comments for the future
> maintainer, should there ever be one (and even for me to still
> understand it next year). I don't use static analyzers.
>
> I'm not the biggest fan of inheritance you'll ever meet, but maybe this
> is a good place for it?
>
> Using a derived version of the class in question to overwrite the
> method was my first idea, however I don't instantiate the class in
> question myself, it is instantiated during the initialisation of
> another class, so I would at least have to derive a modified version of
> that as well. And that code is rather complex, with metaclasses and
> custom decorators, and I feel uncomfortable messing with that, while
> the method I intend to change is quite simple and straightforward.
>
> In case anybody is interested what I'm trying to achieve:
>
> It's simple in pandas to read an excel file into a dataframe, but only
> the cell value is read. Sometimes I need more / other information, e.g.
> some formatting or the hyperlink in a cell. Reopening the file with
> openpyxl and getting the info is possible, but cumbersome.
> Looking into the pandas code for reading excel files (which uses
> openpyxl internally) I noticed a method (of an internal pandas class)
> that extracts the value from an openpyxl cell. This method is rather
> simple and seems the ideal spot to change to get what I want.
>
> My idea is to instantiate pandas.ExcelFile (official pandas API), get
> the reader instance (an attribute of the ExcelFile object) and modify
> the method of the reader instance.
>
> The fact that the method I change and the ExcelFile attribute containing
> the reader are both private (start with _) doesn't make it any better,
> but I'm desperate enough to be willing to adapt my code to every major
> pandas release, if necessary.
>
> Ralf M.
> --
> https://urldefense.com/v3/__https://mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!mYWFkAugwhU4HgCv9nRg1vSJhyJCA8RApcnyGTRNGQYTTmvVigqANAagTbBwo96YFdHmzfCYU8gN3KpVmcrmOg$
--
https://mail.python.org/mailman/listinfo/python-list