Posts: 1,093
Threads: 143
Joined: Jul 2017
Feb-17-2024, 11:31 AM
(This post was last modified: Feb-17-2024, 11:31 AM by Pedroski55.)
I am just looking at Mandelbrot fractals. This class comes from realpython.com I know next to nothing about classes.
I saved this class as a module in my myModules folder and import the class MandelbrotSet :
from mandelbrotV2 import MandelbrotSet
Why does __contains__() use double underscore? I read the double underscore keeps the function hidden??
# mandelbrotV2.py
from dataclasses import dataclass
from math import log
@dataclass
class MandelbrotSet:
max_iterations: int
escape_radius: float = 2.0
def __contains__(self, c: complex) -> bool:
return self.stability(c) == 1
def stability(self, c: complex, smooth=False, clamp=True) -> float:
value = self.escape_count(c, smooth) / self.max_iterations
return max(0.0, min(value, 1.0)) if clamp else value
def escape_count(self, c: complex, smooth=False) -> int | float:
z = 0
for iteration in range(self.max_iterations):
z = z ** 2 + c
if abs(z) > self.escape_radius:
if smooth:
return iteration + 1 - log(log(abs(z))) / log(2)
return iteration
return self.max_iterations
Posts: 6,794
Threads: 20
Joined: Feb 2020
Dunder methods, are specially named methods that have magic powers.
https://docs.python.org/3/reference/data...ecialnames
For example, the method __str__() is called when you print an instance of a class. A class that has the methods __getitme__() and __setitem__ supports indexing or like a list or dictionary, or maybe slicing like a list, or maybe both.
Quote:object.__contains__(self, item)¶
Called to implement membership test operators. Should return true if item is in self, false otherwise. For mapping objects, this should consider the keys of the mapping rather than the values or the key-item pairs.
For objects that don’t define __contains__(), the membership test first tries iteration via __iter__(), then the old sequence iteration protocol via __getitem__(), see this section in the language reference.
In your MandelbrotSet example, calling "if complex_number in mandelbrot_instance: will call mandelbrot_instance.__contains__(complex_number) to get the answer.
Pedroski55 likes this post
Posts: 7,319
Threads: 123
Joined: Sep 2016
To give a eaiser example then is easier to see how special method work.
They give extra behavior to a class,in this case can now use in operator and len() .
class MyList:
def __init__(self, data):
self.data = data
def __contains__(self, item):
return item in self.data
def __len__(self):
return len(self.data) Use Class:
>>> my_list = MyList([1, 2, 3, 4, 5])
>>> my_list.data
[1, 2, 3, 4, 5]
>>> # Now will "in" work,because of __contains__
>>> print(3 in my_list)
True
>>> print(9 in my_list)
False
>>> # len() works because of __len__
>>> len(my_list)
5 In summary, by defining the __contains__ method,
the MandelbrotSet class allows you to use the in operator to check if a complex number is part of the Mandelbrot set.
This makes it easy to check membership in the set with syntax like if c in mandelbrot_set .
Pedroski55 likes this post
Posts: 1,093
Threads: 143
Joined: Jul 2017
On more question if I may: In the Mandelbrot fractal tutorial, the classes shown do not use __init__
When is __init__ necessary?
For example, this Class, although I know it is just an example:
class MyList:
def __init__(self, data):
self.data = data
def __contains__(self, item):
return item in self.data
def __len__(self):
return len(self.data) can be reduced to this:
class ML:
data: list
a = ML()
a.data = [1, 2, 3, 4, 5]
a.data
[1, 2, 3, 4, 5]
len(a.data)
5 What do I gain from using __init__ ??
Posts: 7,319
Threads: 123
Joined: Sep 2016
Feb-19-2024, 11:11 AM
(This post was last modified: Feb-19-2024, 11:16 AM by snippsat.)
Using __init__ in classes is fundamental for initializing(getting data in from outside) to the newly created objects.
While it's true that you can manually set attributes on an instance as in your second example.
Also try len(a) and see that it won't work as that what __len__ dos in my class.
Classes with an __init__ method are generally more readable and maintainable.
Anyone reading your code can easily understand what attributes an instance of the class is expected to have and what initial state it will be in.
Consistency:
Using __init__ ensures that every instance of the class is initialized consistently.
Without __init__ you rely on manually setting attributes after object creation,
which can lead to errors or inconsistencies if some attributes are forgotten or mistyped
So can also add documentation and eg type hint to make even more clear what the class dos.
Then will see when create a object that it need a list of int and help(MtList) will work.
class MyList:
"""A custom list class that encapsulates a list and provides
special methods to interact with its data.
"""
def __init__(self, data: list[int]):
self.data = data
def __contains__(self, item: int) -> bool:
"""Check if the item exists in the data."""
return item in self.data
def __len__(self) -> int:
"""Return the number of items in the data."""
return len(self.data) To give one more example the more realistic as there often several values that you what to initialized class with.
class Car:
"""Represents a car with specific attributes and capabilities."""
def __init__(self, color: str, mileage: float, fuel_efficiency: float):
self.color = color
self.mileage = mileage
self.fuel_efficiency = fuel_efficiency
def calculate_gas_usage(self, distance: float) -> float:
"""Calculate the amount of gas used over a given distance."""
return distance / self.fuel_efficiency
def __repr__(self):
return f"Car(color={self.color!r}, mileage={self.mileage}, fuel_efficiency={self.fuel_efficiency})"
def __str__(self):
return f"A {self.color} car with {self.mileage} miles, fuel efficiency {self.fuel_efficiency} mpg." Use this class.
>>> my_car = Car('red', 10, 14.5)
>>> # This work because of __repr__
>>> my_car
Car(color='red', mileage=10, fuel_efficiency=14.5)
>>> # This work because of __str__
>>> print(my_car)
A red car with 10 miles, fuel efficiency 14.5 mpg.
>>> my_car.calculate_gas_usage(100)
6.896551724137931 >>> help(Car)
Help on class Car in module __main__:
class Car(builtins.object)
| Car(color: str, mileage: float, fuel_efficiency: float)
|
| Represents a car with specific attributes and capabilities.
|
| Methods defined here:
|
| __init__(self, color: str, mileage: float, fuel_efficiency: float)
| Initialize self. See help(type(self)) for accurate signature.
|
| __repr__(self)
| Return repr(self).
|
| __str__(self)
| Return str(self).
|
| calculate_gas_usage(self, distance: float) -> float
| Calculate the amount of gas used over a given distance.
Posts: 2,125
Threads: 11
Joined: May 2017
(Feb-19-2024, 07:39 AM)Pedroski55 Wrote: On more question if I may: In the Mandelbrot fractal tutorial, the classes shown do not use __init__
The __init__ method initializes the instance, but the use of the decorator @dataclass overwrite the __init__ method with its own and calls __post_init__ , if this method exists.
Class vs Dataclass
from dataclasses import dataclass
# type annotations are optional and not checked during runtime
class Foo1:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
print(self.name, self.age)
# type annotations for dataclass is mandatory, but nothing is checked if the type is right
@dataclass
class Foo2:
name : str
age : int
def __post_init__(self):
print(self.name, self.age)
print(Foo1("Foo1", 10))
print(Foo2("Foo2", 10)) # <- the dataclass gives you a nice representation Output: Foo1 10
<__main__.Foo1 object at 0x000002AAC81F1820>
Foo2 10
Foo2(name='Foo2', age=10)
Pedroski55 likes this post
Posts: 7,319
Threads: 123
Joined: Sep 2016
Feb-19-2024, 12:41 PM
(This post was last modified: Feb-19-2024, 12:41 PM by snippsat.)
So a good example bye DeaD_EyE using @dataclass.
As you new to classes Pedroski55,you should first understand the fundamentals of classes.
Car class could be written like this using @dataclass,
will automatically adding generated special methods such as __init_ _ and __repr__ to the class.
from dataclasses import dataclass
@dataclass
class Car:
"""Represents a car with specific attributes and capabilities."""
color: str
mileage: float
fuel_efficiency: float
def calculate_gas_usage(self, distance: float) -> float:
"""Calculate the amount of gas used over a given distance."""
return distance / self.fuel_efficiency Using the class see that it work the same,could add __str__ to make just the same.
Now __repr__ work the same for object and print() .
>>> my_car = Car('blue', 5, 14.5)
>>> my_car.color
'blue'
>>> # __repr__ are added automatically
>>> my_car
Car(color='blue', mileage=5, fuel_efficiency=14.5)
>>> # Work the same for print add __str__ if want to change
>>> print(my_car)
Car(color='blue', mileage=5, fuel_efficiency=14.5)
>>> my_car.calculate_gas_usage(70.5)
4.862068965517241
Pedroski55 likes this post
Posts: 1,093
Threads: 143
Joined: Jul 2017
Thank you both for your help!
I read that a class is basically a dictionary. I believe the value of a dictionary key:value pair can be a function.
So in the case of class Car() do we have:
Car = {"color": 'blue', "mileage": 5, "fuel_efficiency": 14.5, "efficiency": calculate_gas_usage() }
???
Posts: 7,319
Threads: 123
Joined: Sep 2016
Feb-19-2024, 03:38 PM
(This post was last modified: Feb-19-2024, 03:38 PM by snippsat.)
(Feb-19-2024, 02:21 PM)Pedroski55 Wrote: I read that a class is basically a dictionary. I believe the value of a dictionary key:value pair can be a function.
So in the case of class Car() do we have:
Car = {"color": 'blue', "mileage": 5, "fuel_efficiency": 14.5, "efficiency": calculate_gas_usage() } Now is classes💪 is lot more that just a dictionary,it store values values from __init__ internally.
Can look at this:
>>> my_car = Car('blue', 8.8, 10.5)
>>> my_car.__dict__
{'color': 'blue', 'fuel_efficiency': 10.5, 'mileage': 8.8}
>>> my_car.__dict__['color']
'blue' The calculate_gas_usage method is not stored in this dictionary.
Instead, it's a part of the class definition and is accessed through the class's namespace.
When you call my_car.calculate_gas_usage(distance) ,
Python automatically passes my_car as the first argument to the calculate_gas_usage method.
>>> my_car.calculate_gas_usage(275.5)
26.238095238095237
Posts: 6,794
Threads: 20
Joined: Feb 2020
Feb-19-2024, 03:51 PM
(This post was last modified: Feb-19-2024, 03:51 PM by deanhystad.)
Not quite. calculate_gas_milage() would try to call a function named calculate_gas_milage and return a result. This would either raise a NameError, or map "efficiency" to the result, not the function. More importantly, python knows that it needs to change instance.method(args) into insance.__class__.method(instance, args) when it is calling an instance method of a class. It would not know to do that if you just stuffed a function in a dictionary.
I'm occasionally tasked with maintaining Python projects that use dictionaries to mimic classes, even though Python always had classes. Let me just say that doing this is a bad idea and leave it at that. Shudder!
|