Kaedah ajaib dalam Python

WBOY
WBOYke hadapan
2023-04-13 10:25:061441semak imbas

Kaedah ajaib dalam Python ialah kaedah khas yang membolehkan anda menambah "ajaib" pada kelas Ia sering dinamakan dengan dua garis bawah.

Kaedah ajaib dalam Python

Kaedah ajaib Python, juga dikenali sebagai kaedah dunder (garis bawah berganda). Selalunya, kami menggunakannya untuk perkara mudah seperti pembina (init), perwakilan rentetan (str, repr) atau pengendali aritmetik (tambah/mul). Sebenarnya, terdapat banyak kaedah yang mungkin anda tidak pernah dengar tetapi sangat berguna Dalam artikel ini, kami akan menyelesaikan kaedah ajaib ini!

Saiz iterator

Kita semua tahu kaedah __len__, yang boleh digunakan untuk melaksanakan fungsi len() pada kelas kontena. Tetapi bagaimana jika anda ingin mendapatkan panjang objek kelas yang melaksanakan iterator

it = iter(range(100))
 print(it.__length_hint__())
 # 100
 next(it)
 print(it.__length_hint__())
 # 99
 
 a = [1, 2, 3, 4, 5]
 it = iter(a)
 print(it.__length_hint__())
 # 5
 next(it)
 print(it.__length_hint__())
 # 4
 a.append(6)
 print(it.__length_hint__())
 # 5
Apa yang anda perlu lakukan ialah melaksanakan kaedah __length_hint__, iaitu kaedah terbina dalam pada iterator (tidak dihasilkan? penukar), seperti yang anda lihat di atas, dan juga menyokong perubahan panjang dinamik. Walau bagaimanapun, sesuai dengan namanya, ini hanyalah pembayang dan tidak dijamin tepat sepenuhnya: untuk lelang senarai, anda boleh mendapatkan hasil yang tepat, tetapi untuk lelaran lain ia tidak pasti. Tetapi walaupun ia tidak tepat, ia boleh membantu kami mendapatkan maklumat yang kami perlukan, seperti yang dijelaskan dalam PEP 424.

length_hint mesti mengembalikan integer (selain itu TypeError dinaikkan) atau NotImplemented, dan tidak diperlukan untuk tepat. Ia mungkin mengembalikan nilai yang sama ada lebih besar atau lebih kecil daripada saiz sebenar bekas. Nilai pulangan NotImplemented menunjukkan bahawa tiada anggaran panjang terhingga Ia mungkin tidak mengembalikan nilai negatif (selain itu ValueError dinaikkan).

Metaprogramming

Kebanyakan kaedah ajaib yang jarang dilihat adalah berkaitan dengan metaprogramming, dan sementara metaprogramming mungkin bukan sesuatu yang kita perlu gunakan setiap hari, terdapat beberapa helah berguna yang boleh Menggunakannya.

Salah satu helah tersebut ialah menggunakan __init_subclass__ sebagai jalan pintas untuk melanjutkan fungsi kelas asas tanpa perlu berurusan dengan metaclass:

 class Pet:
def __init_subclass__(cls, /, default_breed, **kwargs):
super().__init_subclass__(**kwargs)
cls.default_breed = default_breed
 
 class Dog(Pet, default_name="German Shepherd"):
pass
Dalam kod di atas kami menambah hujah kata kunci pada kelas asas, parameter ini boleh ditetapkan apabila menentukan subkelas. Kes penggunaan sebenar mungkin menggunakan kaedah ini apabila anda ingin mengendalikan parameter yang dibekalkan dan bukannya hanya menyerahkan kepada harta.

kelihatan sangat kabur dan jarang digunakan, tetapi sebenarnya anda mungkin pernah menemuinya berkali-kali, kerana ia biasanya digunakan semasa membina API, seperti dalam SQLAlchemy atau Flask Views tiba.

Kaedah sihir metaclass yang lain ialah __call__. Kaedah ini membenarkan penyesuaian perkara yang berlaku apabila contoh kelas dipanggil:

 class CallableClass:
