Coder Social home page Coder Social logo

Redefine magic methods about forbiddenfruit HOT 19 CLOSED

clarete avatar clarete commented on June 6, 2024
Redefine magic methods

from forbiddenfruit.

Comments (19)

tomyo avatar tomyo commented on June 6, 2024 3

I'll buy a beer to the first one fixing this.

from forbiddenfruit.

jachymb avatar jachymb commented on June 6, 2024 2

I was trying to redefine the __mul__ operator for functions so it's do function composition instead. I was trying with forbidden fruit, but it wouldn't work because of this error. But I came up with a different and satisfactory solution, in case someone needs inspiration, I put it here:

import types
__all__ = ['composible']

# This decorator makes function composible by '*' operator with other functions
def composible(f):
    class ComposibleFunction:
        def __init__(self, f):
            self.f = f
        def __call__(self, *a, **kw):
            return self.f(*a, **kw)
        def __mul__(self, g):
            return self.__class__(lambda *a, **kw: self.f(g(*a, **kw)))
    return ComposibleFunction(f)

# Make all builtin function composible:
for b in (b for b in dir(__builtins__)
            if type(getattr(__builtins__,b)) in (type, types.BuiltinMethodType)):
    exec('global '+b+'\n'+b + '=composible(__builtins__.' + b + ')')
    __all__.append(b)

# New functions need to be decorated
@composible 
def foo(x): return x+1

@composible 
def bar(x): return x*2

# This will output 7:
print((foo * bar * sum)([1, 2]))

from forbiddenfruit.

jonathaneunice avatar jonathaneunice commented on June 6, 2024

There seems to be some "not going through the method dictionary" issue here. Because:

>>> (2).__add__(3)
6

So the __add__ method is being switched out. But in expression form, it's not being used.

from forbiddenfruit.

clarete avatar clarete commented on June 6, 2024

That's fantastic. I was working on this exact feature RIGHT NOW. My guess is that the parser might be calling things differently for built-in types!

from forbiddenfruit.

fabiomcosta avatar fabiomcosta commented on June 6, 2024

wow!

from forbiddenfruit.

clarete avatar clarete commented on June 6, 2024

@jonathaneunice thank you so much for your bug report. I think I understood what's going on. Let me try to explain what's going on here, then we can try to find a solution to this problem! :)

When the python is processing some code and finds a sum operation like this:

var1 + var2

The system that evaluates the code (Python/ceval.c) will basically call the PyNumber_Add function, which is part of the python API that handles the object abstract protocols (the magic methods -- you find it in Objects/abstract.c). This function, unlike the regular getattr will check for things declared in the tp_as_number type slot before trying to access the instance dict (our dear __dict__ attribute).

So, basically, we need to find a way to patch the type data and replace the ob_type->tp_as_{number,sequence}[nb_add] method.

tl;dr: Basically, magic methods are resolved in a different way compared to the instance or class methods. There's a C array that contains a pointer to the actual implementation. Wich means that it's not actually present on the __slots__ dictionary.

I'm not sure if I'm 100% right, but it's my best shot so far. Thank you!

from forbiddenfruit.

jonathaneunice avatar jonathaneunice commented on June 6, 2024

@gabrielfalcao I assume he would not entirely approve. ;-)

As JavaScript and Ruby demonstrate, there's no fundamental reason why the base classes of a dynamic language cannot be extended. In JavaScript, libraries like underscore.js are very popular. But Python seems governed with a less exuberant desire for unapproved extensions, and more of a "monkey-patching considered harmful" view of the world.

from forbiddenfruit.

clarete avatar clarete commented on June 6, 2024

My efforts to kill this issue are happening here: https://github.com/clarete/forbiddenfruit/tree/fix/4

from forbiddenfruit.

clarete avatar clarete commented on June 6, 2024

Well, I was being kinda silly here. I completely forgot about the code generation. Python does not execute everything every time. For example, given the following python file:

1+1
2+3

The python compiler will generate the following code:

  1           0 LOAD_CONST               4 (2)
              3 POP_TOP

  9           4 LOAD_CONST               5 (5)
              7 POP_TOP
              8 LOAD_CONST               3 (None)
             11 RETURN_VALUE

Which means that no matter what, the integer sum operation will happen before my patch gets any chance to be applied.

from forbiddenfruit.

jonathaneunice avatar jonathaneunice commented on June 6, 2024

