>>> class Foo:
... def bar(self):
... print('foo')
...
>>> class Baz:
... def bar(self):
... print('baz')
...
>>> f = Foo()
>>> f
<__main__.Foo object at 0x7fa311e7a278>
>>> f.bar()
foo
>>> f.__class__ = Baz
>>> f
<__main__.Baz object at 0x7fa311e7a278>
>>> f.bar()
bazIs this like an unsafe pointer cast where “you are responsible, and it will likely blow up spectacularly if you don’t know what you are doing” or is it something safer that will magically work e.g with types of different size?
- Does that work even if the types had fields? Yup!
- What about it the fields had a different total size? Totally fine!
- What if Baz had no parameterless constructor (I.e only had a contractor that guaranteed arg > 0 for example)? Then you throw an exception when you call the constructor.
- Is this like an unsafe pointer cast where “you are responsible, and it will likely blow up spectacularly if you don’t know what you are doing” or is it something safer that will magically work e.g with types of different size?
Mostly the former, but if you're coming from a strongly typed compiled language, it may feel like a bit of the latter too, since if you don't run into any obvious runtime incompatibilities, it'll all "just seem to work" even if the underlying classes are 200% different.
(Disclaimer, I've been using python for ~decade now but am still always nervous to speak authoritatively about it, since I work with peers who are FAR deeper in the actual implementation than I am, and I run the risk of being subtlety incorrect.)
I was assuming that doing
myobj.__class__ = whatever
Would not call any constructors?The problems would come later when you try to use any functionality of the new __class__ in the manner of the old __class__ for which the new one is not compatible. e.g.:
class A:
def go(self):
print("A ran!")
class B:
def go(self):
print ("B ran!")
class C:
def go(self, foo):
print ("C ran!", foo)
a = A()
a.__class__ = B
a.go()
B ran!
In [11]:
a = A()
a.__class__ = C
a.go()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-b67fe8fb94e1> in <module>()
1 a = A()
2 a.__class__ = C
----> 3 a.go()
TypeError: go() missing 1 required positional argument: 'foo' class A:
i = -1
def __init__(self, num):
if num <= 0:
raise(Exception('noo!'))
def go(self):
print('A has value {}'.format(self.i))
class B:
def go(self):
print ("B ran!")
b = B()
b.go()
b.__class__ = A
b.go()As with most things, there are trade-offs.
I wouldn't qualify this as an advantage; it encourages bad code and it precludes a lot of good tooling (including tooling which would automate the sort of refactoring you'd like to avoid).
Basically, there are values in both. There is no silver bullet, these are all different tools in our belt, and some work better in some situations and others in others -- hammers and screwdrivers.
One thing I love about Python is that it allows a lot of different tools and methods, allowing you to select what works in a given situation. Many of those tools are far from perfect, but they get the job done in a very satisfying manner, more often than not.
You have it exactly backwards. Generics and interfaces are commitments to formalism and the promises of static typing. Before that there was ‘void’, the canonical dynamic type. ‘void’ (or ‘Object’ in Java and other static OOPs) became less common, not more.
If there is value in dynamic types, I’ve scarcely seen it, (and I use Python every day). I’m told that dynamic typing really shines through Clojure and other lisps, but I haven’t gotten to that level yet.
> One thing I love about Python is that it allows a lot of different tools and methods, allowing you to select what works in a given situation. Many of those tools are far from perfect, but they get the job done in a very satisfying manner, more often than not.
This is a nice property when you want to play around with a new paradigm without learning a new syntax and toolchain, but when you’re working on a team, agreeing about the paradigm and features and style quickly becomes tedious.
In Python, for example, you can just parse your JSON or XML into a dict and start grabbing what you need, typically with fairly minimal hassle involved in dealing with things like missing values or some clown sticking a string into a field you thought could only contain ints.
In Java, by contrast, ugh. You can laboriously litter your code with a whole mess of bean classes and have Jackson take care of the parsing. But if you want to obey Postel's Law, this quickly turns into a foamy quagmire of beans and annotations and whatnot that's at least an order of magnitude larger than the code that actually interacts with the input, and still throws an exception the moment someone populates the timestamp field with ISO 8601 instead of seconds since 1970/01/01. Or leaves it unpopulated. Or alternatively you can ask for a Map<String, Object>. That approach isn't actually any more strongly typed than what a dynamic language offers, but it does have the advantage of justifying the price of that fancy ergo keyboard.
I'm not a big dynamic typing fan overall, but I do think that the .NET folks were on to something when they added the DLR extensions to .NET and gave us ExpandoObjects.
And Go isn't even a particularly good example of JSON handling in statically typed languages; OCaml, for instance, does a much better job.
This has nothing to do with dynamic/static typing and everything to do with weak/strong typing.
That's a matter of opinion, not a fact. It can be thought of as a shortcut for an applied mixin, without the needless pollution and boilerplate.