Elixir - Define functions in module by metaprogramming - metaprogramming

I have three identical methods in my module, that do (almost) exactly the same thing. Instead of repeating the function definitions, I am trying to define them once to keep code minimal and same for all of them.
So far I've tried using Code.eval_string:
defmodule MyModule do
Enum.each ~w(method1 method2 method3), fn method ->
#method method
Code.eval_string """
def #{#method}(map) when is_map(map) do
do_map_stuff(:#{#method}, map)
end
def #{#method}(arg) do
do_other_stuff(:#{#method}, arg)
end
"""
end
## Other Methods
end
but this throws ArgumentError:
Compiling 1 file (.ex)
** (ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:4297: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3299: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
nofile:1: (file)
I think quote/unquote might be the way to go, but I'm not exactly sure how to do this using them (I've already read the Meta Guide on the Elixir website).

Something like this?
defmodule MyModule do
def do_map_stuff(method, arg) do
IO.inspect([method, arg])
end
Enum.each [:method1, :method2, :method3], fn method ->
def unquote(method)(map) when is_map(map) do
do_map_stuff(unquote(method), map)
end
def unquote(method)(arg) do
do_map_stuff(unquote(method), arg)
end
end
end

Related

Usage of __setattr__ to rewrite whole method of library class issue: missing 1 required positional argument: 'self'