def __call__(self, *args, **kwargs):
print("I was called!")
 
 instance = CallableClass()
 
 instance()
 # I was called!
Anda boleh menggunakan ini untuk mencipta kelas yang tidak boleh dipanggil:

 class NoInstances(type):
def __call__(cls, *args, **kwargs):
raise TypeError("Can't create instance of this class")
 
 class SomeClass(metaclass=NoInstances):
@staticmethod
def func(x):
print('A static method')
 
 instance = SomeClass()
 # TypeError: Can't create instance of this class
Untuk kelas dengan statik sahaja kaedah, Kaedah ini digunakan tanpa membuat contoh kelas.

Satu lagi senario serupa ialah corak tunggal - kelas hanya boleh mempunyai paling banyak satu contoh:

 class Singleton(type):
def __init__(cls, *args, **kwargs):
cls.__instance = None
super().__init__(*args, **kwargs)
 
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance
else:
return cls.__instance
 
 class Logger(metaclass=Singleton):
def __init__(self):
print("Creating global Logger instance")
Kelas tunggal mempunyai __contoh peribadi - jika tidak, ia akan menjadi Cipta dan tetapkan nilai, ia hanya akan dikembalikan jika ia sudah wujud.

Andaikan anda mempunyai kelas dan anda ingin mencipta contoh kelas itu tanpa memanggil __init__. Kaedah __new__ boleh membantu dengan perkara ini:

 class Document:
def __init__(self, text):
self.text = text
 
 bare_document = Document.__new__(Document)
 print(bare_document.text)
 # AttributeError: 'Document' object has no attribute 'text'
 
 setattr(bare_document, "text", "Text of the document")
Dalam sesetengah kes, kita mungkin perlu memintas proses biasa untuk mencipta tika, dan kod di atas menunjukkan cara untuk melakukannya. Daripada memanggil Document(…), kami memanggil Document.__new__(Document), yang mencipta contoh kosong tanpa memanggil __init__. Oleh itu, atribut contoh (teks dalam kes ini) tidak dimulakan, jadi kita perlu menggunakan fungsi setattr tambahan untuk menetapkan nilai (ia juga kaedah ajaib __setattr__).

Mengapa anda melakukan ini? Kerana kita mungkin mahu menggantikan pembina, seperti:

 class Document:
def __init__(self, text):
self.text = text
 
@classmethod
def from_file(cls, file): # Alternative constructor
d = cls.__new__(cls)
# Do stuff...
return d
Kaedah from_file ditakrifkan di sini, yang bertindak sebagai pembina, mula-mula menggunakan __new__ untuk mencipta contoh, dan kemudian mengkonfigurasinya tanpa memanggil __init__ itu .

Kaedah ajaib seterusnya yang berkaitan dengan pengaturcaraan meta ialah __getattr__. Kaedah ini dipanggil apabila akses harta biasa gagal. Ini boleh digunakan untuk mewakilkan akses/panggilan kepada kaedah yang hilang kepada kelas lain:

 class String:
def __init__(self, value):
self._value = str(value)
 
def custom_operation(self):
pass
 
def __getattr__(self, name):
return getattr(self._value, name)
 
 s = String("some text")
 s.custom_operation() # Calls String.custom_operation()
 print(s.split()) # Calls String.__getattr__("split") and delegates to str.split
 # ['some', 'text']
 
 print("some text" + "more text")
 # ... works
 print(s + "more text")
 # TypeError: unsupported operand type(s) for +: 'String' and 'str'
Kami ingin menambah beberapa fungsi tambahan pada kelas (seperti custom_operation di atas) untuk menentukan pelaksanaan tersuai rentetan. Tetapi kami tidak mahu melaksanakan semula setiap kaedah rentetan, seperti split, join, capitalize, dsb. Di sini kita boleh menggunakan __getattr__ untuk memanggil kaedah rentetan sedia ada ini.

