PyMagic9 lib¶
PyMagic9 - a library based on calling the stack of frames at runtime and analyzing the code object of frames. Basically, it implements some C# features. For example, it contains the nameof function and auto-implemented properties. See the documentation for more information.
- class pymagic9.PropertyMeta(name, bases, attrs)¶
This metaclass allows you to create auto-implemented properties (like in C#, where you can declare properties without explicitly defining a getter and setter), for which you can use an ellipsis or empty functions to indicate that the Python itself would create the special accessor.
Detailed working principle:
Ordinary auto-implemented properties:
property1 = property(..., ...) property1 = property(..., ..., ...)
In this case, the following are created:
dictionary (in the closure of the metaclass initializer) for recording and retrieving the value;
getter that returns a value from the dictionary (if the value is not in the dictionary, then the
AttributeErrorexception is thrown);setter that writes a value to the dictionary if there is no value yet, or the value differs from that written in the dictionary;
deleter that deletes a value from the dictionary if it exists (if the value is not in the dictionary, then the
AttributeErrorexception is thrown).
Readonly auto-implemented properties:
property1 = property(...)
In this case, the following are created:
dictionary (in the closure of the metaclass initializer) for recording and retrieving the value;
getter that returns a value from the dictionary (if the value is not in the dictionary, then the
AttributeErrorexception is thrown);setter that can be called only once from the class initializer when creating an instance (when called again, the
AttributeErrorexception is thrown);deleter that deletes a value from the dictionary if it exists (if the value is not in the dictionary, then the
AttributeErrorexception is thrown).
Auto-implemented properties with custom getter:
# getter is not empty function property1 = property(getter, ...)
In this case, a custom getter remains. Otherwise, everything is the same as in the previous paragraphs.
Auto-implemented properties with custom setter, deleter:
# setter, deleter is not empty functions property1 = property(..., setter) property1 = property(..., ..., deleter) property1 = property(..., setter, deleter)
In this case, the following are created:
dictionary (in the closure of the metaclass initializer) for recording and retrieving the value;
getter that returns a value from the dictionary (if the value is not in the dictionary, then the
AttributeErrorexception is thrown);setter that preserves the functionality of the initially defined user setter (that is, after the new value is written to the dictionary, the initially defined user setter is called).
deleter that preserves the functionality of the initially defined user deleter (that is, after deleting the value from the dictionary, the initially defined custom deleter is called).
Properties that will not be processed by the PropertyMeta metaclass:
# getter, setter, deleter is not empty functions property1 = property(getter) property1 = property(getter, setter) property1 = property(getter, setter, deleter) property1 = property(getter, setter, ...)
Examples
Import the PropertyMeta metaclass and assign it as a metaclass for the desired class:
from pymagic9 import PropertyMeta class Person(metaclass=PropertyMeta): pass
2. Create properties in this class with empty accessors (using empty function or ellipsis) to indicate that this property will be auto-implemented:
from pymagic9 import PropertyMeta class Person(metaclass=PropertyMeta): """class Person""" def __init__(self, name): self.name = name name = property(fget=...,) # readonly property age = property(fget=..., fset=...,) # ordinary property
3. Now for an ordinary property we can get and put values into it at any time. But for a readonly property, you can put a value into it only once, at the time of creating an instance of the class:
from pymagic9 import PropertyMeta class Person(metaclass=PropertyMeta): """class Person""" def __init__(self, name): self.name = name # self.name = "Sam" # raise AttributeError: 'property' is readonly (reassigning value) name = property(fget=...,) # readonly property age = property(fget=..., fset=...,) # ordinary property if __name__ == "__main__": person = Person("Tom") person.age = 24 print(person.name + ',', person.age) # Tom, 24 # person.name = "Sam" # raise AttributeError: 'property' is readonly
To delete a property value, use the del operator:
from pymagic9 import PropertyMeta class Person(metaclass=PropertyMeta): """class Person""" def __init__(self, name): self.name = name name = property(fget=...,) # readonly property age = property(fget=..., fset=...,) # ordinary property if __name__ == "__main__": person = Person("Tom") person.age = 24 print(person.name + ',', person.age) # Tom, 24 del person.name # print(person.name) # raise AttributeError: auto-implemented field does not exist or has already been # erased
5. If the getter is specified by an empty accessor (using empty function or ellipsis), and the setter is not an empty function, then setter will also be called. This can be used as a callback when assigning a value to a property:
from pymagic9 import nameof, PropertyMeta def NotifyPropertyChanged(propertyname, value): """Notify property changed""" # Do something print(propertyname + ',', value) class Person(metaclass=PropertyMeta): """class Person""" def __init__(self, name): self.name = name name = property(fget=...,) # readonly property age = property(fget=..., fset=...,) # ordinary property @property def height(self): """Person height in cm""" return @height.setter def height(self, value): NotifyPropertyChanged(nameof(self.height), value) if __name__ == "__main__": person = Person("Tom") person.age = 24 print(person.name + ',', person.age) # Tom, 24 person.height = 180 # height, 180
Similar code for Python 2.7 looks like this:
from pymagic9 import nameof, PropertyMeta __metaclass__ = PropertyMeta def NotifyPropertyChanged(propertyname, value): """Notify property changed""" # Do something print(propertyname + ', ' + str(value)) class Person: """class Person""" def __init__(self, name): self.name = name name = property(fget=Ellipsis,) # readonly property age = property(fget=Ellipsis, fset=Ellipsis,) # ordinary property @property def height(self): """Person height in cm""" return @height.setter def height(self, value): NotifyPropertyChanged(nameof(self.height), value) if __name__ == "__main__": person = Person("Tom") person.age = 24 print(person.name + ', ' + str(person.age)) # Tom, 24 person.height = 180 # height, 180
- pymagic9.getframe(__depth=0)¶
The sys._getframe function is used here if it exists in the version of python being used. Otherwise, the _getframe polyfill is used.
- pymagic9.isemptyfunction(func)¶
Checks if a function is empty or not.
- Parameters:
func (function) – The function to check.
- Returns:
True if the function is empty, False otherwise.
- Return type:
bool
- Raises:
TypeError – If the input object is not a function.
This function determines whether the given function is empty or not. An empty function is defined here as a function that: - may contain operators pass, return; - may only return None; - may contain a documentation string (classic documentation string in triple quotes, in double/single quotes as str type, and also in bytes type) and comments; - may contain any unreachable code (see odd_function in Examples); - may contain statements to have no effect (see odd_function in Examples). If something else is present in the function, then it is considered not empty. It can be useful in scenarios where you need to check if a function has any implementation or if it is just a placeholder.
The definition above may seem strange, but an empty function is defined this way for the sake of compatibility with the versions of Python supported by this project (since the interpreter behaves differently on different versions of Python, a simpler implementation of this function would return different values).
Examples
>>> def empty_function(): ... pass ... >>> print(isemptyfunction(empty_function)) True >>> def empty_function(): ... return ... >>> print(isemptyfunction(empty_function)) True >>> def empty_function(): ... return None ... >>> print(isemptyfunction(empty_function)) True >>> def empty_function(): ... # only in Python3 ... ... ... >>> print(isemptyfunction(empty_function)) True >>> def empty_function(): ... """ docstring """ ... >>> print(isemptyfunction(empty_function)) True >>> def empty_function(): ... "doc" ... >>> print(isemptyfunction(empty_function)) True >>> def empty_function(): ... b'Hello World!' ... return >>> print(isemptyfunction(empty_function)) True >>> def empty_function(): ... # comments ... return ... >>> print(isemptyfunction(empty_function)) True >>> def empty_function(): ... """ docstring """ ... return ... >>> print(isemptyfunction(empty_function)) True >>> empty_lambda = lambda x: None >>> print(isemptyfunction(empty_lambda)) True >>> def non_empty_function(): ... print("Hello, world!") ... >>> print(isemptyfunction(non_empty_function)) False >>> def non_empty_function(): ... return 0 ... >>> print(isemptyfunction(non_empty_function)) False >>> not_empty_lambda = lambda: 0 >>> print(isemptyfunction(not_empty_lambda)) False >>> # All odd_functions is empty! >>> def odd_function(): # statement (immutable) to have no effect ... None ... 100 ... True ... () ... "string" ... b'd' ... return ... >>> print(isemptyfunction(odd_function)) True >>> def odd_function(): # statement (mutable) to have no effect ... [] ... return ... >>> print(isemptyfunction(odd_function)) True >>> def odd_function(): """ docstring """; return; None; # oneline function and unreachable code >>> print(isemptyfunction(odd_function)) True >>> def odd_function(): # unreachable code ... return ... a = 2 ... >>> print(isemptyfunction(odd_function)) True
- pymagic9.isfunctionincallchain(o, __depth=-1)¶
Determines whether the given function object or code object is present in the call chain.
- Parameters:
o (FunctionType or CodeType) – The function object or code object to check.
__depth (int, optional) – The depth of the call chain to search. Default is -1, which means search the entire call chain.
- Returns:
True if the function or code object is found in the call chain, False otherwise.
- Return type:
bool
- Raises:
TypeError – If the input object is not a function or code object.
This function checks if the given function object or code object is present in the call chain of the current execution. The call chain is the sequence of function calls that led to the current point of execution.
Warning
Be careful when debugging in PyCharm - there may be incorrect behavior when a function that is being debugged (a function that has breakpoints) is passed as an argument.
Examples
>>> def foo(): ... return isfunctionincallchain(foo) ... >>> def bar(): ... return isfunctionincallchain(foo) ... >>> def baz(): ... return foo() ... >>> print(foo()) True >>> print(bar()) False >>> print(baz()) True
- pymagic9.nameof(o)¶
Returns the name of an object.
- Parameters:
o (object) – The object for which to retrieve the name.
- Returns:
The name of the object or empty string.
- Return type:
str
This function correctly determines the ‘name’ of an object, without being tied to the object itself. It can be used to retrieve the name of variables, functions, classes, modules, and more. An empty string will be returned if an explicit value or call is passed to the nameof function as an argument.
Examples
>>> var1 = [1, 2] >>> var2 = var1 >>> print(nameof(var1)) var1 >>> print(nameof(var2)) var2 >>> print(nameof(123)) empty string ("")