I've got some imported packages with tricky structure
and need to call some method that bases on lots of other methods
with non-default parameters, which are not class attributes themself like pipeline in sklearn.
Minimal example of this module structure:
class Library_class:
def __init__(
self,
defined_class_options,
):
self.defined_class_options = defined_class_options
def method1( self , default_non_class_arg = 12 ):
assert self.defined_class_options==3
return default_non_class_arg
def method2( self, image ):
return image/ self.method1()
Default usage:
class_instance = Library_class( 3 )
class_instance.method2( 36 )
> 3.0
I need to set default_non_class_arg to 6 for example.
I've tried multiple approaches:
Analogous to https://stackoverflow.com/a/35634198/7607734
class_instance.method2( 36 ,
method1__default_non_class_arg=3 )
TypeError: method2() got an unexpected keyword argument 'method1__default_non_class_arg'
It don't work probably because class definitely don't have set_params
With setattr on redefined function
class_instance.__setattr__('method1',Library_class.new_method1)
class_instance.method2( 36 )
TypeError: new_method1() missing 1 required positional argument: 'self'
Both your snippets and question are quite messy, almost to the point of being unreadable.
Anyway, if you wantt to replace method1 with another function, say new_method1 in an specific instance, just do that. Your call to .__setattr__ does that, but it is not needed at all, (and if it was, due to you not having the method to be replaced name at code writting time, and needed it as a parameter, it is more correct to call the built-in setattr, not the instance method: `setattr(class_instance, "method1", new_method1").
Ordinarily, if you know, at code writting time you have to replace "method1" in an instance, the assigment operator will do it:
class_instance.method1 = new_method1
What went wrong in your examle is that if you assign a method to an instance, instead of a class, you are bypassing the mechanism that Python uses to insert the self attribute into it - so your new_method1 needs a different signature. (and this is exactly what the error message "TypeError: new_method1() missing 1 required positional argument: 'self'" is saying):
class MyClass:
...
def method1(self, param1=36):
...
...
def new_method1(param1=6): # <-- written outside of any class body, sans self
...
my_instance = MyClass()
my_instance.method1 = new_method1
this will work.
new_method1 could be written in a class body as well, and could be replaced just the same, but you would have to write it without the self parameter the same, and then it would not work straight as a normal method.
OR, you can, at assigment time, insert the self argument yourself - the functools.partial call is a convenient way to do that:
class MyClass:
...
def method1(self, param1=36):
...
def new_method1(self, param1=6):
...
...
my_instance = MyClass()
from functools import partial
MyClass.method1 = partial(MyClass.new_method1, my_instance)
Now, this should answer what you are asking, but it would not be honest of me to end the answer without saying this is not a good design. The best thing there is to pull your parameter from another place, it might be from an instance attribute, instead of replacing the method entirely just to change it.
Since for normal attributes, Python will read the class attribute if no instance attribute exists, it will happen naturally, and all you have to do is to set the new default value in your instance.
class MyClass:
default_param_1 = 36 # Class attribute. Valid for every instance unless overriden
...
def method1(self, param1=None):
if param1 is None:
param1 = self.default_param_1 #Automatically fetched from the class if not set on the instance
...
...
my_instance = MyClass()
my_instance.default_param_1 = 6
...

MyPy type annotation for a bound method?

I'm writing some tests for a library using pytest. I want to try a number of test cases for each function exposed by the library, so I've found it convenient to group the tests for each method in a class. All of the functions I want to test have the same signature and return similar results, so I'd like to use a helper method defined in a superclass to do some assertions on the results. A simplified version would run like so:
class MyTestCase:
function_under_test: Optional[Callable[[str], Any]] = None
def assert_something(self, input_str: str, expected_result: Any) -> None:
if self.function_under_test is None:
raise AssertionError(
"To use this helper method, you must set the function_under_test"
"class variable within your test class to the function to be called.")
result = self.function_under_test.__func__(input_str)
assert result == expected_result
# various other assertions on result...
class FunctionATest(MyTestCase):
function_under_test = mymodule.myfunction
def test_whatever(self):
self.assert_something("foo bar baz")
In assert_something, It's necessary to call __func__() on the function since assigning a function to a class attribute makes it a bound method of that class -- otherwise self will be passed through as the first argument to the external library function, where it doesn't make any sense.
This code works as intended. However, it yields the MyPy error:
"Callable[[str], Any]" has no attribute "__func__"
Based on my annotation, it's correct that this isn't a safe operation: an arbitrary Callable may not have a __func__ attribute. However, I can't find any type annotation that would indicate that the function_under_test variable refers to a method and thus will always have __func__. Am I overlooking one, or is there another way to tweak my annotations or accesses to get this working with type-checking?
Certainly, there are plenty of other ways I could get around this, some of which might even be cleaner (use an Any type, skip type checking, use a private method to return the function under test rather than making it a class variable, make the helper method a function, etc.). I'm more interested in whether there's an annotation or other mypy trick that would get this code working.
Callable only makes sure that your object has the __call__ method.
You problem is your call self.function_under_test.__func__(input_str) you should just call your function self.function_under_test(input_str)
See below your example without mypy complaints (v0.910)
from typing import Any, Callable, Optional
class MyTestCase:
function_under_test: Optional[Callable] = None
def myfunction_wrap(self, *args, **kwargs):
raise NotImplementedError
def assert_something(self, input_str: str, expected_result: Any) -> None:
if self.function_under_test is None:
raise AssertionError(
"To use this helper method, you must set the function_under_test"
"class variable within your test class to the function to be called.")
result = self.myfunction_wrap(input_str)
assert result == expected_result
# various other assertions on result...
def myfunction(a: str) -> None:
...
class FunctionATest(MyTestCase):
def myfunction_wrap(self, *args, **kwargs):
myfunction(*args, **kwargs)
def test_whatever(self):
self.assert_something("foo bar baz")
Edit1: missed the point of the questio, moved function inside a wrapper function

Python script is returning function not defined error

I am writing a python class and trying to call a function from another function in the class but I am running into an error.NameError: name 'bobfunction' is not defined. My call to the class works, even the call to method/function job works. When job tries to call bobfunction I get the error message. removing the call to bobfunction works. So how do I call the bobfunction from the job function?
class stuff():
def __init__(self):
#setup stuff
def bobfunction(self,junk):
print("should work")
return ''
def job(self,data):
bobfunction('test data')
return 'other junk'
Run it with self.bobfunction("test data")
Python uses the keyword self to refer to class methods and variables of the same class. It is similar to this keyword in other languages. (Not the same though) . If you end up defining a variable in your __init__ function , you can also use it with self.variable_name in other functions.
You should try to run it with stuff.bobfunction(self,"test data")
Because bobfunction was situated in class stuff, you need to write class' name before function's name.
Self in parentheses must send the name of self.

In python3, how can one reference a function from within the scope of that function without knowing the name of the function?

Say I have a function:
def foo():
"""foo docstring"""
print(foo.__doc__)
How can I write this, without using the function name foo within the print function?
Please note I've read through this similar question, which allows access to the name of a function as a string, but as far as I can see does not give access to the function itself.
The answer was much simpler than I expected. To access the function itself instead of just the name, apply eval() to the name.
import inspect
def foo():
"""zxy"""
print(fself().__doc__)
def fself():
"""returns parent function when called"""
return eval(inspect.stack()[1][3])
def bar():
"""abcdefg"""
print(fself().__doc__)
bar()
foo()
This code produces:
>abcdefg
>zxy
The great thing is I can now use this as a module and access fself in other scripts.

How to define functions based on attribute to elixir?

Let's say I have a modules Silent and Definer. I want to define a couple of functions for Silent, based on its attribute. Let me explain:
defmodule Silent do
#function_names [:a, :b, :c]
use Definer
end
defmodule Definer do
defmacro __using__(_) do
quote do
Enum.each(#function_names, fn(n) ->
def unquote(n)() do # line 5
IO.puts "a new method is here!"
end
end)
end
end
end
But this approach actually doesn't work because I have undefined function n/0 on line 5. How can I implement desired functionality?
You need to pass unquote: false to quote in Definer.__using__/1 to be able to inject an unquote fragment inside a quote.
defmodule Definer do
defmacro __using__(_) do
quote unquote: false do
Enum.each(#function_names, fn(n) ->
def unquote(n)() do # line 5
IO.puts "a new method is here!"
end
end)
end
end
end
defmodule Silent do
#function_names [:a, :b, :c]
use Definer
end
Silent.a
Silent.b
Silent.c
prints
a new method is here!
a new method is here!
a new method is here!
A similar case is documented in detail in the Kernel.SpecialForms.quote/2 docs which also mentions how to use bind_quoted if you want to both inject some variables into a quote and create unquote fragments.

Resources