Walaupun ini berfungsi untuk kaedah biasa, ambil perhatian bahawa dalam contoh di atas, kaedah ajaib __add__ (yang menyediakan operasi seperti sambungan) tidak mendapat perwakilan. Jadi, jika kita mahu mereka bekerja juga, kita perlu melaksanakannya semula.

自省(introspection)

最后一个与元编程相关的方法是__getattribute__。它一个看起来非常类似于前面的__getattr__,但是他们有一个细微的区别,__getattr__只在属性查找失败时被调用,而__getattribute__是在尝试属性查找之前被调用。

所以可以使用__getattribute__来控制对属性的访问,或者你可以创建一个装饰器来记录每次访问实例属性的尝试:

 def logger(cls):
original_getattribute = cls.__getattribute__
 
def getattribute(self, name):
print(f"Getting: '{name}'")
return original_getattribute(self, name)
 
cls.__getattribute__ = getattribute
return cls
 
 @logger
 class SomeClass:
def __init__(self, attr):
self.attr = attr
 
def func(self):
...
 
 instance = SomeClass("value")
 instance.attr
 # Getting: 'attr'
 instance.func()
 # Getting: 'func'

装饰器函数logger 首先记录它所装饰的类的原始__getattribute__方法。然后将其替换为自定义方法,该方法在调用原始的__getattribute__方法之前记录了被访问属性的名称。

魔法属性

到目前为止,我们只讨论了魔法方法,但在Python中也有相当多的魔法变量/属性。其中一个是__all__:

 # some_module/__init__.py
 __all__ = ["func", "some_var"]
 
 some_var = "data"
 some_other_var = "more data"
 
 def func():
return "hello"
 
 # -----------
 
 from some_module import *
 
 print(some_var)
 # "data"
 print(func())
 # "hello"
 
 print(some_other_var)
 # Exception, "some_other_var" is not exported by the module

这个属性可用于定义从模块导出哪些变量和函数。我们创建了一个Python模块…/some_module/单独文件(__init__.py)。在这个文件中定义了2个变量和一个函数,只导出其中的2个(func和some_var)。如果我们尝试在其他Python程序中导入some_module的内容,我们只能得到2个内容。

但是要注意,__all__变量只影响上面所示的* import,我们仍然可以使用显式的名称导入函数和变量,比如import some_other_var from some_module。

另一个常见的双下划线变量(模块属性)是__file__。这个变量标识了访问它的文件的路径:

 from pathlib import Path
 
 print(__file__)
 print(Path(__file__).resolve())
 # /home/.../directory/examples.py
 
 # Or the old way:
 import os
 print(os.path.dirname(os.path.abspath(__file__)))
 # /home/.../directory/

这样我们就可以结合__all__和__file__,可以在一个文件夹中加载所有模块:

# Directory structure:
 # .
 # |____some_dir
 # |____module_three.py
 # |____module_two.py
 # |____module_one.py
 
 from pathlib import Path, PurePath
 modules = list(Path(__file__).parent.glob("*.py"))
 print([PurePath(f).stem for f in modules if f.is_file() and not f.name == "__init__.py"])
 # ['module_one', 'module_two', 'module_three']

最后一个我重要的属性是的是__debug__。它可以用于调试,但更具体地说,它可以用于更好地控制断言:

 # example.py
 def func():
if __debug__:
print("debugging logs")
 
# Do stuff...
 
 func()

如果我们使用python example.py正常运行这段代码,我们将看到打印出“调试日志”,但是如果我们使用python -O example.py,优化标志(-O)将把__debug__设置为false并删除调试消息。因此,如果在生产环境中使用-O运行代码,就不必担心调试过程中被遗忘的打印调用,因为它们都不会显示。

创建自己魔法方法?

我们可以创建自己的方法和属性吗?是的,你可以,但你不应该这么做。

双下划线名称是为Python语言的未来扩展保留的,不应该用于自己的代码。如果你决定在你的代码中使用这样的名称,那么将来如果它们被添加到Python解释器中,这就与你的代码不兼容了。所以对于这些方法,我们只要记住和使用就好了。

Atas ialah kandungan terperinci Kaedah ajaib dalam Python. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:51cto.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam