Python 3.7 static dictionary of class instances [duplicate] - python

This question already has answers here:
How do I type hint a method with the type of the enclosing class?
(7 answers)
Closed 2 years ago.
I am reusing a popular c++ idiom where a class contains a static dictionary of class instances:
class Zzz:
elements = {}
def __init__(self, name):
self._name = name
Zzz.elements[name] = self
#staticmethod
def list_instances():
for k in Zzz.elements.items():
print(k)
It worked fine until I added type annotation, now python complains that Zzz is an unknown type: NameError: name 'Zzz' is not defined
from typing import Dict
class Zzz:
elements: Dict[str,Zzz] = {} <---- here

You can forward-reference your type defining it as a string instead.
from typing import Dict
class Zzz:
elements: Dict[str, 'Zzz']
Edit by the way, you can easily auto-populate this static dictionary implementing a __init_subclass__() method.
class Zzz:
elements: Dict[str, 'Zzz'] = {}
name: str
def __init_subclass__(cls, **kw):
cls.elements[cls.name] = cls
class ZzzImpl(Zzz):
name = 'foo'
assert Zzz.elements['foo'] is ZzzImpl

At the time the annotation is "read", Zzz does not yet exist. Python 3.7 still evaluates the annotations at definition time; at which in this case it is still undefined.
This is covered by Pep563:
from __futures__ import annotations

Related

Can I control how Classes called? [duplicate]

This question already has answers here:
How to print instances of a class using print()?
(12 answers)
What is the difference between __str__ and __repr__?
(28 answers)
How can I choose a custom string representation for a class itself (not instances of the class)?
(7 answers)
Closed 1 year ago.
I wrote some code and was not sure how to make it work as expected,
class Bee():
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
bee = Bee(name='Bumble', identifier=1)
print(str(bee))
->> Should Print: 1 Bumble
I thought of creating a function in Class Bee as follows:
def get_hive(self):
return '{} {}'.format(self.identifier, self.name)
However, I am not sure how, when I create an object of class Bee, to assign given attributes instead of address of the memory cell.
My code currently ofc prints smth like: <main.Bee object at 0x000001EA24F49460> instead of the attributes.
If you want str(bee) to return a string as you describe, implement a __str__ method:
class Bee():
def __init__(self, name: str, identifier: int):
self.name = name
self.identifier = identifier
def __str__(self) -> str:
return f"{self.identifier} {self.name}"
bee = Bee('Bumble', 1)
print(bee)
prints:
1 Bumble
Note that print will automatically call str on any parameter that's not already a str, so you can just say print(bee) instead of print(str(bee)).

Why is my class factory not populating my dynamic object's `__dict__` attribute

I have a class factory which dynamically builds new types (not objects/instances). The code is currently working per-se, but I can't figure out where the attributes I give it are stored. Below is an image of my driver code and output:
All attributes and methods are set and working as expected, but they're not registered in the __dict__ property. This is not ok, since a downstream use case will search __dict__ for those custom types. The entire "working" factory is here:
from typing import Iterable, Any
from types_extensions import void
class DynamicClassFactory:
def __init__(self, default_base_classes: Iterable[type] = None, prefix: str = '', suffix: str = '',
attributes: dict[str, Any] = None) -> void:
self.prefix: str = prefix
self.suffix: str = suffix
self.mixins: list[type] = [x for x in default_base_classes or (object,)]
self.attributes: dict[str, Any] = attributes or {}
def build(self, class_name: str, extra_mixins: Iterable[type] = (),
attributes: dict[str, Any] = None) -> type:
return type(
self.prefix + class_name + self.suffix,
self._create_bases(extra_base_classes=extra_mixins),
{**self.attributes, **(attributes or {})}
)
def _create_bases(self, extra_base_classes: Iterable[type]) -> tuple[type]:
return tuple(self.mixins + [x for x in extra_base_classes])
I have looked online and mainly used this as a guide: https://realpython.com/python-metaclasses/ (for this particular use case of type(), Ctrl+F in the page and search "You can also call type() with three arguments" without the quotes.
Thanks in advance!
PS: I know I don't have correct error handling for illegal characters in names, it's planned.
When you pass attributes to the type class, you're actually passing class attributes instead of instance attributes. From documentation:
The dict dictionary contains attribute and method definitions for the class body; it may be copied or wrapped before becoming the __dict__ attribute. The following two statements create identical type objects:
class X:
a = 1
X = type('X', (), dict(a=1))
So the following holds true:
my_class2 = factory.build(...)
print(my_class2.__dict__) # Outputs {..., x: 1, y: 2}
obj2 = my_class2()
print(obj2.__dict__) # Outputs {}
You may ask how can you access obj2.y successfully. That's because when Python does not find a attribute in the instance (in this case, obj2), it'll look up in the class attributes (in this case, my_class2). Since there is one, it'll evaluate to it.
Figured it out. Since I'm setting class properties, I need to access it via obj2.__class__.__dict__ instead of obj2.__dict__