I'm not hugely disappointed that operations on primitive numeric types cannot be entirely intercepted. I expected that, optimizations being what they are. The ability to monkey-patch basic data classes such as dict, str, list, and set is still quite good magic.

from forbiddenfruit.

clarete avatar clarete commented on June 6, 2024

Also, @jonathaneunice we still can change all other operations that are not optimized. So I'll keep this bug open and I'll write more tests addressing other cases. Thank you and let me know if you have any other ideas!

from forbiddenfruit.

clarete avatar clarete commented on June 6, 2024

Working on getting the segfault fixed on Python 3.x

from forbiddenfruit.

rizo avatar rizo commented on June 6, 2024

Do we have any progress on this issue? What's the current status? I'd be glad to help with it.

from forbiddenfruit.

clarete avatar clarete commented on June 6, 2024

Hi @rizo, thanks for asking. That's the last thing I did: feae79a

If you're interested in helping, I can definitely provide you all the details you need to keep the train moving! :D

from forbiddenfruit.

jab avatar jab commented on June 6, 2024

Just found my way here while trying to override slice to use a sentinel object rather than None for the value of start, stop, and step when the user doesn't pass anything (background).

I tried running the following with forbiddenfruit revision feae79a and it didn't work:

from forbiddenfruit import curse

def simpletest(self):
    return 'works'

curse(slice, 'simpletest', simpletest)
assert slice(None).simpletest() == 'works'


def myslice__init__(self, *args, **kw):
    print('myslice__init__')  # never called
    self.start = self.stop = self.step = 42

curse(slice, '__init__', myslice__init__)
print(eval('slice(None).start'))  # doesn't work :(

class myslice:
    def __init__(self, *args, **kw):
        self.start = self.stop = self.step = 42

def myslice__new__(cls, *args, **kw):
    print('myslice__new__')  # never called
    inst = myslice()

curse(slice, '__new__', classmethod(myslice__new__))
print(eval('slice(None).start'))  # doesn't work :(

Is there something I'm missing, or is this expected to not work yet?

Thanks for reading, and thanks for releasing forbiddenfruit, it's super cool.

from forbiddenfruit.

voidworker avatar voidworker commented on June 6, 2024

I've found this issue when looking for ability to compose functions like f @ g @ h. While cursing object with redefined __matmul__ method doesn't work, there is still yet another satisfactory solution, inspired by @jachymb :

from forbiddenfruit import curse
def go(arg, *t):
	s = t[0](*arg)
	for f in t[1:]:
		s = f(s)
	return (s,) # Composability: everybody loves monoids!
curse(tuple,"go", go)

usage: (x,y,z).go(f, g, h).go(f1,f2) turns into f2(f1(h(g(f(x,y,z)))))

(3,4).\
go(lambda x,y: x*y
  ,lambda x: x**2).go(print)
144
(None,)

Of course, one can also define composable functions like (some_long_chain).then(f, additional_args_for_f). Iterables may also come into play...
Happy functional programming! :)

from forbiddenfruit.

clarete avatar clarete commented on June 6, 2024

@tomyo I guess you owe a beer to @alendit :)

from forbiddenfruit.

clarete avatar clarete commented on June 6, 2024

Closed by #24

from forbiddenfruit.

davips avatar davips commented on June 6, 2024

I was trying to redefine the __mul__ operator for functions so it's do function composition instead. I was trying with forbidden fruit, but it wouldn't work because of this error. But I came up with a different and satisfactory solution, in case someone needs inspiration, I put it here:

import types
__all__ = ['composible']

# This decorator makes function composible by '*' operator with other functions
def composible(f):
    class ComposibleFunction:
        def __init__(self, f):
            self.f = f
        def __call__(self, *a, **kw):
            return self.f(*a, **kw)
        def __mul__(self, g):
            return self.__class__(lambda *a, **kw: self.f(g(*a, **kw)))
    return ComposibleFunction(f)

# Make all builtin function composible:
for b in (b for b in dir(__builtins__)
            if type(getattr(__builtins__,b)) in (type, types.BuiltinMethodType)):
    exec('global '+b+'\n'+b + '=composible(__builtins__.' + b + ')')
    __all__.append(b)

# New functions need to be decorated
@composible 
def foo(x): return x+1

@composible 
def bar(x): return x*2

# This will output 7:
print((foo * bar * sum)([1, 2]))

Is there any lib implementing this wonderful?

from forbiddenfruit.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.