How to pass a class constructor to a python function - python

Suppose I have a class Foo, I want to define a function that receives the class constructor as a parameter:
def bar(class_name):
local_class = None
# TODO: if I call bar(Foo()), I want to get local_class = Foo()
How can I implement the function?

The following bar function will work. Note, the first parameter will be a class itself and not the name of a class, so "class_name", which implies that it's a str, is misleading. args will be a tuple of args to initialize klass objects with, *-unpacked in the calls to klass. You said in a later comment that you wanted to "create multiple independent objects", all of the same class and initialized with the same args, so I've revised my answer to reflect that:
def bar(klass, *args):
# Now you can create multiple independent objects of type klass,
# all initialized with the same args
obj1 = klass(*args)
obj2 = klass(*args)
# ...
# do whatever you have in mind with the objs
Your "local_class" isn't a class at all, but rather an instance of klass, so that's a bad name; and anyway you want several of them.
Assuming Foo objects are initialized with three int arguments, and Baz objects with two strings, you can call bar like so:
bar(Foo, 1, 2, 3)
bar(Baz, 'Yo', 'bro')
etc.
Especially in a dynamically-typed language like Python, reasoning about code is more difficult when variables have misleading names.

When can pass the classname as an argument to your function, and then call class_name(). E.g., if you also want to pass arguments.
class Foo:
def __init__(self, arg1, arg2):
pass
def bar1(class_name):
args = ("val1", "val2")
local_class = class_name(*args)
or
def bar2(class_name):
kwargs = {'arg1':'val1','arg2':'val2'}
local_class = class_name(**kwargs)
You can call the functions like:
one = bar1(Foo)
two = bar2(Foo)
If you really want to call the class from a string read this post. I would suggest you use #Evan Fosmark's solution because use of eval and globals should be avoided

Related

Automatically assign method arguments to instance variables

Is there a way in Python to declare instance variables from the methods arguments without the need for boilerplate writing?
For example, is there a way for self.foo, self.bar and all other arguments to be automatically declared?
def __init__(self, foo, bar, ..., last):
self.foo = foo
self.bar = bar
...
self.last = last
There actually is, though I think it's a very ugly way
class Foo:
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
Foo(name='foo', baz=123, bar=True, last=None)
As Tim Roberts suggested in the comments, using a namedtuple is a much better and cleaner way (you may also take a look at dataclasses.dataclass)
You can use a keyword argument in __init__. For example:
class MyClass:
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
Then you can create a class using whatever keyword arguments you want:
>>> mc = MyClass(foo=123, bar=456, baz=789)
>>>
Those arguments are assigned as attributes of the class instance:
>>> mc.foo
123
>>> mc.bar
456
>>> mc.baz
789
>>>
You may want to add some checks if you do this, e.g. restrict them to some known set of names. Otherwise an undesired name could override a method name for instance.
Or, you could save the keyword dictionary directly in the class instance, as a single attribute. That would insulate the class from the names. For example:
class MyClass:
def __init__(self, **kwargs):
self.args = kwargs
You could then access them through self.args.
Solution 1
As #Tim Roberts suggested this is how it can be accomplished with a namedtuple.
from collections import namedtuple
class Foo(namedtuple("Foo", ["param1", "param2"])):
pass # Rest of code goes here
my_class = Foo(1, 2)
my_class.param1 # returns 1
You can even create default arguments
namedtuple("Foo", ["param1", "param2"], defaults=[1, 2])
Solution 2
Alternatively, you can use **kwargs to set the instance variables (which doesn't use the collections library and is arguably easier) if you are fine with the user passing any random argument.
Here is an example
class Foo:
def __init__(self, **kwargs):
super().__dict__.update(kwargs)
my_class = Foo(param1=1)
my_class.param1 # returns 1
# Doesn't force you to pass the correct parameters
random_param_class = Foo(a_made_up_parameter=2)
random_param_class.param1 # Results in an error
Bear in mind that none of this is standard and most often people just declare the instance variables from within __init__.
To be honest, if you have a fixed sets of inputs, you can use this:
from inspect import getargvalues, stack
def arguments():
args = getargvalues(stack()[1][0])[-1]
del args['self']
if 'kwargs' in args:
args.update(args['kwargs'])
del args['kwargs']
return args
class myClass():
def __init__(self, foo, bar, ..., last):
# Auto update all arguments into object dictionary
self.__dict__.update(arguments())
This should do it, if you don't specify *kwargs
object = myClass(1,2,3,'foo','random')
# all the right instances will be created
# object.foo =1
# object.bar = 2

When am I not supposed to put an argument after the init function?

For school, they give me this code where they created a class 'dinosaur' with an instance variable called _type. They created a getter method called getType() to return the dinosaur's type. They then created a setter method called setType() that sets its type.
class Dinosaur:
def __init__(self):
self._type=" "
def setType(self,type):
self._type=type
def getType(self):
return self._type
# Create three dinsosaurs
d1 = Dinosaur()
d2 = Dinosaur()
d3 = Dinosaur()
# Set their types
d1.setType("T-Rex")
d2.setType("Velociraptor")
d3.setType("Stegosaurus")
# Print the types
print(d1.getType())
print(d2.getType())
print(d3.getType())
What I don't get is in the constructor, why do they not put an argument for the instance variable _type? I thought you always supposed to do this for instance variables?
When is it okay not to put an argument after self in the constructur, because I see my prof do this a lot?

More efficient way of setting default method argument to instance attribute [duplicate]

I want to pass a default argument to an instance method using the value of an attribute of the instance:
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=self.format):
print(formatting)
When trying that, I get the following error message:
NameError: name 'self' is not defined
I want the method to behave like this:
C("abc").process() # prints "abc"
C("abc").process("xyz") # prints "xyz"
What is the problem here, why does this not work? And how could I make this work?
You can't really define this as the default value, since the default value is evaluated when the method is defined which is before any instances exist. The usual pattern is to do something like this instead:
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=None):
if formatting is None:
formatting = self.format
print(formatting)
self.format will only be used if formatting is None.
To demonstrate the point of how default values work, see this example:
def mk_default():
print("mk_default has been called!")
def myfun(foo=mk_default()):
print("myfun has been called.")
print("about to test functions")
myfun("testing")
myfun("testing again")
And the output here:
mk_default has been called!
about to test functions
myfun has been called.
myfun has been called.
Notice how mk_default was called only once, and that happened before the function was ever called!
In Python, the name self is not special. It's just a convention for the parameter name, which is why there is a self parameter in __init__. (Actually, __init__ is not very special either, and in particular it does not actually create the object... that's a longer story)
C("abc").process() creates a C instance, looks up the process method in the C class, and calls that method with the C instance as the first parameter. So it will end up in the self parameter if you provided it.
Even if you had that parameter, though, you would not be allowed to write something like def process(self, formatting = self.formatting), because self is not in scope yet at the point where you set the default value. In Python, the default value for a parameter is calculated when the function is compiled, and "stuck" to the function. (This is the same reason why, if you use a default like [], that list will remember changes between calls to the function.)
How could I make this work?
The traditional way is to use None as a default, and check for that value and replace it inside the function. You may find it is a little safer to make a special value for the purpose (an object instance is all you need, as long as you hide it so that the calling code does not use the same instance) instead of None. Either way, you should check for this value with is, not ==.
Since you want to use self.format as a default argument this implies that the method needs to be instance specific (i.e. there is no way to define this at class level). Instead you can define the specific method during the class' __init__ for example. This is where you have access to instance specific attributes.
One approach is to use functools.partial in order to obtain an updated (specific) version of the method:
from functools import partial
class C:
def __init__(self, format):
self.format = format
self.process = partial(self.process, formatting=self.format)
def process(self, formatting):
print(formatting)
c = C('default')
c.process()
# c.process('custom') # Doesn't work!
c.process(formatting='custom')
Note that with this approach you can only pass the corresponding argument by keyword, since if you provided it by position, this would create a conflict in partial.
Another approach is to define and set the method in __init__:
from types import MethodType
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=self.format):
print(formatting)
self.process = MethodType(process, self)
c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')
This allows also passing the argument by position, however the method resolution order becomes less apparent (which can affect the IDE inspection for example, but I suppose there are IDE specific workarounds for that).
Another approach would be to create a custom type for these kind of "instance attribute defaults" together with a special decorator that performs the corresponding getattr argument filling:
import inspect
class Attribute:
def __init__(self, name):
self.name = name
def decorator(method):
signature = inspect.signature(method)
def wrapper(self, *args, **kwargs):
bound = signature.bind(*((self,) + args), **kwargs)
bound.apply_defaults()
bound.arguments.update({k: getattr(self, v.name) for k, v in bound.arguments.items()
if isinstance(v, Attribute)})
return method(*bound.args, **bound.kwargs)
return wrapper
class C:
def __init__(self, format):
self.format = format
#decorator
def process(self, formatting=Attribute('format')):
print(formatting)
c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')
You can't access self in the method definition. My workaround is this -
class Test:
def __init__(self):
self.default_v = 20
def test(self, v=None):
v = v or self.default_v
print(v)
Test().test()
> 20
Test().test(10)
> 10
"self" need to be pass as the first argument to any class functions if you want them to behave as non-static methods.
it refers to the object itself. You could not pass "self" as default argument as it's position is fix as first argument.
In your case instead of "formatting=self.format" use "formatting=None" and then assign value from code as below:
[EDIT]
class c:
def __init__(self, cformat):
self.cformat = cformat
def process(self, formatting=None):
print "Formating---",formatting
if formatting == None:
formatting = self.cformat
print formatting
return formatting
else:
print formatting
return formatting
c("abc").process() # prints "abc"
c("abc").process("xyz") # prints "xyz"
Note : do not use "format" as variable name, 'cause it is built-in function in python
Instead of creating a list of if-thens that span your default arguements, one can make use of a 'defaults' dictionary and create new instances of a class by using eval():
class foo():
def __init__(self,arg):
self.arg = arg
class bar():
def __init__(self,*args,**kwargs):
#default values are given in a dictionary
defaults = {'foo1':'foo()','foo2':'foo()'}
for key in defaults.keys():
#if key is passed through kwargs, use that value of that key
if key in kwargs: setattr(self,key,kwargs[key])
#if no key is not passed through kwargs
#create a new instance of the default value
else: setattr(self,key, eval(defaults[key]))
I throw this at the beginning of every class that instantiates another class as a default argument. It avoids python evaluating the default at compile... I would love a cleaner pythonic approach, but lo'.