Python 3 dictionary with known keys typing

I'm using Python 3 typing feature for better autocomplete.
Many times I have functions that return key/value (dictionary) with specific keys. super simple example:
def get_info(name):
name_first_letter = name[0]
return {'my_name': name, 'first_letter': name_first_letter}
I want to add type hinting to this function to tell others who use this function what to expect.
I can do something like:
class NameInfo(object):
def __init__(self, name, first_letter):
self.name = name
self.first_letter = first_letter
and then change the function signature to:
def get_info(name) -> NameInfo:
But it requires too much code for each dictionary.
What is the best practice in that case?
As pointed out by Blckknght, you and Stanislav Ivanov in the comments, you can use NamedTuple:
from typing import NamedTuple
class NameInfo(NamedTuple):
name: str
first_letter: str
def get_info(name: str) -> NameInfo:
return NameInfo(name=name, first_letter=name[0])
Starting from Python 3.8 you can use TypedDict which is more similar to what you want:
from typing import TypedDict
class NameInfo(TypedDict):
name: str
first_letter: str
def get_info(name: str) -> NameInfo:
return {'name': name, 'first_letter': name[0]}

Get the name under which a variable or property is stored [duplicate]

This question already has answers here:
Getting the name of a variable as a string
(32 answers)
Closed 6 years ago.
Is there a way to get the name under which a variable or a property is stored?
Something like:
class X:
def __init__(self):
self.name = __name_under_which_I_m_stored__
jeff = X()
print(jeff.name) # yields 'jeff'
Pretty sure that this is not possible, but you never know... Furthermore, I'm aware that the name could be unspecified (do_something(X())) or ambiguous (jeff=frank=X()).
The Python VM has no way to know the literal name of a name. For objects, you can access the name via __name__ attribute.
If you want to do this, you'll need to implement a mapping of names to values. You can do this with a dict.
class X:
def __init__(self):
self.names = {};
def set(self, name, value):
self.names[name] = value
def get(self, name):
return (name, self.names[value])
jeff = X()
jeff.set("name", "jeff");
print(jeff.get("name")) # yields ("name", "jeff")

dynamically create a type [duplicate]

This question already has answers here:
How to avoid having class data shared among instances?
(7 answers)
Closed 9 years ago.
I can create class definition dynamically, like there:
class_name = 'Human'
base_classes = (object,)
attributes = {'name':'',
'books':list(),
'say_hello':lambda self: sys.stdout.write('Hello!')}
Human = type(class_name, base_classes, attributes)
uzumaxy = Human()
uzumaxy.name = 'Maxim'
uzumaxy.books.append('Programming via .NET')
print(uzumaxy.name) # Out: "Maxim"
print(uzumaxy.books) # Out: "['Programming via .NET']"
grandrey = Human()
grandrey.name = 'Andrey'
grandrey.books.append('Programming via python')
print(grandrey.name) # Out: "Andrey"
print(uzumaxy.name) # Out: "Maxim"
print(grandrey.books) # Out: "['Programming via .NET', 'Programming via python']"
print(uzumaxy.books) # Out: "['Programming via .NET', 'Programming via python']", but i'm expecting: "['Programming via .NET']"
Seems, attribute "name" is instance-level, but why attribute "books" is class-level?
How I can dynamically create definition of type with instance-level attributes? Thx for help.
Actually, both name and books are class-level. It's just that strings are immutable, so when you use uzumaxy.name = "Maxim", you're adding a new attribute called name hiding the class name, while for uzumaxy.books.append("Programming via .NET"), you're accessing the existing (class) books and modifying it. Your code is equivalent to this:
class Human(object):
name = ''
books = []
def say_hello(self):
sys.stdout.write("Hello!")
Note the same behavior. Traditionally, we'd fix that by writing Human like this:
class Human(object):
def __init__(self):
self.name = ''
self.books = []
def say_hello(self):
sys.stdout.write("Hello!")
Now each instance has its own name and books. To do this with a dynamically-created type, you do essentially the same thing, giving it an __init__:
def init_human(self):
self.name = ''
self.books = []
attributes = { '__init__': init_human,
'say_hello': lambda self: sys.stdout.write("Hello!") }
They're both class-level. name is simply immutable, so it doesn't look class-level at first glance. Most attempts to modify it will create a new instance-level attribute with the same name.
Just like when writing a class the normal way, you need to create instance attributes in the constructor:
def __init__(self):
self.name = ''
self.books = []
def say_hello(self):
# This prints a newline. The original didn't.
print 'Hello!'
Human = type('Human', (object,), {
'__init__': __init__,
'say_hello': say_hello,
})

Categories

Resources