Change object's class - python

In a chess game, I define p1 = Pawn().
I'd like to be able to promote it:
def promote(self, piece):
pos = f'{Config.tile_convert(self.x)}{Config.tile_convert(self.y, True)}'
try:
self = piece(pos, color=self.color, num='s')
except NameError:
print(f'Cannot promote {self.pieceid} to {piece}')
However, feeding in Queen or Bishop doesn't actually change the object from being a Pawn. I think it's because of scoping:
class Chesspiece:
...
class Pawn(ChessPiece):
def promote()
...
class Queen(Chesspiece):
...
How can I change the class of an object?

Reassigning any bare name (including self) rebinds the name; whatever was in there before is thrown away, and the name now points to something unrelated.
So when you do:
self = piece(pos, color=self.color, num='s')
you just lose access to the "real" self (the object the method was called on), you don't change the object it was called on.
The only way to do what you want is kinda hacky; to reassign __class__ on self (and change any other attributes needed to make it a valid instance of the new class).
So in this case, you might be able to just do:
self.__class__ = piece
and if attribute values might need to be recreated/revalidated, or piece might be a factory function or weirdo class whose __new__ doesn't necessarily return the type it was called on, you'd create a new piece as a template, then copy from it, like so:
# Make a new piece to copy from
newpiece = piece(pos, color=self.color, num='s')
vars(self).clear() # Clear out existing attributes
vars(self).update(vars(newpiece)) # Copy attributes from new piece
self.__class__ = newpiece.__class__ # Using newpiece.__class__ ensures it's the resulting class
# in case of factory functions, weird __new__, etc.
Note: This is usually not what you want to do. As Rocky Li mentions in the comments, the usual approach is to replace the object in whatever structure holds it, rather than updating the class in place on an existing object, e.g. if you have variable named board which is a 2D list of lists, you'd just do:
a_piece = ... # Piece to replace
new_piece_type = ... # Type of piece it's being replaced with
pos = f'{Config.tile_convert(a_piece.x)}{Config.tile_convert(a_piece.y, True)}'
# Replace entire object (and local alias name) at known coordinates with new object
board[a_piece.x][a_piece.y] = a_piece = new_piece_type(pos, color=a_piece.color, num='s')

Related

Is this sound software engineering practice for class construction?

