[Python Advanced] Custom Class: Attributes

4.10.2 Attributes

4.10.2.1 getattrgetattribute

Usually we can access the attributes of an object through obj.attr. And __getattr__ is used to handle the processing we want when we get an attribute that does not exist.
By default, if we get an attribute that does not exist, an error will be reported: AttributeError.

from icecream import ic


class A:
    def __init__(self):
        self.test = 't'

    def __getattr__(self, item):
        print(f'getting {
      
      item}')
        raise AttributeError


a = A()
ic(a.test)

ic| a.test: ‘t’

If the attribute exists, the __getattr__ method will not be called. Otherwise the method will be called:

from icecream import ic


class A:
    def __init__(self):
        ...

    def __getattr__(self, item):
        print(f'getting {
      
      item}')
        raise AttributeError


a = A()
ic(a.test)

Traceback (most recent call last):
File “E:\BaiduSyncdisk\FrbPythonFiles\t1.py”, line 14, in
ic(a.test)
File “E:\BaiduSyncdisk\FrbPythonFiles\t1.py”, line 10, in getattr
raise AttributeError
AttributeError
getting test

The __getattribute__ method is to call this method as long as you try to get an attribute, regardless of whether the attribute exists or not.

from icecream import ic


class A:
    def __init__(self):
        self.data = 'abc'
        self.counter = 0

    def __getattribute__(self, name):
        if name == 'data':
            self.counter += 1
        return super().__getattribute__(name)


o = A()
ic(o.data)
ic(o.data)
ic(o.counter)

ic| o.data: ‘abc’
ic| o.data: ‘abc’
ic| o.counter: 2

Note:
1. When rewriting the __getattr__ and __getattribute__ methods, it is easy to cause infinite recursion, because as long as the attributes are referenced in these two methods, it will be easy to call these two methods again.
2. When there are two methods, when a non-existing attribute is called, only the __getattribute__ method will be called, and the __getattr__ method will not be called.

4.10.2.2 setattr

The magic method __setattr__ is called when class attributes are assigned, even in the class initialization __init__ method. We can call the default attribute assignment method of the class through super(). setattr (name, value).

from icecream import ic


class A:
    def __init__(self):
        self.data = 'abc'
        self.counter = 0

    def __setattr__(self, name, value):
        print('setattr', name, value)
        super().__setattr__(name, value)


o = A()
ic(o.data)
o.data = 'ufo'

setattr data abc
setattr counter 0
setattr data ufo
14:16:43|> o.data: ‘abc’

4.10.2.3 delattr

When we try to delete an attribute in an object with the del keyword, the __delattr__ magic method is called.

from icecream import ic


class A:
    def __init__(self):
        self.data = 'abc'
        self.counter = 0

    def __delattr__(self, name):
        print('delattr', name)
        super().__delattr__(name)


o = A()
del o.data
ic(o.data)

delattr data
Traceback (most recent call last):
File “E:\t1.py”, line 16, in
ic(o.data)
AttributeError: ‘A’ object has no attribute ‘data’

4.10.2.4 dir

Usually we can get information about an object's built-in properties and methods by using the dir function. You can customize the return result by modifying the __dir__ magic method in the object.

from icecream import ic


class A:
    def __init__(self):
        self.data = 'abc'

    def test(self):
        ...

    def __dir__(self):
        return [None]


o = A()
ic(dir(o))

14:28:15|> dir(o): [None]

4.10.2.5 getsetdelete

In Python, the magic method __get__ is used to define a descriptor. A descriptor is an object with "bound behavior" whose main purpose is to manage access to class and instance members. When accessing an attribute of an object, when Python detects that it belongs to a descriptor, it automatically calls the corresponding descriptor method.
Here is a simple example showing how to use the __get__ magic method to create a simple counter descriptor:

class Counter:
    def __init__(self):
        self.value = 0

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        self.value = value if value > 0 else 0
	

    def __delete__(self, instance):

        print(instance, 'del')

class MyClass:
    counter = Counter()

obj = MyClass()
print(obj.counter) # 输出 0
obj.counter = -1
print(obj.counter) # 输出 0
obj.counter = 10
print(obj.counter) # 输出 10
del obj.counter

<main.MyClass object at 0x000002AEF8BEBDC0> del

In the above code, we use the Counter class to create a counter descriptor and use it as the member variable counter of the MyClass class. When we try to read the counter attribute of the MyClass instance, Python will automatically call the __get__ method in the Counter class to return the value of the counter. When we try to set the counter attribute, Python will automatically call the __set__ method in the Counter class to set the value of the counter.
This descriptor ensures that the counter cannot be reset by a negative number and will only ever be incremented. In fact, our code does nothing to increment the counter, but when you use the descriptor in a real application scenario, you may add more logic in the __get__ and __set__ methods. For example, you can create a validator descriptor that will ensure that a property value satisfies certain conditions.
The __delete__ method is called when the descriptor is deleted.

4.10.2.6 slots (class attribute)

In Python, __slots__ is a class attribute (class attribute) used to limit the attributes that an instance can dynamically bind. It is a string or tuple of strings containing a list of property names allowed by the class.
Using __slots__ can effectively reduce the memory space occupied by the instance and improve the speed of accessing instance attributes. This is because it stores property names in special array slots instead of creating a dictionary in each instance to store properties.
Here is a simple example that demonstrates how to use the __slots__ attribute in a class:

class Person:
    __slots__ = ('name', 'age')
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
p = Person('John', 30)
print(p.name)  # output: John
print(p.age)   # output: 30

p.email = '[email protected]'  # AttributeError: 'Person' object has no attribute 'email'

In this example, we define a Person class and set its __slots__ to ('name', 'age'). This means that the Person class only allows dynamic binding of the name and age attributes, any other attribute will throw an AttributeError exception. In this way, we can ensure that only predefined properties are maintained in the Person class.

Guess you like

Origin blog.csdn.net/crleep/article/details/132054614