which position is self in python class?

In the book learning python 5th edition (o'reilly Mark Lutz)page912)
class PrivateExc(Exception): pass # More on exceptions in Part VII
class Privacy:
def __setattr__(self, attrname, value): # On self.attrname = value
if attrname in self.privates:
raise PrivateExc(attrname, self) # Make, raise user-define except
else:
self.__dict__[attrname] = value # Avoid loops by using dict key
class Test1(Privacy):
privates = ['age']
class Test2(Privacy):
privates = ['name', 'pay']
def __init__(self):
self.__dict__['name'] = 'Tom' # To do better, see Chapter 39!
Maybe it is wrong in the 5th lineraise PrivateExc(attrname, self) ,
the self argument will be set as position 1st.
Will be the line changed into raise PrivateExc(self,attrname)?Why not?
Actually it doesn't matter.
Subclassing from Exception without any additional constructor doesn't restrict what you can pass as arguments to the exception class. And you can pass them in any order you want.
The arguments passed to the PrivateExc class just get stored in the instance as the instance attribute .args
Example:
>>> class MyError(Exception):
... """MyError"""
...
>>> e = MyError("foo", "bar")
>>> e.args
('foo', 'bar')
>>> e
MyError('foo', 'bar')
What this basically means in the book you're reading is;
If you were to catch the exception PrivateExc you'd do something like this:
try:
...
except PrivateExc as error:
attrname, obj = error.args
...
When you are calling a method like this:
#!/bin/python
myinstance.some_method(a,b,c)
... then this is dispatched to some_method as: some_method(myinstance, a, b, c)
The instance through which the method was invoked is passed as your first argument. This is completely different than C++ and Java ... which use an implicit "this" reference ... a pointer valid from within your method's scope but not passed to it as an argument.
I hope that answers your question, thought the code example does nothing to clarify what you're attempting to do.
I think you are just confused about parameters in function definition and function calling.
In a class, a method(instance method) has a non-optional parameter in the first position, usually named self, in the definition, like this:
class Foo:
def foo(self, another_param):
pass
And the self references the instance that you call foo function with. If you have code like this:
f=Foo()
f.foo("test")
self references the f and another_param references the "test" string in the above code.
And then in the foo function, you can use self just like other parameters.
Suppose you have a Print function like this:
def Print(x):
print "Param:", x
Then you can make you Foo class like this:
class Foo:
def foo(self, another_param):
Print(another_param) # I think this will not confuse you
Or this:
class Foo:
def foo(self, another_param):
Print(self) # Now, you may understand this, self is just a param in function calling, like another_param
And now, change the Print function to PrivateExc(you can think it a function to create a PrivateExc instance here), you may understand it either.
Hope these examples can help you understand you question.

Magic assign for custom parameters

I want to give user API for my library with easier way to distinguish different types of parameters which I pass to function. All groups of arguments are defined earlier (for now I have 3 groups), but attributes of them need to be constructed on run. I can do this in Django ORM style, where double underscore separates 2 parts of parameter. But it is very unreadable. Example:
def api_function(**kwargs):
""" Separate passed arguments """
api_function(post__arg1='foo', api__arg1='bar', post_arg2='foo2')
Better way do this SQLAlchemy, but only to compare attributes and all args are defined earlier. Example:
class API(object):
arg1 = Arg()
arg2 = Arg()
class Post(object): #...
def api_function(*args):
""" Separate passed arguments """
api_function(POST.arg1=='foo', API.arg1=='bar', POST.arg2=='foo2')
What I would like to achive is behaviour like this:
class API(object): # Magic
class POST(object): # Magic
def api_function(*args):
""" Separate passed arguments """
api_function(POST.arg1='foo', API.arg1='bar', POST.arg2='foo2')
What have I tried:
declare metamodel with defined __setattr__, but it rise on evaluation SyntaxError: keyword can't be an expression
declare __set__, but it is designed for known attributes
My questions are:
Is it even possible in Python to work like in third snippet?
If not, is there any really close solution to look like in third snippet? The best way should use assignment operator API.arg1='foo', the worst API(arg1='foo')
Requirements -- should work at least at Python 2.7. Good to work on Python 3.2.
EDIT1
My first test, which is using equality operator (but it NEVER should be use in this way):
class APIMeta(type):
def __getattr__(cls, item):
return ApiData(item, None)
class API(object):
__metaclass__ = APIMeta
def __init__(self, key, value):
self.key = key
self.value = value
def __str__(self):
return "{0}={1}".format(self.key, self.value)
def __eq__(self, other):
self.value = other
return self
def print_api(*api_data):
for a in api_data:
print(str(a))
print_api(API.page=='3', API=='bar')
It is working right, but using == is suggesting that I want to compare something and I want to assign value.
NOTE: I don't know how much I like this schema you want. But I know one annoying thing will be all the imports to call api_function. E.G. from api import POST, API, api_function
As I said in the comments, the first way is not possible. This is because assignment (=) is a statement not an expression, so it can't return a value. Source
But the other way you asked for certainly is:
class POST(object):
def __init__(self, **kwargs):
self.args = kwargs
# You'll also probably want to make this function a little safer.
def __getattr__(self, name):
return self.args[name]
def api_function(*args):
# Update this to how complicated the handling needs to be
# but you get the general idea...
post_data = None
for a in args:
if isinstance(a, POST):
post_data = a.args
if post_data is None:
raise Exception('This function needs a POST object passed.')
print post_data
Using it:
>>> api_function('foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in api_function
Exception: This function needs a POST object passed.
>>> api_function(POST(arg1='foo'))
{'arg1': 'foo'}
>>> api_function(POST(arg1='foo',
... arg2='bar'
... )
... )
{'arg1': 'foo', 'arg2': 'bar'}
Here's my solution. It's not the best in design, as the structure of the argument grouper is nested quite deep, so I'd appreciate feedback on it:
class ArgumentGrouper(object):
"""Transforms a function so that you can apply arguments in named groups.
This system isn't tested as thoroughly as something with so many moving
parts should be. Use at own risk.
Usage:
#ArgumentGrouper("foo", "bar")
def method(regular_arg, foo__arg1, bar__arg2):
print(regular_arg + foo__arg1 + bar__arg2)
method.foo(", ").bar("world!")("Hello")() # Prints "Hello, world!"
"""
def __call__(self, func):
"""Decorate the function."""
return self.Wrapper(func, self.argument_values)
def __init__(self, *argument_groups):
"""Constructor.
argument_groups -- The names of argument groups in the function.
"""
self.argument_values = {i: {} for i in argument_groups}
class Wrapper(object):
"""This is the result of decorating the function. You can call group
names as function to supply their keyword arguments.
"""
def __call__(self, *args):
"""Execute the decorated function by passing any given arguments
and predefined group arguments.
"""
kwargs = {}
for group, values in self.argument_values.items():
for name, value in values.items():
# Add a new argument in the form foo__arg1 to kwargs, as
# per the supplied arguments.
new_name = "{}__{}".format(
group,
name
)
kwargs[new_name] = value
# Invoke the function with the determined arguments.
return self.func(*args, **kwargs)
def __init__(self, func, argument_values):
"""Constructor.
func -- The decorated function.
argument_values -- A dict with the current values for group
arguments. Must be a reference to the actual dict, since each
WrappedMethod uses it.
"""
self.func = func
self.argument_values = argument_values
def __getattr__(self, name):
"""When trying to call `func.foo(arg1="bar")`, provide `foo`. TODO:
This would be better handled at initialization time.
"""
if name in self.argument_values:
return self.WrappedMethod(name, self, self.argument_values)
else:
return self.__dict__[name]
class WrappedMethod(object):
"""For `func.foo(arg1="bar")`, this is `foo`. Pretends to be a
function that takes the keyword arguments to be supplied to the
decorated function.
"""
def __call__(self, **kwargs):
"""`foo` has been called, record the arguments passed."""
for k, v in kwargs.items():
self.argument_values[self.name][k] = v
return self.wrapper
def __init__(self, name, wrapper, argument_values):
"""Constructor.
name -- The name of the argument group. (This is the string
"foo".)
wrapper -- The decorator. We need this so that we can return it
to chain calls.
argument_values -- A dict with the current values for group
arguments. Must be a reference to the actual dict, since
each WrappedMethod uses it.
"""
self.name = name
self.wrapper = wrapper
self.argument_values = argument_values
# Usage:
#ArgumentGrouper("post", "api")
def api_function(regular_arg, post__arg1, post__arg2, api__arg3):
print("Got regular args {}".format(regular_arg))
print("Got API args {}, {}, {}".format(post__arg1, post__arg2, api__arg3))
api_function.post(
arg1="foo", arg2="bar"
).api(
arg3="baz"
)
api_function("foo")
Then, usage:
#ArgumentGrouper("post", "api")
def api_function(regular_arg, post__arg1, post__arg2, api__arg3):
print("Got regular args {}".format(regular_arg))
print("Got API args {}, {}, {}".format(post__arg1, post__arg2, api__arg3))
api_function.post(
arg1="foo", arg2="bar"
).api(
arg3="baz"
)
api_function("foo")
Output:
Got regular args foo
Got API args foo, bar, baz
It should be simple to scrape argument group names by introspection.
You'll notice the argument naming convention is hardcoded into the WrappedMethod, so you'll have to make sure you're okay with that.
You can also invoke it in one statement:
api_function.post(
arg1="foo", arg2="bar"
).api(
arg3="baz"
)("foo")
Or you could add a dedicated run method which would invoke it, which would just take the place of Wrapper.__call__.
Python don't allow to use assignment operator inside any other code, so:
(a=1)
func((a=1))
will rise SyntaxError. This means that it is not possible to use it in this way. Moreover:
func(API.arg1=3)
Will be treated that left side of assignment is argument API.arg1 which is not valid name in Python for variables. Only solution is to make this in SQLAlchemy style:
func({
API.arg1: 'foo',
API.arg2: 'bar',
DATA.arg1: 'foo1',
})
or
func(**{
API.arg1: 'foo',
API.arg2: 'bar',
DATA.arg1: 'foo1',
})
or just only:
func( API(arg1='foo', arg2='bar'), POST(arg1='foo1'), POST(arg2='bar1'))
Thank you for your interest and answers.

Categories