Everything is well explained in [Python.Docs]: ctypes - A foreign function library for Python:
class ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=0)
Instances of this class represent loaded shared libraries. Functions in these libraries use the standard C calling convention, and are assumed to return int.
...
class ctypes.LibraryLoader(dlltype)
...
LoadLibrary(name)
Load a shared library into the process and return it. This method always returns a new instance of the library.
These prefabricated library loaders are available:
ctypes.cdll
Creates CDLL instances.
So, the 2nd form is just a convenience wrapper, there's absolutely no functional difference between them, as shown below:
>>> import ctypes as ct
>>>
>>>
>>> k32_1 = ct.CDLL("kernel32.dll") # 1.
>>> k32_2 = ct.cdll.LoadLibrary("kernel32.dll") # 2.1.
>>> k32_3 = ct.cdll.kernel32 # 2.2.
>>>
>>> k32_1, k32_2, k32_3
(<CDLL 'kernel32.dll', handle 7fff59100000 at 0x2335c444ee0>, <CDLL 'kernel32.dll', handle 7fff59100000 at 0x2335b44bc10>, <CDLL 'kernel32', handle 7fff59100000 at 0x2335c45a790>)
>>> type(k32_1), type(k32_2), type(k32_3)
(<class 'ctypes.CDLL'>, <class 'ctypes.CDLL'>, <class 'ctypes.CDLL'>)
>>>
>>> k32_1._handle == k32_2._handle == k32_3._handle
True
Use whatever suits you best.
2nd form (#2.2.) is shorter (I suppose this is its purpose).
#1. and #2.1. are the same (#2.1. is probably more explanatory (as it has LoadLibrary)) and they allow you to load a library from a custom path, or with an extension different than the default. Personally, #1. is the one I prefer.
For more details, you can take a look at [GitHub]: python/cpython - (master) cpython/Lib/ctypes/__init__.py, especially the LibraryLoader at implementation which (cdll actually is, and) is easy to understand.
Just a heads-up (probably you already know what you're getting into): loading and unloading libraries can sometimes be tricky.