Is this a plausible and sound way to write a class where there is a syntactic sugar #staticmethod that is used for the outside to interact with? Thanks.
###scrip1.py###
import SampleClass.method1 as method1
output = method1(input_var)
###script2.py###
class SampleClass(object):
def __init__(self):
self.var1 = 'var1'
self.var2 = 'var2'
#staticmethod
def method1(input_var):
# Syntactic Sugar method that outside uses
sample_class = SampleClass()
result = sample_class._method2(input_var)
return result
def _method2(self, input_var):
# Main method executes the various steps.
self.var4 = self._method3(input_var)
return self._method4(self.var4)
def _method3(self):
pass
def _method4(self):
pass
Answering to both your question and your comment, yes it is possible to write such a code but I see no point in doing it:
class A:
def __new__(cls, value):
return cls.meth1(value)
def meth1(value):
return value + 1
result = A(100)
print(result)
# output:
101
You can't store a reference to a class A instance because you get your method result instead of an A instance. And because of this, an existing __init__will not be called.
So if the instance just calculates something and gets discarded right away, what you want is to write a simple function, not a class. You are not storing state anywhere.
And if you look at it:
result = some_func(value)
looks exactly to what people expect when reading it, a function call.
So no, it is not a good practice unless you come up with a good use case for it (I can't remember one right now)
Also relevant for this question is the documentation here to understand __new__ and __init__ behaviour.
Regarding your other comment below my answer:
defining __init__ in a class to set the initial state (attribute values) of the (already) created instance happens all the time. But __new__ has the different goal of customizing the object creation. The instance object does not exist yet when __new__is run (it is a constructor function). __new__ is rarely needed in Python unless you need things like a singleton, say a class A that always returns the very same object instance (of A) when called with A(). Normal user-defined classes usually return a new object on instantiation. You can check this with the id() builtin function. Another use case is when you create your own version (by subclassing) of an immutable type. Because it's immutable the value was already set and there is no way of changing the value inside __init__ or later. Hence the need to act before that, adding code inside __new__. Using __new__ without returning an object of the same class type (this is the uncommon case) has the addtional problem of not running __init__.
If you are just grouping lots of methods inside a class but there is still no state to store/manage in each instance (you notice this also by the absence of self use in the methods body), consider not using a class at all and organize these methods now turned into selfless functions in a module or package for import. Because it looks you are grouping just to organize related code.
If you stick to classes because there is state involved, consider breaking the class into smaller classes with no more than five to 7 methods. Think also of giving them some more structure by grouping some of the small classes in various modules/submodules and using subclasses, because a long plain list of small classes (or functions anyway) can be mentally difficult to follow.
This has nothing to do with __new__ usage.
In summary, use the syntax of a call for a function call that returns a result (or None) or for an object instantiation by calling the class name. In this case the usual is to return an object of the intended type (the class called). Returning the result of a method usually involves returning a different type and that can look unexpected to the class user. There is a close use case to this where some coders return self from their methods to allow for train-like syntax:
my_font = SomeFont().italic().bold()
Finally if you don't like result = A().method(value), consider an alias:
func = A().method
...
result = func(value)
Note how you are left with no reference to the A() instance in your code.
If you need the reference split further the assignment:
a = A()
func = a.method
...
result = func(value)
If the reference to A() is not needed then you probably don't need the instance too, and the class is just grouping the methods. You can just write
func = A.method
result = func(value)
where selfless methods should be decorated with #staticmethod because there is no instance involved. Note also how static methods could be turned into simple functions outside classes.
Edit:
I have setup an example similar to what you are trying to acomplish. It is also difficult to judge if having methods injecting results into the next method is the best choice for a multistep procedure. Because they share some state, they are coupled to each other and so can also inject errors to each other more easily. I assume you want to share some data between them that way (and that's why you are setting them up in a class):
So this an example class where a public method builds the result by calling a chain of internal methods. All methods depend on object state, self.offset in this case, despite getting an input value for calculations.
Because of this it makes sense that every method uses self to access the state. It also makes sense that you are able to instantiate different objects holding different configurations, so I see no use here for #staticmethod or #classmethod.
Initial instance configuration is done in __init__ as usual.
# file: multistepinc.py
def __init__(self, offset):
self.offset = offset
def result(self, value):
return self._step1(value)
def _step1(self, x):
x = self._step2(x)
return self.offset + 1 + x
def _step2(self, x):
x = self._step3(x)
return self.offset + 2 + x
def _step3(self, x):
return self.offset + 3 + x
def get_multi_step_inc(offset):
return MultiStepInc(offset).result
--------
# file: multistepinc_example.py
from multistepinc import get_multi_step_inc
# get the result method of a configured
# MultiStepInc instance
# with offset = 10.
# Much like an object factory, but you
# mentioned to prefer to have the result
# method of the instance
# instead of the instance itself.
inc10 = get_multi_step_inc(10)
# invoke the inc10 method
result = inc10(1)
print(result)
# creating another instance with offset=2
inc2 = get_multi_step_inc(2)
result = inc2(1)
print(result)
# if you need to manipulate the object
# instance
# you have to (on file top)
from multistepinc import MultiStepInc
# and then
inc_obj = MultiStepInc(5)
# ...
# ... do something with your obj, then
result = inc_obj.result(1)
print(result)
Outputs:
37
13
22

how can i add data in a class object after i ran def __init__(self) function?

i created this class for my homework:
class sayfa():
isim=" "
def __init__(self,bSayisi,ySayisi,pSayisi,iSayisi,tSayisi):
self.bSayisi=bSayisi
self.ySayisi=ySayisi
self.pSayisi=pSayisi
self.iSayisi=iSayisi
self.tSayisi=tSayisi
if ((((bSayisi+ySayisi+pSayisi)/iSayisi)/tSayisi)*100)>0.2:
print(isim,"başarılı")
else:
print(isim,"başarısız")
then i called it in another .py file:
from eRate import sayfa
ybs1=sayfa(365000,65000,870,500,1125000)
ybs1.isim="YBS-1"
then i tried to work it and it gave me this error:
NameError: name 'isim' is not defined
I think i did something wrong when i'm writing class but i don't know what i actually done wrong.Can you help me?
edit:
My code worked when i put isim variable in def init but it looks weird.It looks like this:
class sayfa():
def __init__(self,bSayisi,ySayisi,pSayisi,iSayisi,tSayisi,isim):
self.isim=str(isim)
self.bSayisi=bSayisi
self.ySayisi=ySayisi
self.pSayisi=pSayisi
self.iSayisi=iSayisi
self.tSayisi=tSayisi
if ((((bSayisi+ySayisi+pSayisi)/iSayisi)/tSayisi)*100)>0.2:
print(isim,"başarılı")
else:
print(isim,"başarısız")
and when i'm adding data in class it gets weirder:
from eRate import sayfa
ybs1=sayfa(365000,65000,870,500,1125000,"YBS-1")
The error isn't with the way you're assigning things, but with the way you're accessing them.
Just as you have to do self.bSayisi to set an attribute, you have to do self.isim to access one. So:
print(self.isim, "başarılı")
(and the same for the other line…)
If you're wondering why you were able to access other values like bSayisi without self.bSayisi—that's just because you happen to have a parameter named bSayisi that happens to have the same value as self.bSayisi (because you just made that true a few lines earlier). If you changed it to, say, self.bSayisi = bSayisi*2, or you renamed the parameter to myBSayisi and did self.bSayisi = myBSayisi, you'd see that just using bSayisi instead of self.bSayisi was no longer correct.
However, while this eliminates the error, I'm not sure it actually does what you want. At the time you're doing this print, you haven't assigned an isim value to the object yet, so it's going to get the class value as a default, so it's always just going to be " ". Is that really what you wanted?
If not, you need to move the print calls to some other method that you can call later, after having assigned isim. For example:
class sayfa():
isim=" "
def __init__(self,bSayisi,ySayisi,pSayisi,iSayisi,tSayisi):
self.bSayisi=bSayisi
self.ySayisi=ySayisi
self.pSayisi=pSayisi
self.iSayisi=iSayisi
self.tSayisi=tSayisi
def displaystuff(self):
if ((((self.bSayisi+self.ySayisi+self.pSayisi)/self.iSayisi)/self.tSayisi)*100)>0.2:
print(self.isim,"başarılı")
else:
print(self.isim,"başarısız")
ybs1=sayfa(365000,65000,870,500,1125000)
ybs1.isim="YBS-1"
ybs1.displaystuff()
Of course moving the isim into the constructor works, by avoiding the problem you were running into. It's not an answer to how to add data after the __init__ method, of course, because you're instead adding the data in the __init__ method. When that's appropriate, it's the simplest answer.
But if it looks weird in this case (I'll take your word for it; I don't know exactly what this code is trying to do), it's probably the wrong answer for this particular class.
In which case, you do need to know how to add data after the __init__ method, as you asked. Or, rather, you need to know how to access that data—because you were already adding it correctly.
This is the difference between class attributes (when it is outside of the __init__ with no self.) and instance attributes (when you added it inside the __init__ with the self.).
Class attributes are a little more complicated since they pertain to all the instances of that class (you could overwrite them within some instances, but then they'd become instance attributes in those cases)... and so if you changed a class attribute, it would affect all other instances you may have created or will create in the future.
For a more in-depth discussion of class attributes vs instance attributes see this answer that summarizes this post.
Normall __init__(..) is used to initialize / instantiate your instance. I would not print in it, nor calculate (unless you calculate some other class-variables and set them).
You need to prefix your variables of the instance by self. and the static class variable with the class name to acess it:
class sayfa():
isim=" " # this is a shared class variabl (aka static)
def __init__(self,bSayisi,ySayisi,pSayisi,iSayisi,tSayisi):
self.bSayisi=bSayisi # these are all instance variables, not shared
self.ySayisi=ySayisi
self.pSayisi=pSayisi
self.iSayisi=iSayisi
self.tSayisi=tSayisi
self.unusedSum = ySayisi + pSayisi + iSayisi
def printMe(self): # lookup __str__() and __repr__() for how to output your instance
if ((((self.bSayisi+self.ySayisi+self.pSayisi)/self.iSayisi)/self.tSayisi)*100)>0.2:
print(sayfa.isim,"some text") # use the static class variable
else:
print(sayfa.isim,"some other text")
sayfa.isim = "Coffee " # you set static class variables by prefixing class name
my_sayfa_instance = sayfa(365000,65000,870,500,1125000)
other_sayfa_instance = sayfa(3600,65000,870,500,10)
my_sayfa_instance.printMe()
other_sayfa_instance.printMe()
Output:
Coffee some other text
Coffee some text

How do you create an instance of a class when the class type is stored in a variable?

So I have a somewhat long and growing list of classes in a script. At a certain point in the script I want to be able to test an arbitrary instance for its type, and then whatever that type is, I want to create a second object of the same type. I've tried researching this and I know I can accomplish this by storing every class in a dictionary, like so:
class Foo(object):
pass
class Bar(object):
pass
d = {"Foo": Foo, "Bar": Bar}
x = dict["Foo"]()
It does the trick, allowing me to use a variable or string containing the name of the class, in order to create an instance of the class. However, it requires that every time I create a new class I have to remember to also put a new entry in the dictionary--which isn't the worst thing in the world, but as they say, if you're doing the same task more than once you should make a computer do it.
Is there a better way? Can you somehow take a variable or string containing the name of a class, and without knowing what value the variable or string has, generate an instance of the class?
So this is answering your problem rather than your question, but it seems you actually want to create another instance of an object rather than find a class by name. So that is even easier because you can find the class of an object with the type function. So to create a new instance b of the same type as a but with constructor parameters args simply do:
b = type(a)(args)
All classes are in globals dictionary (dictionary containing the current scope's global variables). Get the dictionary with globals(), and then find it by name (string). As a result you will get a class, which can be instantiated with ().
class Foo(object):
pass
x = globals()['Foo']()
Not sure why Huazuo Gao didn't make that an answer, but it solves my problem exactly and I've never seen that solution in my research on this problem--so I'll go ahead and punch it up as an answer.
You can do it using the string of the name of the class and the eval() function. So
class Foo(object):
pass
a = Foo()
s = str(type(a))
# The string produced isn't quite the name of the class so it has to be stripped
# of some surrounding characters.
m = s.find('.')
n1 = s.find("'")
n2 = s[n1+1:].find("'")
s = s[m+1:n1+n2+1]
b = eval(s + "()")
produces the desired behavior.

Python3, using object instance within another class

I'm trying to modify class attribute by reference to object in __init__ method and then use it in another method. Sadly the following code sample doesn't work as expected...
CODE
class Translator:
#list of attributes
parser=None
def __init__(self):
parser = Parser_class() ...
#some other commands
def Translate(self):
something=self.parser.GenerateHead() ...
#more commands
COMPILE ERR
AttributeError: 'NoneType' object has no attribute 'GenerateHead'
I know that I can give it to the Translate method as argument, I'm just curious why this statement within Python doesn't work.
You're doing your instance attributes wrong.
First off, you don't need to declare your attributes ahead of time. Putting parser = None at the top level of the class creates a class variable named parser, which I don't think is what you want. Usually in Python you can add new instance attributes at any time by a simple assignment: instance.attr = "whatever".
Second, when you want to do an instance assignment from within a method, you need to use self to refer to the instance. If you leave off self, you'll be assigning to a local variable inside your function, not to an instance or class variable. Actually, the specific name self isn't necessary, but you do need to use the first argument to the method (and it's probably not a good idea to break the convention of naming that self).
So, to fix your code, do this:
class Translator:
# don't declare variables at class level (unless you actually want class variables)
def __init__(self):
self.parser = Parser_class() # use self to assign an instance attribute
def Translate(self):
something = self.parser.GenerateHead() # this should now work

Create new class instance from class method [duplicate]

This question already has answers here:
How can I create a copy of an object in Python?
(4 answers)
What is the purpose of the return statement? How is it different from printing?
(15 answers)
Closed 5 months ago.
I want to be able to create a new instance of an object by calling a method on an already instantiated object. For example, I have the object:
organism = Organism()
I want to be able to call organism.reproduce() and have two objects of type Organism. My method at this point looks like this:
class Organism(object):
def reproduce():
organism = Organism()
and I'm pretty sure it doesn't work (I'm not really even sure how to test it. I tried the gc method in this post). So how can I make my object create a copy of itself that's accessible just like the first object I created (with organism = Organism())?
class Organism(object):
def reproduce(self):
#use self here to customize the new organism ...
return Organism()
Another option -- if the instance (self) isn't used within the method:
class Organism(object):
#classmethod
def reproduce(cls):
return cls()
This makes sure that Organisms produce more Organisms and (hypothetical Borgs which are derived from Organisms produce more Borgs).
A side benefit of not needing to use self is that this can now be called from the class directly in addition to being able to be called from an instance:
new_organism0 = Organism.reproduce() # Creates a new organism
new_organism1 = new_organism0.reproduce() # Also creates a new organism
Finally, if both the instance (self) and the class (Organism or subclasses if called from a subclass) are used within the method:
class Organism(object):
def reproduce(self):
#use self here to customize the new organism ...
return self.__class__() # same as cls = type(self); return cls()
In each case, you'd use it as:
organism = Organism()
new_organism = organism.reproduce()
why not simply use the copy module?
import copy
organism = Organism()
replica = copy.deepcopy(organism)
What about something like this:
class Organism(object):
population = []
def __init__(self, name):
self.name = name
self.population.append(self)
def have_one_child(self, name):
return Organism(name)
def reproduce(self, names):
return [self.have_one_child(name) for name in names]
Result:
>>> a = Organism('a')
>>> len(Organism.population)
1
>>> a.reproduce(['x', 'y', 'z']) # when one organism reproduces, children are added
# to the total population
# organism produces as many children as you state
[<__main__.Organism object at 0x05F23190>, <__main__.Organism object at 0x05F230F0>, <__main__.Organism object at 0x05F23230>]
>>> for ele in Organism.population:
... print ele.name
...
a
x
y
z
>>> Organism.population[3].reproduce(['f', 'g'])
[<__main__.Organism object at 0x05F231D0>, <__main__.Organism object at 0x05F23290>]
>>> for ele in Organism.population:
... print ele.name
...
a
x
y
z
f
g
The same way you did originally, but then you have to do something with it!
organism = Organism() calls the class Organism (parentheses directly after a name is the "call" operation). This creates and returns a new instance of the class, which you then bind to the name organism.
When you execute that line in the interpreter, you now have a variable organism referring to the new Organism instance you just created.
When you write that line inside a function (including a method, because there's no difference between a method and a function "from the inside"), it does the same thing, but the variable organism is a local variable. Local variables are thrown away when the function is finished, so this does create a new Organism instance, but it doesn't achieve anything because you never gain access to it.
Your function should return any information it wants to communicate to its caller. Any local variables that you don't return are only useful if you use those variables to create something you do return.
Note that this has nothing to do with your particular problem of creating an instance inside a method; it's just how functions/methods work in general. You will need to learn how functions work before you can successfully write object-oriented programs using classes and instances; I would strongly suggest you work through some tutorials.
I believe you are asking how to copy an object.
Surprisingly (maybe), there is (almost) no standard method for this, and this is by design. The issue comes from the intrinsic ambiguity of the idea of copying, i.e.: when you copy an object property do you mean to copy it as reference (copy) or as value (deepcopy)?
However, in the majority of cases you want a consistent behavior (deepcopy or copy for all properties), in this case you can use copy module as
import copy
new_obj = copy.copy(old_obj)
or
new_obj = copy.deepcopy(old_obj)
In a generic case in which you want a more customized behavior, you use the same commands, but override the __copy__ and __deepcopy__ methods of your objects.
See more answers for details and examples, e.g.:
How to override the copy/deepcopy operations for a Python object?
from copy import copy
class Organism(object):
def __init__(self,name):
self.name=name
def setName(self,name):
self.name=name
def reproduce(self,childname):
#use deepcopy if necessary
cp = copy(self)
cp.setName("descendant from " + self.name + " " + childname)
return cp
def __str__(self):
return self.name
first = Organism("first")
second = first.reproduce("second")
print first
print second

Categories